"""CSSImportRule implements DOM Level 2 CSS CSSImportRule.

plus: 

``name`` property
    http://www.w3.org/TR/css3-cascade/#cascading

"""
__all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssimportrule.py 1262 2008-05-28 11:29:18Z cthedot $'

import os
import urlparse
import xml.dom
import cssrule
import cssutils

class CSSImportRule(cssrule.CSSRule):
    """
    Represents an @import rule within a CSS style sheet.  The @import rule
    is used to import style rules from other style sheets.

    Properties
    ==========
    atkeyword: (cssutils only)
        the literal keyword used
    cssText: of type DOMString
        The parsable textual representation of this rule
    href: of type DOMString, (DOM readonly, cssutils also writable)
        The location of the style sheet to be imported. The attribute will
        not contain the url(...) specifier around the URI.
    hreftype: 'uri' (serializer default) or 'string' (cssutils only)
        The original type of href, not really relevant as it may be
        reconfigured in the serializer but it is kept anyway
    media: of type stylesheets::MediaList (DOM readonly)
        A list of media types for this rule of type MediaList.
    name: 
        An optional name used for cascading
    styleSheet: of type CSSStyleSheet (DOM readonly)
        The style sheet referred to by this rule. The value of this
        attribute is None if the style sheet has not yet been loaded or if
        it will not be loaded (e.g. if the stylesheet is for a media type
        not supported by the user agent).

    Inherits properties from CSSRule

    Format
    ======
    import
      : IMPORT_SYM S*
      [STRING|URI] S* [ medium [ COMMA S* medium]* ]? S* STRING? S* ';' S*
      ;
    """
    type = property(lambda self: cssrule.CSSRule.IMPORT_RULE)

    def __init__(self, href=None, mediaText=u'all', name=None, 
                 parentRule=None, parentStyleSheet=None, readonly=False):
        """
        if readonly allows setting of properties in constructor only

        Do not use as positional but as keyword attributes only!

        href
            location of the style sheet to be imported.
        mediaText
            A list of media types for which this style sheet may be used
            as a string
        """
        super(CSSImportRule, self).__init__(parentRule=parentRule, 
                                            parentStyleSheet=parentStyleSheet)
        self._atkeyword = u'@import'
        self.hreftype = None
        self._styleSheet = None

        self._href = None
        self.href = href
        
        self._media = cssutils.stylesheets.MediaList(
            mediaText, readonly=readonly)
        self._name = name
        if not self.media.wellformed:
            self._media = cssutils.stylesheets.MediaList()
            
        seq = self._tempSeq()
        seq.append(self.href, 'href')
        seq.append(self.media, 'media')
        seq.append(self.name, 'name')
        self._setSeq(seq)

        self._readonly = readonly

    def _getCssText(self):
        """
        returns serialized property cssText
        """
        return cssutils.ser.do_CSSImportRule(self)

    def _setCssText(self, cssText):
        """
        DOMException on setting

        - HIERARCHY_REQUEST_ERR: (CSSStylesheet)
          Raised if the rule cannot be inserted at this point in the
          style sheet.
        - INVALID_MODIFICATION_ERR: (self)
          Raised if the specified CSS string value represents a different
          type of rule than the current one.
        - NO_MODIFICATION_ALLOWED_ERR: (CSSRule)
          Raised if the rule is readonly.
        - SYNTAX_ERR: (self)
          Raised if the specified CSS string value has a syntax error and
          is unparsable.
        """
        super(CSSImportRule, self)._setCssText(cssText)
        tokenizer = self._tokenize2(cssText)
        attoken = self._nexttoken(tokenizer, None)
        if self._type(attoken) != self._prods.IMPORT_SYM:
            self._log.error(u'CSSImportRule: No CSSImportRule found: %s' %
                self._valuestr(cssText),
                error=xml.dom.InvalidModificationErr)
        else:
            # for closures: must be a mutable
            new = {'keyword': self._tokenvalue(attoken),
                   'href': None,
                   'hreftype': None,
                   'media': cssutils.stylesheets.MediaList(),
                   'name': None,
                   'wellformed': True
                   }

            def __doname(seq, token):
                # called by _string or _ident
                new['name'] = self._stringtokenvalue(token)
                seq.append(new['name'], 'name')
                return ';'

            def _string(expected, seq, token, tokenizer=None):
                if 'href' == expected:
                    # href
                    new['href'] = self._stringtokenvalue(token)
                    new['hreftype'] = 'string'
                    seq.append(new['href'], 'href')
                    return 'media name ;'
                elif 'name' in expected:
                    # name
                    return __doname(seq, token)
                else:
                    new['wellformed'] = False
                    self._log.error(
                        u'CSSImportRule: Unexpected string.', token)
                    return expected

            def _uri(expected, seq, token, tokenizer=None):
                # href
                if 'href' == expected:
                    uri = self._uritokenvalue(token)
                    new['hreftype'] = 'uri'
                    new['href'] = uri
                    seq.append(new['href'], 'href')
                    return 'media name ;'
                else:
                    new['wellformed'] = False
                    self._log.error(
                        u'CSSImportRule: Unexpected URI.', token)
                    return expected

            def _ident(expected, seq, token, tokenizer=None):
                # medialist ending with ; which is checked upon too
                if expected.startswith('media'):
                    mediatokens = self._tokensupto2(
                        tokenizer, importmediaqueryendonly=True)
                    mediatokens.insert(0, token) # push found token

                    last = mediatokens.pop() # retrieve ;
                    lastval, lasttyp = self._tokenvalue(last), self._type(last)
                    if lastval != u';' and lasttyp not in ('EOF', self._prods.STRING):
                        new['wellformed'] = False
                        self._log.error(u'CSSImportRule: No ";" found: %s' %
                                        self._valuestr(cssText), token=token)

                    media = cssutils.stylesheets.MediaList()
                    media.mediaText = mediatokens
                    if media.wellformed:
                        new['media'] = media
                        seq.append(media, 'media')
                    else:
                        new['wellformed'] = False
                        self._log.error(u'CSSImportRule: Invalid MediaList: %s' %
                                        self._valuestr(cssText), token=token)
                        
                    if lasttyp == self._prods.STRING:
                        # name
                        return __doname(seq, last)
                    else:
                        return 'EOF' # ';' is token "last"
                else:
                    new['wellformed'] = False
                    self._log.error(
                        u'CSSImportRule: Unexpected ident.', token)
                    return expected

            def _char(expected, seq, token, tokenizer=None):
                # final ;
                val = self._tokenvalue(token)
                if expected.endswith(';') and u';' == val:
                    return 'EOF'
                else:
                    new['wellformed'] = False
                    self._log.error(
                        u'CSSImportRule: Unexpected char.', token)
                    return expected

            # import : IMPORT_SYM S* [STRING|URI]
            #            S* [ medium [ ',' S* medium]* ]? ';' S* 
            #         STRING? # see http://www.w3.org/TR/css3-cascade/#cascading
            #        ;
            newseq = self._tempSeq()
            wellformed, expected = self._parse(expected='href',
                seq=newseq, tokenizer=tokenizer,
                productions={'STRING': _string,
                             'URI': _uri,
                             'IDENT': _ident,
                             'CHAR': _char},
                new=new)

            # wellformed set by parse
            wellformed = wellformed and new['wellformed']

            # post conditions
            if not new['href']:
                wellformed = False
                self._log.error(u'CSSImportRule: No href found: %s' %
                    self._valuestr(cssText))

            if expected != 'EOF':
                wellformed = False
                self._log.error(u'CSSImportRule: No ";" found: %s' %
                    self._valuestr(cssText))
            
            # set all
            if wellformed:
                self.atkeyword = new['keyword']
                self.hreftype = new['hreftype']
                self._media = new['media']
                self.name = new['name']
                self._setSeq(newseq)

                self.href = new['href']
                
                if self.styleSheet:
                    # title is set by href
                    #self.styleSheet._href = self.href
                    self.styleSheet._parentStyleSheet = self.parentStyleSheet

    cssText = property(fget=_getCssText, fset=_setCssText,
        doc="(DOM attribute) The parsable textual representation.")

    def _setHref(self, href):
        # update seq
        for i, item in enumerate(self.seq):
            val, typ = item.value, item.type
            if 'href' == typ:
                self._seq[i] = (href, typ, item.line, item.col)
                break
        else:
            seq = self._tempSeq()
            seq.append(self.href, 'href')
            self._setSeq(seq)
        # set new href
        self._href = href
        
        if not self.styleSheet:
            # set only if not set before
            self.__setStyleSheet()
            
    href = property(lambda self: self._href, _setHref,
                    doc="Location of the style sheet to be imported.")

    media = property(lambda self: self._media,
                     doc=u"(DOM readonly) A list of media types for this rule"
                     " of type MediaList")

    def _setName(self, name):
        """raises xml.dom.SyntaxErr if name is not a string"""
        if isinstance(name, basestring) or name is None:
            # "" or ''
            if not name:
                name = None
            # update seq
            for i, item in enumerate(self.seq):
                val, typ = item.value, item.type
                if 'name' == typ:
                    self._seq[i] = (name, typ, item.line, item.col)
                    break
            else:
                # append
                seq = self._tempSeq()
                for item in self.seq:
                    # copy current seq
                    seq.append(item.value, item.type, item.line, item.col)
                seq.append(name, 'name')
                self._setSeq(seq)
            self._name = name
            # set title of referred sheet
            if self.styleSheet:
                self.styleSheet.title = name
        else:
            self._log.error(u'CSSImportRule: Not a valid name: %s' % name)

    name = property(lambda self: self._name, _setName,
                    doc=u"An optional name for the imported sheet")

    def __setStyleSheet(self):
        """Read new CSSStyleSheet cssText from href using parentStyleSheet.href
        
        indirectly called if setting ``href``
        """
        # should simply fail so all errors are catched!
        if self.parentStyleSheet and self.href:
            # relative href
            href = urlparse.urljoin(self.parentStyleSheet.href, self.href)
            # all possible exceptions are ignored and styleSheet is None
            try:
                overrideEncoding, cssText = self.parentStyleSheet._resolveImport(href)
                if cssText is None:
                    raise IOError()

                refsheet = cssutils.css.CSSStyleSheet(href=href, 
                                                      media=self.media,
                                                      ownerRule=self,
                                                      title=self.name)
                # inherit fetcher for @imports in refsheet
                refsheet._setFetcher(self.parentStyleSheet._fetcher)
                # overrideEncoding with parentStyleSheet.overrideEncoding, 
                # HTTP or parent
                refsheet._setCssTextWithEncodingOverride(cssText, 
                                                         overrideEncoding)
                 
            except (IOError, ValueError), e:
                self._log.warn(u'CSSImportRule: Error processing imported style sheet href=%r: %r'
                               % (self.href, e), neverraise=True)
            else:
                self._styleSheet = refsheet
                
    styleSheet = property(lambda self: self._styleSheet,
                          doc="(readonly) The style sheet referred to by this rule.")

    wellformed = property(lambda self: bool(self.href and self.media.wellformed))

    def __repr__(self):
        if self.media:
            mediaText = self.media.mediaText
        else:
            mediaText = None
        return "cssutils.css.%s(href=%r, mediaText=%r, name=%r)" % (
                self.__class__.__name__, 
                self.href, self.media.mediaText, self.name)

    def __str__(self):
        if self.media:
            mediaText = self.media.mediaText
        else:
            mediaText = None
        return "<cssutils.css.%s object href=%r mediaText=%r name=%r at 0x%x>" % (
                self.__class__.__name__, self.href, mediaText, self.name, id(self))
