1 """CSS2Properties (partly!) implements DOM Level 2 CSS CSS2Properties used
2 by CSSStyleDeclaration
3
4 TODO: CSS2Properties
5 If an implementation does implement this interface, it is expected to
6 understand the specific syntax of the shorthand properties, and apply
7 their semantics; when the margin property is set, for example, the
8 marginTop, marginRight, marginBottom and marginLeft properties are
9 actually being set by the underlying implementation.
10
11 When dealing with CSS "shorthand" properties, the shorthand properties
12 should be decomposed into their component longhand properties as
13 appropriate, and when querying for their value, the form returned
14 should be the shortest form exactly equivalent to the declarations made
15 in the ruleset. However, if there is no shorthand declaration that
16 could be added to the ruleset without changing in any way the rules
17 already declared in the ruleset (i.e., by adding longhand rules that
18 were previously not declared in the ruleset), then the empty string
19 should be returned for the shorthand property.
20
21 For example, querying for the font property should not return
22 "normal normal normal 14pt/normal Arial, sans-serif", when
23 "14pt Arial, sans-serif" suffices. (The normals are initial values, and
24 are implied by use of the longhand property.)
25
26 If the values for all the longhand properties that compose a particular
27 string are the initial values, then a string consisting of all the
28 initial values should be returned (e.g. a border-width value of
29 "medium" should be returned as such, not as "").
30
31 For some shorthand properties that take missing values from other
32 sides, such as the margin, padding, and border-[width|style|color]
33 properties, the minimum number of sides possible should be used; i.e.,
34 "0px 10px" will be returned instead of "0px 10px 0px 10px".
35
36 If the value of a shorthand property can not be decomposed into its
37 component longhand properties, as is the case for the font property
38 with a value of "menu", querying for the values of the component
39 longhand properties should return the empty string.
40
41 TODO: CSS2Properties DOMImplementation
42 The interface found within this section are not mandatory. A DOM
43 application can use the hasFeature method of the DOMImplementation
44 interface to determine whether it is supported or not. The feature
45 string for this extended interface listed in this section is "CSS2"
46 and the version is "2.0".
47
48
49 cssvalues
50 =========
51 contributed by Kevin D. Smith, thanks!
52
53 "cssvalues" is used as a property validator.
54 it is an importable object that contains a dictionary of compiled regular
55 expressions. The keys of this dictionary are all of the valid CSS property
56 names. The values are compiled regular expressions that can be used to
57 validate the values for that property. (Actually, the values are references
58 to the 'match' method of a compiled regular expression, so that they are
59 simply called like functions.)
60
61 """
62 __all__ = ['CSS2Properties', 'cssvalues']
63 __docformat__ = 'restructuredtext'
64 __version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $'
65
66 import re
67
68 """
69 Define some regular expression fragments that will be used as
70 macros within the CSS property value regular expressions.
71 """
72 MACROS = {
73 'ident': r'[-]?{nmstart}{nmchar}*',
74 'name': r'{nmchar}+',
75 'nmstart': r'[_a-z]|{nonascii}|{escape}',
76 'nonascii': r'[^\0-\177]',
77 'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?',
78 'escape': r'{unicode}|\\[ -~\200-\777]',
79
80 'int': r'[-]?\d+',
81 'nmchar': r'[\w-]|{nonascii}|{escape}',
82 'num': r'[-]?\d+|[-]?\d*\.\d+',
83 'number': r'{num}',
84 'string': r'{string1}|{string2}',
85 'string1': r'"(\\\"|[^\"])*"',
86 'string2': r"'(\\\'|[^\'])*'",
87 'nl': r'\n|\r\n|\r|\f',
88 'w': r'\s*',
89
90 'integer': r'{int}',
91 'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
92 'angle': r'0|{num}(deg|grad|rad)',
93 'time': r'0|{num}m?s',
94 'frequency': r'0|{num}k?Hz',
95 'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
96 'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)',
97 'percentage': r'{num}%',
98 'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset',
99 'border-color': '{color}',
100 'border-width': '{length}|thin|medium|thick',
101
102 'background-color': r'{color}|transparent|inherit',
103 'background-image': r'{uri}|none|inherit',
104 'background-position': r'({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right))|((left|center|right)\s*(top|center|bottom))|inherit',
105 'background-repeat': r'repeat|repeat-x|repeat-y|no-repeat|inherit',
106 'background-attachment': r'scroll|fixed|inherit',
107
108 'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)',
109 'counter': r'counter\({w}{identifier}{w}(?:,{w}{list-style-type}{w})?\)',
110 'identifier': r'{ident}',
111 'family-name': r'{string}|{identifier}',
112 'generic-family': r'serif|sans-serif|cursive|fantasy|monospace',
113 'absolute-size': r'(x?x-)?(small|large)|medium',
114 'relative-size': r'smaller|larger',
115 'font-family': r'(({family-name}|{generic-family}){w},{w})*({family-name}|{generic-family})|inherit',
116 'font-size': r'{absolute-size}|{relative-size}|{length}|{percentage}|inherit',
117 'font-style': r'normal|italic|oblique|inherit',
118 'font-variant': r'normal|small-caps|inherit',
119 'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit',
120 'line-height': r'normal|{number}|{length}|{percentage}|inherit',
121 'list-style-image': r'{uri}|none|inherit',
122 'list-style-position': r'inside|outside|inherit',
123 'list-style-type': r'disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-(latin|alpha)|upper-(latin|alpha)|armenian|georgian|none|inherit',
124 'margin-width': r'{length}|{percentage}|auto',
125 'outline-color': r'{color}|invert|inherit',
126 'outline-style': r'{border-style}|inherit',
127 'outline-width': r'{border-width}|inherit',
128 'padding-width': r'{length}|{percentage}',
129 'specific-voice': r'{identifier}',
130 'generic-voice': r'male|female|child',
131 'content': r'{string}|{uri}|{counter}|attr\({w}{identifier}{w}\)|open-quote|close-quote|no-open-quote|no-close-quote',
132 'border-attrs': r'{border-width}|{border-style}|{border-color}',
133 'background-attrs': r'{background-color}|{background-image}|{background-repeat}|{background-attachment}|{background-position}',
134 'list-attrs': r'{list-style-type}|{list-style-position}|{list-style-image}',
135 'font-attrs': r'{font-style}|{font-variant}|{font-weight}',
136 'outline-attrs': r'{outline-color}|{outline-style}|{outline-width}',
137 'text-attrs': r'underline|overline|line-through|blink',
138 }
139
140 """
141 Define the regular expressions for validation all CSS values
142 """
143 cssvalues = {
144 'azimuth': r'{angle}|(behind\s+)?(left-side|far-left|left|center-left|center|center-right|right|far-right|right-side)(\s+behind)?|behind|leftwards|rightwards|inherit',
145 'background-attachment': r'{background-attachment}',
146 'background-color': r'{background-color}',
147 'background-image': r'{background-image}',
148 'background-position': r'{background-position}',
149 'background-repeat': r'{background-repeat}',
150
151 'background': r'{background-attrs}(\s+{background-attrs})*|inherit',
152 'border-collapse': r'collapse|separate|inherit',
153 'border-color': r'({border-color}|transparent)(\s+({border-color}|transparent)){0,3}|inherit',
154 'border-spacing': r'{length}(\s+{length})?|inherit',
155 'border-style': r'{border-style}(\s+{border-style}){0,3}|inherit',
156 'border-top': r'{border-attrs}(\s+{border-attrs})*|inherit',
157 'border-right': r'{border-attrs}(\s+{border-attrs})*|inherit',
158 'border-bottom': r'{border-attrs}(\s+{border-attrs})*|inherit',
159 'border-left': r'{border-attrs}(\s+{border-attrs})*|inherit',
160 'border-top-color': r'{border-color}|transparent|inherit',
161 'border-right-color': r'{border-color}|transparent|inherit',
162 'border-bottom-color': r'{border-color}|transparent|inherit',
163 'border-left-color': r'{border-color}|transparent|inherit',
164 'border-top-style': r'{border-style}|inherit',
165 'border-right-style': r'{border-style}|inherit',
166 'border-bottom-style': r'{border-style}|inherit',
167 'border-left-style': r'{border-style}|inherit',
168 'border-top-width': r'{border-width}|inherit',
169 'border-right-width': r'{border-width}|inherit',
170 'border-bottom-width': r'{border-width}|inherit',
171 'border-right-width': r'{border-width}|inherit',
172 'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit',
173 'border': r'{border-attrs}(\s+{border-attrs})*|inherit',
174 'bottom': r'{length}|{percentage}|auto|inherit',
175 'caption-side': r'top|bottom|inherit',
176 'clear': r'none|left|right|both|inherit',
177 'clip': r'{shape}|auto|inherit',
178 'color': r'{color}|inherit',
179 'content': r'normal|{content}(\s+{content})*|inherit',
180 'counter-increment': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit',
181 'counter-reset': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit',
182 'cue-after': r'{uri}|none|inherit',
183 'cue-before': r'{uri}|none|inherit',
184 'cue': r'({uri}|none|inherit){1,2}|inherit',
185 'cursor': r'((({uri}{w},{w})*)?(auto|crosshair|default|pointer|move|(e|ne|nw|n|se|sw|s|w)-resize|text|wait|help|progress))|inherit',
186 'direction': r'ltr|rtl|inherit',
187 'display': r'inline|block|list-item|run-in|inline-block|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|none|inherit',
188 'elevation': r'{angle}|below|level|above|higher|lower|inherit',
189 'empty-cells': r'show|hide|inherit',
190 'float': r'left|right|none|inherit',
191 'font-family': r'{font-family}',
192 'font-size': r'{font-size}',
193 'font-style': r'{font-style}',
194 'font-variant': r'{font-variant}',
195 'font-weight': r'{font-weight}',
196 'font': r'({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{font-family}|caption|icon|menu|message-box|small-caption|status-bar|inherit',
197 'height': r'{length}|{percentage}|auto|inherit',
198 'left': r'{length}|{percentage}|auto|inherit',
199 'letter-spacing': r'normal|{length}|inherit',
200 'line-height': r'{line-height}',
201 'list-style-image': r'{list-style-image}',
202 'list-style-position': r'{list-style-position}',
203 'list-style-type': r'{list-style-type}',
204 'list-style': r'{list-attrs}(\s+{list-attrs})*|inherit',
205 'margin-right': r'{margin-width}|inherit',
206 'margin-left': r'{margin-width}|inherit',
207 'margin-top': r'{margin-width}|inherit',
208 'margin-bottom': r'{margin-width}|inherit',
209 'margin': r'{margin-width}(\s+{margin-width}){0,3}|inherit',
210 'max-height': r'{length}|{percentage}|none|inherit',
211 'max-width': r'{length}|{percentage}|none|inherit',
212 'min-height': r'{length}|{percentage}|none|inherit',
213 'min-width': r'{length}|{percentage}|none|inherit',
214 'orphans': r'{integer}|inherit',
215 'outline-color': r'{outline-color}',
216 'outline-style': r'{outline-style}',
217 'outline-width': r'{outline-width}',
218 'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit',
219 'overflow': r'visible|hidden|scroll|auto|inherit',
220 'padding-top': r'{padding-width}|inherit',
221 'padding-right': r'{padding-width}|inherit',
222 'padding-bottom': r'{padding-width}|inherit',
223 'padding-left': r'{padding-width}|inherit',
224 'padding': r'{padding-width}(\s+{padding-width}){0,3}|inherit',
225 'page-break-after': r'auto|always|avoid|left|right|inherit',
226 'page-break-before': r'auto|always|avoid|left|right|inherit',
227 'page-break-inside': r'avoid|auto|inherit',
228 'pause-after': r'{time}|{percentage}|inherit',
229 'pause-before': r'{time}|{percentage}|inherit',
230 'pause': r'({time}|{percentage}){1,2}|inherit',
231 'pitch-range': r'{number}|inherit',
232 'pitch': r'{frequency}|x-low|low|medium|high|x-high|inherit',
233 'play-during': r'{uri}(\s+(mix|repeat))*|auto|none|inherit',
234 'position': r'static|relative|absolute|fixed|inherit',
235 'quotes': r'({string}\s+{string})(\s+{string}\s+{string})*|none|inherit',
236 'richness': r'{number}|inherit',
237 'right': r'{length}|{percentage}|auto|inherit',
238 'speak-header': r'once|always|inherit',
239 'speak-numeral': r'digits|continuous|inherit',
240 'speak-punctuation': r'code|none|inherit',
241 'speak': r'normal|none|spell-out|inherit',
242 'speech-rate': r'{number}|x-slow|slow|medium|fast|x-fast|faster|slower|inherit',
243 'stress': r'{number}|inherit',
244 'table-layout': r'auto|fixed|inherit',
245 'text-align': r'left|right|center|justify|inherit',
246 'text-decoration': r'none|{text-attrs}(\s+{text-attrs})*|inherit',
247 'text-indent': r'{length}|{percentage}|inherit',
248 'text-transform': r'capitalize|uppercase|lowercase|none|inherit',
249 'top': r'{length}|{percentage}|auto|inherit',
250 'unicode-bidi': r'normal|embed|bidi-override|inherit',
251 'vertical-align': r'baseline|sub|super|top|text-top|middle|bottom|text-bottom|{percentage}|{length}|inherit',
252 'visibility': r'visible|hidden|collapse|inherit',
253 'voice-family': r'({specific-voice}|{generic-voice}{w},{w})*({specific-voice}|{generic-voice})|inherit',
254 'volume': r'{number}|{percentage}|silent|x-soft|soft|medium|loud|x-loud|inherit',
255 'white-space': r'normal|pre|nowrap|pre-wrap|pre-line|inherit',
256 'widows': r'{integer}|inherit',
257 'width': r'{length}|{percentage}|auto|inherit',
258 'word-spacing': r'normal|{length}|inherit',
259 'z-index': r'auto|{integer}|inherit',
260 }
261
263 """ Expand macros in token dictionary """
264 def macro_value(m):
265 return '(?:%s)' % MACROS[m.groupdict()['macro']]
266 for key, value in tokdict.items():
267 while re.search(r'{[a-z][a-z0-9-]*}', value):
268 value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
269 macro_value, value)
270 tokdict[key] = value
271 return tokdict
272
274 """ Compile all regular expressions into callable objects """
275 for key, value in tokdict.items():
276 tokdict[key] = re.compile('^(?:%s)$' % value, re.I).match
277 return tokdict
278
279 _compile_regexes(_expand_macros(cssvalues))
280
281
282
283
284 _reCSStoDOMname = re.compile('-[a-z]', re.I)
286 """
287 returns DOMname for given CSSname e.g. for CSSname 'font-style' returns
288 'fontStyle'
289 """
290 def _doCSStoDOMname2(m): return m.group(0)[1].capitalize()
291 return _reCSStoDOMname.sub(_doCSStoDOMname2, CSSname)
292
293 _reDOMtoCSSname = re.compile('([A-Z])[a-z]+')
295 """
296 returns CSSname for given DOMname e.g. for DOMname 'fontStyle' returns
297 'font-style'
298 """
299 def _doDOMtoCSSname2(m): return '-' + m.group(0).lower()
300 return _reDOMtoCSSname.sub(_doDOMtoCSSname2, DOMname)
301
302
304 """
305 The CSS2Properties interface represents a convenience mechanism
306 for retrieving and setting properties within a CSSStyleDeclaration.
307 The attributes of this interface correspond to all the properties
308 specified in CSS2. Getting an attribute of this interface is
309 equivalent to calling the getPropertyValue method of the
310 CSSStyleDeclaration interface. Setting an attribute of this
311 interface is equivalent to calling the setProperty method of the
312 CSSStyleDeclaration interface.
313
314 cssutils actually also allows usage of ``del`` to remove a CSS property
315 from a CSSStyleDeclaration.
316
317 This is an abstract class, the following functions need to be present
318 in inheriting class:
319
320 - ``_getP``
321 - ``_setP``
322 - ``_delP``
323 """
324
325 - def _getP(self, CSSname): pass
326 - def _setP(self, CSSname, value): pass
327 - def _delP(self, CSSname): pass
328
329
330
331
332 CSS2Properties._properties = [_toDOMname(p) for p in cssvalues.keys()]
333
334
336 """
337 closure to keep name known in each properties accessor function
338 DOMname is converted to CSSname here, so actual calls use CSSname
339 """
340 CSSname = _toCSSname(DOMname)
341 def _get(self): return self._getP(CSSname)
342 def _set(self, value): self._setP(CSSname, value)
343 def _del(self): self._delP(CSSname)
344 return _get, _set, _del
345
346
347 for DOMname in CSS2Properties._properties:
348 setattr(CSS2Properties, DOMname,
349 property(*__named_property_def(DOMname)))
350