/support/common/css/css.py

https://github.com/mfferreira/titanium_mobile · Python · 402 lines · 215 code · 76 blank · 111 comment · 27 complexity · 8a3f1083f35f739b4a1550cbe7d816f3 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. '''
  3. Classes representing CSS syntactic concepts.
  4. '''
  5. import re
  6. import itertools
  7. import serialize
  8. __all__ = ('Hexcolor', 'Function', 'Uri', 'String', 'Ident',
  9. 'Term', 'Declaration', 'Ruleset', 'Charset', 'Page',
  10. 'Media', 'Import', 'Stylesheet')
  11. class SyntaxObject(object):
  12. '''An abstract type of syntactic construct.'''
  13. def __str__(self):
  14. '''
  15. Returns an ASCII string representation.
  16. Delegated to subclasses by calling `self.datum(str)`.
  17. '''
  18. return self.datum(str)
  19. def __unicode__(self):
  20. '''
  21. Returns an Unicode string representation.
  22. Delegated to subclasses by calling `self.datum(str)`.
  23. '''
  24. return self.datum(unicode)
  25. re_hexcolor = re.compile(r'#[0-9a-fA-F]{3,6}$')
  26. class Hexcolor(SyntaxObject):
  27. '''
  28. An RGB color in hex notation.
  29. '''
  30. def __init__(self, value):
  31. '''
  32. The given value must begin with a # character and contain 3 or 6 hex digits.
  33. '''
  34. if not re.match(re_hexcolor,value):
  35. raise ValueError, '''Hexcolor values must start with # and contain 3 or 6 hex digits.'''
  36. self.value = value[1:]
  37. def __repr__(self):
  38. return 'Hexcolor(%r)' % ('#'+self.value,)
  39. def datum(self, serializer):
  40. return serialize.serialize_Hexcolor(self, serializer)
  41. class Function(object):
  42. '''
  43. A term in functional notation, e.g. colors specified with rgb().
  44. Note: although URIs are specified with the functional notation url(),
  45. they are a distinct type of data.
  46. '''
  47. def __init__(self, name, parameters):
  48. self.name = name
  49. self.parameters = parameters
  50. def __repr__(self):
  51. return 'Function(%r, %r)' % (self.name, self.parameters)
  52. def datum(self, serializer):
  53. return serialize.serialize_Function(self, serializer)
  54. class Uri(SyntaxObject):
  55. '''
  56. An URI.
  57. '''
  58. def __init__(self, url):
  59. if isinstance(url, String):
  60. url = url.value
  61. self.url = url
  62. def __repr__(self):
  63. return 'Uri(%r)' % (self.url,)
  64. def datum(self, serializer):
  65. return serialize.serialize_Uri(self, serializer)
  66. class String(SyntaxObject):
  67. '''
  68. A string of characters delimited by quotation marks.
  69. '''
  70. def __init__(self, value):
  71. self.value = value
  72. def __repr__(self):
  73. return 'String(%r)' % (self.value,)
  74. def datum(self, serializer):
  75. return serialize.serialize_String(self, serializer)
  76. class Ident(SyntaxObject):
  77. '''
  78. An identifier.
  79. '''
  80. def __init__(self, name):
  81. self.name = name
  82. def __repr__(self):
  83. return 'Ident(%r)' % (self.name,)
  84. def datum(self, serializer):
  85. return serialize.serialize_Ident(self, serializer)
  86. class Term(SyntaxObject):
  87. '''
  88. An expression term, other than a Ident, Function or Hexcolor.
  89. Quantitative terms, such as EMS may have a - or + sign as
  90. a unary operator.
  91. '''
  92. def __init__(self, value, unary_operator=None):
  93. if unary_operator and -1 == '-+'.find(unary_operator):
  94. raise ValueError, '''unary operator, if given, must be - or +'''
  95. self.value = value
  96. self.unary_operator = unary_operator
  97. def __repr__(self):
  98. r = 'Term(' + repr(self.value)
  99. if self.unary_operator:
  100. r += ', unary_operator=' + repr(self.unary_operator)
  101. r += ')'
  102. return r
  103. def datum(self, serializer):
  104. return serialize.serialize_Term(self, serializer)
  105. class Declaration(SyntaxObject):
  106. '''
  107. A property-value declaration with an optional important flag.
  108. '''
  109. def __init__(self, property, value, important=False):
  110. self.property = property
  111. self.value = value
  112. self.important = important
  113. def __repr__(self):
  114. r = 'Declaration(' + repr(self.property)
  115. r += ', ' + repr(self.value)
  116. if self.important:
  117. r += ', important=True'
  118. r += ')'
  119. return r
  120. def datum(self, serializer):
  121. return serialize.serialize_Declaration(self, serializer)
  122. class Ruleset(SyntaxObject):
  123. '''
  124. A list of declarations for a given list of selectors.
  125. '''
  126. def __init__(self, selectors, declarations=None):
  127. # Implementation detail: declarations are stored in a list, rather
  128. # than a property => value mapping, because a property may be
  129. # repeated in the Ruleset. (Semantically, the last value takes
  130. # precedence over any earlier values for the same property.)
  131. self.selectors = selectors
  132. self.declarations = declarations or list()
  133. def __repr__(self):
  134. r = 'Ruleset(' + repr(self.selectors)
  135. if self.declarations:
  136. r += ', declarations=' + repr(self.declarations)
  137. r += ')'
  138. return r
  139. def __iter__(self):
  140. '''Iterates the list of declarations.'''
  141. return iter(self.declarations)
  142. def __len__(self):
  143. '''Returns the number of declarations.'''
  144. return len(self.declarations)
  145. def __getitem__(self, index):
  146. '''Returns the declaration at the given index.'''
  147. return self.declarations[index]
  148. def __contains__(self, declaration):
  149. '''Indicates whether the given declaration is present.'''
  150. return declaration in self.declarations
  151. def append(self, declaration):
  152. '''
  153. Appends a declaration to the end of the Ruleset.
  154. Modifies the list of declarations *in place.*
  155. '''
  156. if not isinstance(declaration, Declaration):
  157. raise ArgumentError, 'Expected a Declaration.'
  158. self.declarations.append(declaration)
  159. def datum(self, serializer):
  160. return serialize.serialize_Ruleset(self, serializer)
  161. class Charset(SyntaxObject):
  162. '''
  163. A @charset rule indicating the character encoding of a stylesheet.
  164. '''
  165. def __init__(self, encoding):
  166. self.encoding = encoding
  167. def __repr__(self):
  168. return 'Charset(%r)' % (self.encoding,)
  169. def datum(self, serializer):
  170. return serialize.serialize_charset(self, serializer)
  171. class Page(SyntaxObject):
  172. '''
  173. A @page rule statement containing a list of declarations.
  174. The rule may have a pseudo-page specifer like :left or :right.
  175. '''
  176. def __init__(self, declarations=None, pseudo_page=None):
  177. self.declarations = declarations or list()
  178. self.pseudo_page = pseudo_page
  179. def __repr__(self):
  180. r = 'Page(' + repr(self.declarations)
  181. if self.pseudo_page:
  182. r += ', pseudo_page=' + repr(self.pseudo_page)
  183. r += ')'
  184. return r
  185. def __iter__(self):
  186. '''Iterates the list of declarations.'''
  187. return iter(self.declarations)
  188. def __len__(self):
  189. '''Returns the number of declarations.'''
  190. return len(self.declarations)
  191. def __getitem__(self, index):
  192. '''Returns the declaration at the given index.'''
  193. return self.declarations[index]
  194. def __contains__(self, item):
  195. '''Indicates whether the given declaration is present.'''
  196. return item in self.declarations
  197. def append(self, declaration):
  198. '''
  199. Appends a declaration to the end of the Page rule.
  200. Modifies the Page rule *in place.*
  201. '''
  202. if not isinstance(declaration, Declaration):
  203. raise ArgumentError, 'Expected a Declaration.'
  204. self.declarations.append(declaration)
  205. def datum(self, serializer):
  206. return serialize.serialize_Page(self, serializer)
  207. class Media(SyntaxObject):
  208. '''An @media rule statement containing a list of rulesets.'''
  209. def __init__(self, media_types, rulesets=None):
  210. self.media_types = media_types
  211. self.rulesets = rulesets or list()
  212. def __repr__(self):
  213. r = 'Media(' + repr(self.media_types)
  214. if self.rulesets:
  215. r += ', rulesets=' + repr(self.rulesets)
  216. r += ')'
  217. return r
  218. def __iter__(self):
  219. '''Iterates the list of rulesets.'''
  220. return iter(self.rulesets)
  221. def __len__(self):
  222. '''Returns the number of rulesets.'''
  223. return len(self.rulesets)
  224. def __getitem__(self, index):
  225. '''Returns the ruleset at the given index.'''
  226. return self.rulesets[index]
  227. def __contains__(self, item):
  228. '''Indicates whether the given ruleset is present.'''
  229. return item in self.rulesets
  230. def append(self, ruleset):
  231. '''
  232. Appends a Ruleset to the end of the Media rule.
  233. Modifies the list of rulesets *in place.*
  234. '''
  235. if not isinstance(ruleset, Ruleset):
  236. raise ArgumentError, 'Expected a Ruleset.'
  237. self.ruleset.append(ruleset)
  238. def datum(self, serializer):
  239. return serialize.serialize_Media(self, serializer)
  240. class Import(SyntaxObject):
  241. '''
  242. An @import rule statement.
  243. May have an optional list of media type specifiers.
  244. '''
  245. def __init__(self, source, media_types=None):
  246. if not isinstance(source, Uri):
  247. source = Uri(source)
  248. self.source = source
  249. self.media_types = media_types or list()
  250. def __repr__(self):
  251. r = 'Import(' + repr(self.source)
  252. if self.media_types:
  253. r += ', media_types=' + repr(self.media_types)
  254. r += ')'
  255. return r
  256. def datum(self, serializer):
  257. return serialize.serialize_Import(self, serializer)
  258. class Stylesheet(SyntaxObject):
  259. '''
  260. A CSS stylesheet containing a list of statements.
  261. May have an optional list of import rules and an optional
  262. character set specification.
  263. '''
  264. def __init__(self, statements, imports=None, charset=None):
  265. self.statements = statements
  266. self.imports = imports or list()
  267. self.charset = charset
  268. def __repr__(self):
  269. r = 'Stylesheet(' + repr(self.statements)
  270. if self.imports:
  271. r += ', imports=' + repr(self.imports)
  272. if self.charset:
  273. r += ', charset=' + repr(self.charset)
  274. r += ')'
  275. return r
  276. def __iter__(self):
  277. '''
  278. Iterates the rules in the order of charset, imports, then other statements.
  279. '''
  280. its = list()
  281. if self.charset:
  282. its.append([self.charset])
  283. if self.imports:
  284. its.append(self.imports)
  285. its.append(self.statements)
  286. return itertools.chain(*its)
  287. def __len__(self):
  288. ''' Returns the total number of rules.'''
  289. n = len(self.statements) + len(self.imports)
  290. if self.charset:
  291. n += 1
  292. return n
  293. def __getitem__(self, key):
  294. return list(self)[key]
  295. def __contains__(self, item):
  296. '''
  297. Indicates whether the given rule is in the top level of the stylesheet.
  298. '''
  299. return (item is not None and
  300. (item is self.charset or
  301. item in self.imports or
  302. item in self.statements))
  303. def append(self, rule):
  304. '''
  305. Appends a rule to the end of the Stylesheet.
  306. Modifies the Stylesheet *in place.*
  307. '''
  308. if isinstance(rule, Charset):
  309. self.charset = rule
  310. elif isinstance(rule, Import):
  311. self.imports.append(rule)
  312. else:
  313. self.statements.append(rule)
  314. def datum(self, serializer):
  315. return serialize.serialize_Stylesheet(self, serializer)