1"""CSSNamespaceRule currently implements http://dev.w3.org/csswg/css3-namespace/
2"""
3__all__ = ['CSSNamespaceRule']
4
5from . import cssrule
6import cssutils
7import xml.dom
8
9
10class CSSNamespaceRule(cssrule.CSSRule):
11    """
12    Represents an @namespace rule within a CSS style sheet.
13
14    The @namespace at-rule declares a namespace prefix and associates
15    it with a given namespace (a string). This namespace prefix can then be
16    used in namespace-qualified names such as those described in the
17    Selectors Module [SELECT] or the Values and Units module [CSS3VAL].
18
19    Dealing with these rules directly is not needed anymore, easier is
20    the use of :attr:`cssutils.css.CSSStyleSheet.namespaces`.
21
22    Format::
23
24        namespace
25          : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
26          ;
27        namespace_prefix
28          : IDENT
29          ;
30    """
31
32    def __init__(
33        self,
34        namespaceURI=None,
35        prefix=None,
36        cssText=None,
37        parentRule=None,
38        parentStyleSheet=None,
39        readonly=False,
40    ):
41        """
42        :Parameters:
43            namespaceURI
44                The namespace URI (a simple string!) which is bound to the
45                given prefix. If no prefix is set
46                (``CSSNamespaceRule.prefix==''``) the namespace defined by
47                namespaceURI is set as the default namespace
48            prefix
49                The prefix used in the stylesheet for the given
50                ``CSSNamespaceRule.uri``.
51            cssText
52                if no namespaceURI is given cssText must be given to set
53                a namespaceURI as this is readonly later on
54            parentStyleSheet
55                sheet where this rule belongs to
56
57        Do not use as positional but as keyword parameters only!
58
59        If readonly allows setting of properties in constructor only
60
61        format namespace::
62
63            namespace
64              : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
65              ;
66            namespace_prefix
67              : IDENT
68              ;
69        """
70        super(CSSNamespaceRule, self).__init__(
71            parentRule=parentRule, parentStyleSheet=parentStyleSheet
72        )
73        self._atkeyword = '@namespace'
74        self._prefix = ''
75        self._namespaceURI = None
76
77        if namespaceURI:
78            self.namespaceURI = namespaceURI
79            self.prefix = prefix
80            tempseq = self._tempSeq()
81            tempseq.append(self.prefix, 'prefix')
82            tempseq.append(self.namespaceURI, 'namespaceURI')
83            self._setSeq(tempseq)
84
85        elif cssText is not None:
86            self.cssText = cssText
87
88        if parentStyleSheet:
89            self._parentStyleSheet = parentStyleSheet
90
91        self._readonly = readonly
92
93    def __repr__(self):
94        return "cssutils.css.%s(namespaceURI=%r, prefix=%r)" % (
95            self.__class__.__name__,
96            self.namespaceURI,
97            self.prefix,
98        )
99
100    def __str__(self):
101        return "<cssutils.css.%s object namespaceURI=%r prefix=%r at 0x%x>" % (
102            self.__class__.__name__,
103            self.namespaceURI,
104            self.prefix,
105            id(self),
106        )
107
108    def _getCssText(self):
109        """Return serialized property cssText"""
110        return cssutils.ser.do_CSSNamespaceRule(self)
111
112    def _setCssText(self, cssText):  # noqa: C901
113        """
114        :param cssText: initial value for this rules cssText which is parsed
115        :exceptions:
116            - :exc:`~xml.dom.HierarchyRequestErr`:
117              Raised if the rule cannot be inserted at this point in the
118              style sheet.
119            - :exc:`~xml.dom.InvalidModificationErr`:
120              Raised if the specified CSS string value represents a different
121              type of rule than the current one.
122            - :exc:`~xml.dom.NoModificationAllowedErr`:
123              Raised if the rule is readonly.
124            - :exc:`~xml.dom.SyntaxErr`:
125              Raised if the specified CSS string value has a syntax error and
126              is unparsable.
127        """
128        super(CSSNamespaceRule, self)._setCssText(cssText)
129        tokenizer = self._tokenize2(cssText)
130        attoken = self._nexttoken(tokenizer, None)
131        if self._type(attoken) != self._prods.NAMESPACE_SYM:
132            self._log.error(
133                'CSSNamespaceRule: No CSSNamespaceRule found: %s'
134                % self._valuestr(cssText),
135                error=xml.dom.InvalidModificationErr,
136            )
137        else:
138            # for closures: must be a mutable
139            new = {
140                'keyword': self._tokenvalue(attoken),
141                'prefix': '',
142                'uri': None,
143                'wellformed': True,
144            }
145
146            def _ident(expected, seq, token, tokenizer=None):
147                # the namespace prefix, optional
148                if 'prefix or uri' == expected:
149                    new['prefix'] = self._tokenvalue(token)
150                    seq.append(new['prefix'], 'prefix')
151                    return 'uri'
152                else:
153                    new['wellformed'] = False
154                    self._log.error('CSSNamespaceRule: Unexpected ident.', token)
155                    return expected
156
157            def _string(expected, seq, token, tokenizer=None):
158                # the namespace URI as a STRING
159                if expected.endswith('uri'):
160                    new['uri'] = self._stringtokenvalue(token)
161                    seq.append(new['uri'], 'namespaceURI')
162                    return ';'
163
164                else:
165                    new['wellformed'] = False
166                    self._log.error('CSSNamespaceRule: Unexpected string.', token)
167                    return expected
168
169            def _uri(expected, seq, token, tokenizer=None):
170                # the namespace URI as URI which is DEPRECATED
171                if expected.endswith('uri'):
172                    uri = self._uritokenvalue(token)
173                    new['uri'] = uri
174                    seq.append(new['uri'], 'namespaceURI')
175                    return ';'
176                else:
177                    new['wellformed'] = False
178                    self._log.error('CSSNamespaceRule: Unexpected URI.', token)
179                    return expected
180
181            def _char(expected, seq, token, tokenizer=None):
182                # final ;
183                val = self._tokenvalue(token)
184                if ';' == expected and ';' == val:
185                    return 'EOF'
186                else:
187                    new['wellformed'] = False
188                    self._log.error('CSSNamespaceRule: Unexpected char.', token)
189                    return expected
190
191            # "NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*"
192            newseq = self._tempSeq()
193            wellformed, expected = self._parse(
194                expected='prefix or uri',
195                seq=newseq,
196                tokenizer=tokenizer,
197                productions={
198                    'IDENT': _ident,
199                    'STRING': _string,
200                    'URI': _uri,
201                    'CHAR': _char,
202                },
203                new=new,
204            )
205
206            # wellformed set by parse
207            wellformed = wellformed and new['wellformed']
208
209            # post conditions
210            if new['uri'] is None:
211                wellformed = False
212                self._log.error(
213                    'CSSNamespaceRule: No namespace URI found: %s'
214                    % self._valuestr(cssText)
215                )
216
217            if expected != 'EOF':
218                wellformed = False
219                self._log.error(
220                    'CSSNamespaceRule: No ";" found: %s' % self._valuestr(cssText)
221                )
222
223            # set all
224            if wellformed:
225                self.atkeyword = new['keyword']
226                self._prefix = new['prefix']
227                self.namespaceURI = new['uri']
228                self._setSeq(newseq)
229
230    cssText = property(
231        fget=_getCssText,
232        fset=_setCssText,
233        doc="(DOM) The parsable textual representation of this " "rule.",
234    )
235
236    def _setNamespaceURI(self, namespaceURI):
237        """
238        :param namespaceURI: the initial value for this rules namespaceURI
239        :exceptions:
240            - :exc:`~xml.dom.NoModificationAllowedErr`:
241              (CSSRule) Raised if this rule is readonly or a namespaceURI is
242              already set in this rule.
243        """
244        self._checkReadonly()
245        if not self._namespaceURI:
246            # initial setting
247            self._namespaceURI = namespaceURI
248            tempseq = self._tempSeq()
249            tempseq.append(namespaceURI, 'namespaceURI')
250            self._setSeq(tempseq)  # makes seq readonly!
251        elif self._namespaceURI != namespaceURI:
252            self._log.error(
253                'CSSNamespaceRule: namespaceURI is readonly.',
254                error=xml.dom.NoModificationAllowedErr,
255            )
256
257    namespaceURI = property(
258        lambda self: self._namespaceURI,
259        _setNamespaceURI,
260        doc="URI (handled as simple string) of the defined namespace.",
261    )
262
263    def _replaceNamespaceURI(self, namespaceURI):
264        """Used during parse of new sheet only!
265
266        :param namespaceURI: the new value for this rules namespaceURI
267        """
268        self._namespaceURI = namespaceURI
269        for i, x in enumerate(self._seq):
270            if 'namespaceURI' == x.type:
271                self._seq._readonly = False
272                self._seq.replace(i, namespaceURI, 'namespaceURI')
273                self._seq._readonly = True
274                break
275
276    def _setPrefix(self, prefix=None):
277        """
278        :param prefix: the new prefix
279        :exceptions:
280            - :exc:`~xml.dom.SyntaxErr`:
281              Raised if the specified CSS string value has a syntax error and
282              is unparsable.
283            - :exc:`~xml.dom.NoModificationAllowedErr`:
284              Raised if this rule is readonly.
285        """
286        self._checkReadonly()
287        if not prefix:
288            prefix = ''
289        else:
290            tokenizer = self._tokenize2(prefix)
291            prefixtoken = self._nexttoken(tokenizer, None)
292            if not prefixtoken or self._type(prefixtoken) != self._prods.IDENT:
293                self._log.error(
294                    'CSSNamespaceRule: No valid prefix "%s".' % self._valuestr(prefix),
295                    error=xml.dom.SyntaxErr,
296                )
297                return
298            else:
299                prefix = self._tokenvalue(prefixtoken)
300        # update seq
301        for i, x in enumerate(self._seq):
302            if x == self._prefix:
303                self._seq[i] = (prefix, 'prefix', None, None)
304                break
305        else:
306            # put prefix at the beginning!
307            self._seq[0] = (prefix, 'prefix', None, None)
308
309        # set new prefix
310        self._prefix = prefix
311
312    prefix = property(
313        lambda self: self._prefix,
314        _setPrefix,
315        doc="Prefix used for the defined namespace.",
316    )
317
318    type = property(
319        lambda self: self.NAMESPACE_RULE,
320        doc="The type of this rule, as defined by a CSSRule " "type constant.",
321    )
322
323    wellformed = property(lambda self: self.namespaceURI is not None)
324