serializer.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. def serialize(nodes):
  2. """Serialize nodes to CSS syntax.
  3. This should be used for :term:`component values`
  4. instead of just :meth:`tinycss2.ast.Node.serialize` on each node
  5. as it takes care of corner cases such as ``;`` between declarations,
  6. and consecutive identifiers
  7. that would otherwise parse back as the same token.
  8. :type nodes: :term:`iterable`
  9. :param nodes: An iterable of :class:`tinycss2.ast.Node` objects.
  10. :returns: A :obj:`string <str>` representing the nodes.
  11. """
  12. chunks = []
  13. _serialize_to(nodes, chunks.append)
  14. return ''.join(chunks)
  15. def serialize_identifier(value):
  16. """Serialize any string as a CSS identifier
  17. :type value: :obj:`str`
  18. :param value: A string representing a CSS value.
  19. :returns:
  20. A :obj:`string <str>` that would parse as an
  21. :class:`tinycss2.ast.IdentToken` whose
  22. :attr:`tinycss2.ast.IdentToken.value` attribute equals the passed
  23. ``value`` argument.
  24. """
  25. if value == '-':
  26. return r'\-'
  27. if value[:2] == '--':
  28. return '--' + serialize_name(value[2:])
  29. if value[0] == '-':
  30. result = '-'
  31. value = value[1:]
  32. else:
  33. result = ''
  34. c = value[0]
  35. result += (
  36. c if c in ('abcdefghijklmnopqrstuvwxyz_'
  37. 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F else
  38. r'\A ' if c == '\n' else
  39. r'\D ' if c == '\r' else
  40. r'\C ' if c == '\f' else
  41. '\\%X ' % ord(c) if c in '0123456789' else
  42. '\\' + c
  43. )
  44. result += serialize_name(value[1:])
  45. return result
  46. def serialize_name(value):
  47. return ''.join(
  48. c if c in ('abcdefghijklmnopqrstuvwxyz-_0123456789'
  49. 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or ord(c) > 0x7F else
  50. r'\A ' if c == '\n' else
  51. r'\D ' if c == '\r' else
  52. r'\C ' if c == '\f' else
  53. '\\' + c
  54. for c in value
  55. )
  56. def serialize_string_value(value):
  57. return ''.join(
  58. r'\"' if c == '"' else
  59. r'\\' if c == '\\' else
  60. r'\A ' if c == '\n' else
  61. r'\D ' if c == '\r' else
  62. r'\C ' if c == '\f' else
  63. c
  64. for c in value
  65. )
  66. def serialize_url(value):
  67. return ''.join(
  68. r"\'" if c == "'" else
  69. r'\"' if c == '"' else
  70. r'\\' if c == '\\' else
  71. r'\ ' if c == ' ' else
  72. r'\9 ' if c == '\t' else
  73. r'\A ' if c == '\n' else
  74. r'\D ' if c == '\r' else
  75. r'\C ' if c == '\f' else
  76. r'\(' if c == '(' else
  77. r'\)' if c == ')' else
  78. c
  79. for c in value
  80. )
  81. # https://drafts.csswg.org/css-syntax/#serialization-tables
  82. def _serialize_to(nodes, write):
  83. """Serialize an iterable of nodes to CSS syntax.
  84. White chunks as a string by calling the provided :obj:`write` callback.
  85. """
  86. bad_pairs = BAD_PAIRS
  87. previous_type = None
  88. for node in nodes:
  89. serialization_type = (node.type if node.type != 'literal'
  90. else node.value)
  91. if (previous_type, serialization_type) in bad_pairs:
  92. write('/**/')
  93. elif previous_type == '\\' and not (
  94. serialization_type == 'whitespace' and
  95. node.value.startswith('\n')):
  96. write('\n')
  97. node._serialize_to(write)
  98. if serialization_type == 'declaration':
  99. write(';')
  100. previous_type = serialization_type
  101. BAD_PAIRS = set(
  102. [(a, b)
  103. for a in ('ident', 'at-keyword', 'hash', 'dimension', '#', '-', 'number')
  104. for b in ('ident', 'function', 'url', 'number', 'percentage',
  105. 'dimension', 'unicode-range')] +
  106. [(a, b)
  107. for a in ('ident', 'at-keyword', 'hash', 'dimension')
  108. for b in ('-', '-->')] +
  109. [(a, b)
  110. for a in ('#', '-', 'number', '@')
  111. for b in ('ident', 'function', 'url')] +
  112. [(a, b)
  113. for a in ('unicode-range', '.', '+')
  114. for b in ('number', 'percentage', 'dimension')] +
  115. [('@', b) for b in ('ident', 'function', 'url', 'unicode-range', '-')] +
  116. [('unicode-range', b) for b in ('ident', 'function', '?')] +
  117. [(a, '=') for a in '$*^~|'] +
  118. [('ident', '() block'), ('|', '|'), ('/', '*')]
  119. )