1"""CSSVariables implements (and only partly) experimental
2`CSS Variables <http://disruptive-innovations.com/zoo/cssvariables/>`_
3"""
4__all__ = ['CSSVariablesRule']
5
6from .cssvariablesdeclaration import CSSVariablesDeclaration
7from . import cssrule
8import cssutils
9import xml.dom
10
11
12class CSSVariablesRule(cssrule.CSSRule):
13    """
14    The CSSVariablesRule interface represents a @variables rule within a CSS
15    style sheet. The @variables rule is used to specify variables.
16
17    cssutils uses a :class:`~cssutils.css.CSSVariablesDeclaration`  to
18    represent the variables.
19
20    Format::
21
22        variables
23            VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S*
24            variableset* '}' S*
25            ;
26
27    for variableset see :class:`cssutils.css.CSSVariablesDeclaration`
28
29    **Media are not implemented. Reason is that cssutils is using CSS
30    variables in a kind of preprocessing and therefor no media information
31    is available at this stage. For now do not use media!**
32
33    Example::
34
35        @variables {
36          CorporateLogoBGColor: #fe8d12;
37        }
38
39        div.logoContainer {
40          background-color: var(CorporateLogoBGColor);
41        }
42    """
43
44    def __init__(
45        self,
46        mediaText=None,
47        variables=None,
48        parentRule=None,
49        parentStyleSheet=None,
50        readonly=False,
51    ):
52        """
53        If readonly allows setting of properties in constructor only.
54        """
55        super(CSSVariablesRule, self).__init__(
56            parentRule=parentRule, parentStyleSheet=parentStyleSheet
57        )
58        self._atkeyword = '@variables'
59
60        # dummy
61        self._media = cssutils.stylesheets.MediaList(mediaText, readonly=readonly)
62
63        if variables:
64            self.variables = variables
65        else:
66            self.variables = CSSVariablesDeclaration(parentRule=self)
67
68        self._readonly = readonly
69
70    def __repr__(self):
71        return "cssutils.css.%s(mediaText=%r, variables=%r)" % (
72            self.__class__.__name__,
73            self._media.mediaText,
74            self.variables.cssText,
75        )
76
77    def __str__(self):
78        return (
79            "<cssutils.css.%s object mediaText=%r variables=%r valid=%r "
80            "at 0x%x>"
81            % (
82                self.__class__.__name__,
83                self._media.mediaText,
84                self.variables.cssText,
85                self.valid,
86                id(self),
87            )
88        )
89
90    def _getCssText(self):
91        """Return serialized property cssText."""
92        return cssutils.ser.do_CSSVariablesRule(self)
93
94    def _setCssText(self, cssText):
95        """
96        :exceptions:
97            - :exc:`~xml.dom.SyntaxErr`:
98              Raised if the specified CSS string value has a syntax error and
99              is unparsable.
100            - :exc:`~xml.dom.InvalidModificationErr`:
101              Raised if the specified CSS string value represents a different
102              type of rule than the current one.
103            - :exc:`~xml.dom.HierarchyRequestErr`:
104              Raised if the rule cannot be inserted at this point in the
105              style sheet.
106            - :exc:`~xml.dom.NoModificationAllowedErr`:
107              Raised if the rule is readonly.
108
109        Format::
110
111            variables
112            : VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S*
113              variableset* '}' S*
114            ;
115
116            variableset
117            : LBRACE S* vardeclaration [ ';' S* vardeclaration ]* '}' S*
118            ;
119        """
120        super(CSSVariablesRule, self)._setCssText(cssText)
121
122        tokenizer = self._tokenize2(cssText)
123        attoken = self._nexttoken(tokenizer, None)
124        if self._type(attoken) != self._prods.VARIABLES_SYM:
125            self._log.error(
126                'CSSVariablesRule: No CSSVariablesRule found: %s'
127                % self._valuestr(cssText),
128                error=xml.dom.InvalidModificationErr,
129            )
130        else:
131            newVariables = CSSVariablesDeclaration(parentRule=self)
132            ok = True
133
134            beforetokens, brace = self._tokensupto2(
135                tokenizer, blockstartonly=True, separateEnd=True
136            )
137            if self._tokenvalue(brace) != '{':
138                ok = False
139                self._log.error(
140                    'CSSVariablesRule: No start { of variable '
141                    'declaration found: %r' % self._valuestr(cssText),
142                    brace,
143                )
144
145            # parse stuff before { which should be comments and S only
146            new = {'wellformed': True}
147            newseq = self._tempSeq()  # []
148
149            beforewellformed, expected = self._parse(
150                expected=':',
151                seq=newseq,
152                tokenizer=self._tokenize2(beforetokens),
153                productions={},
154            )
155            ok = ok and beforewellformed and new['wellformed']
156
157            variablestokens, braceorEOFtoken = self._tokensupto2(
158                tokenizer, blockendonly=True, separateEnd=True
159            )
160
161            val, type_ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
162            if val != '}' and type_ != 'EOF':
163                ok = False
164                self._log.error(
165                    'CSSVariablesRule: No "}" after variables '
166                    'declaration found: %r' % self._valuestr(cssText)
167                )
168
169            nonetoken = self._nexttoken(tokenizer)
170            if nonetoken:
171                ok = False
172                self._log.error(
173                    'CSSVariablesRule: Trailing content found.', token=nonetoken
174                )
175
176            if 'EOF' == type_:
177                # add again as variables needs it
178                variablestokens.append(braceorEOFtoken)
179            # SET but may raise:
180            newVariables.cssText = variablestokens
181
182            if ok:
183                # contains probably comments only upto {
184                self._setSeq(newseq)
185                self.variables = newVariables
186
187    cssText = property(
188        _getCssText,
189        _setCssText,
190        doc="(DOM) The parsable textual representation of this " "rule.",
191    )
192
193    media = property(
194        doc="NOT IMPLEMENTED! As cssutils resolves variables "
195        "during serializing media information is lost."
196    )
197
198    def _setVariables(self, variables):
199        """
200        :param variables:
201            a CSSVariablesDeclaration or string
202        """
203        self._checkReadonly()
204        if isinstance(variables, str):
205            self._variables = CSSVariablesDeclaration(
206                cssText=variables, parentRule=self
207            )
208        else:
209            variables._parentRule = self
210            self._variables = variables
211
212    variables = property(
213        lambda self: self._variables,
214        _setVariables,
215        doc="(DOM) The variables of this rule set, a "
216        ":class:`cssutils.css.CSSVariablesDeclaration`.",
217    )
218
219    type = property(
220        lambda self: self.VARIABLES_RULE,
221        doc="The type of this rule, as defined by a CSSRule " "type constant.",
222    )
223
224    valid = property(lambda self: True, doc='NOT IMPLEMTED REALLY (TODO)')
225
226    # constant but needed:
227    wellformed = property(lambda self: True)
228