"Fossies" - the Fresh Open Source Software Archive

Member "xhtml2pdf-0.2.5/xhtml2pdf/w3c/css.py" (25 Sep 2020, 28472 Bytes) of package /linux/www/xhtml2pdf-0.2.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "css.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 0.2.2_vs_0.2.4.

    1 #!/usr/bin/env python
    2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    3 # #~ Copyright (C) 2002-2004  TechGame Networks, LLC.
    4 # #~
    5 # #~ This library is free software; you can redistribute it and/or
    6 # #~ modify it under the terms of the BSD style License as found in the
    7 # #~ LICENSE file included with this distribution.
    8 # #
    9 # #  Modified by Dirk Holtwick <holtwick@web.de>, 2007-2008
   10 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   11 from __future__ import absolute_import
   12 
   13 """CSS-2.1 engine
   14 
   15 Primary classes:
   16     * CSSElementInterfaceAbstract
   17         Provide a concrete implementation for the XML element model used.
   18 
   19     * CSSCascadeStrategy
   20         Implements the CSS-2.1 engine's attribute lookup rules.
   21 
   22     * CSSParser
   23         Parses CSS source forms into usable results using CSSBuilder and
   24         CSSMutableSelector.  You may want to override parseExternal()
   25 
   26     * CSSBuilder (and CSSMutableSelector)
   27         A concrete implementation for cssParser.CSSBuilderAbstract (and
   28         cssParser.CSSSelectorAbstract) to provide usable results to
   29         CSSParser requests.
   30 
   31 Dependencies:
   32     python 2.3 (or greater)
   33     sets, cssParser, re (via cssParser)
   34 """
   35 
   36 import os
   37 import sys
   38 import copy
   39 import six
   40 
   41 
   42 from . import cssParser
   43 from . import cssSpecial
   44 
   45 
   46 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   47 # ~ To replace any for with list comprehension
   48 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   49 
   50 def stopIter(value):
   51     raise StopIteration(*value)
   52 
   53 
   54 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   55 # ~ Constants / Variables / Etc.
   56 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   57 
   58 CSSParseError = cssParser.CSSParseError
   59 
   60 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   61 # ~ Definitions
   62 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   63 
   64 class CSSElementInterfaceAbstract(object):
   65     def getAttr(self, name, default=NotImplemented):
   66         raise NotImplementedError('Subclass responsibility')
   67 
   68 
   69     def getIdAttr(self):
   70         return self.getAttr('id', '')
   71 
   72 
   73     def getClassAttr(self):
   74         return self.getAttr('class', '')
   75 
   76 
   77     def getInlineStyle(self):
   78         raise NotImplementedError('Subclass responsibility')
   79 
   80 
   81     def matchesNode(self):
   82         raise NotImplementedError('Subclass responsibility')
   83 
   84 
   85     def inPseudoState(self, name, params=()):
   86         raise NotImplementedError('Subclass responsibility')
   87 
   88 
   89     def iterXMLParents(self):
   90         """Results must be compatible with CSSElementInterfaceAbstract"""
   91         raise NotImplementedError('Subclass responsibility')
   92 
   93 
   94     def getPreviousSibling(self):
   95         raise NotImplementedError('Subclass responsibility')
   96 
   97 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   98 
   99 class CSSCascadeStrategy(object):
  100     author = None
  101     user = None
  102     userAgenr = None
  103 
  104 
  105     def __init__(self, author=None, user=None, userAgent=None):
  106         if author is not None:
  107             self.author = author
  108         if user is not None:
  109             self.user = user
  110         if userAgent is not None:
  111             self.userAgenr = userAgent
  112 
  113     def copyWithUpdate(self, author=None, user=None, userAgent=None):
  114         if author is None:
  115             author = self.author
  116         if user is None:
  117             user = self.user
  118         if userAgent is None:
  119             userAgent = self.userAgenr
  120         return self.__class__(author, user, userAgent)
  121 
  122 
  123     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  124 
  125     def iterCSSRulesets(self, inline=None):
  126         if self.userAgenr is not None:
  127             yield self.userAgenr[0]
  128             yield self.userAgenr[1]
  129 
  130         if self.user is not None:
  131             yield self.user[0]
  132 
  133         if self.author is not None:
  134             yield self.author[0]
  135             yield self.author[1]
  136 
  137         if inline:
  138             yield inline[0]
  139             yield inline[1]
  140 
  141         if self.user is not None:
  142             yield self.user[1]
  143 
  144 
  145     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  146 
  147     def findStyleFor(self, element, attrName, default=NotImplemented):
  148         """Attempts to find the style setting for attrName in the CSSRulesets.
  149 
  150         Note: This method does not attempt to resolve rules that return
  151         "inherited", "default", or values that have units (including "%").
  152         This is left up to the client app to re-query the CSS in order to
  153         implement these semantics.
  154         """
  155         rule = self.findCSSRulesFor(element, attrName)
  156         return self._extractStyleForRule(rule, attrName, default)
  157 
  158 
  159     def findStylesForEach(self, element, attrNames, default=NotImplemented):
  160         """Attempts to find the style setting for attrName in the CSSRulesets.
  161 
  162         Note: This method does not attempt to resolve rules that return
  163         "inherited", "default", or values that have units (including "%").
  164         This is left up to the client app to re-query the CSS in order to
  165         implement these semantics.
  166         """
  167         rules = self.findCSSRulesForEach(element, attrNames)
  168         return [(attrName, self._extractStyleForRule(rule, attrName, default))
  169                 for attrName, rule in six.iteritems(rules)]
  170 
  171 
  172     def findCSSRulesFor(self, element, attrName):
  173         rules = []
  174 
  175         inline = element.getInlineStyle()
  176 
  177         # Generator are wonderfull but sometime slow...
  178         # for ruleset in self.iterCSSRulesets(inline):
  179         #    rules += ruleset.findCSSRuleFor(element, attrName)
  180 
  181         if self.userAgenr is not None:
  182             rules += self.userAgenr[0].findCSSRuleFor(element, attrName)
  183             rules += self.userAgenr[1].findCSSRuleFor(element, attrName)
  184 
  185         if self.user is not None:
  186             rules += self.user[0].findCSSRuleFor(element, attrName)
  187 
  188         if self.author is not None:
  189             rules += self.author[0].findCSSRuleFor(element, attrName)
  190             rules += self.author[1].findCSSRuleFor(element, attrName)
  191 
  192         if inline:
  193             rules += inline[0].findCSSRuleFor(element, attrName)
  194             rules += inline[1].findCSSRuleFor(element, attrName)
  195 
  196         if self.user is not None:
  197             rules += self.user[1].findCSSRuleFor(element, attrName)
  198 
  199         rules.sort()
  200         return rules
  201 
  202 
  203     def findCSSRulesForEach(self, element, attrNames):
  204         rules = dict((name, []) for name in attrNames)
  205 
  206         inline = element.getInlineStyle()
  207         for ruleset in self.iterCSSRulesets(inline):
  208             for attrName, attrRules in six.iteritems(rules):
  209                 attrRules += ruleset.findCSSRuleFor(element, attrName)
  210 
  211         for attrRules in six.itervalues(rules):
  212             attrRules.sort()
  213         return rules
  214 
  215 
  216     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  217 
  218     def _extractStyleForRule(self, rule, attrName, default=NotImplemented):
  219         if rule:
  220             # rule is packed in a list to differentiate from "no rule" vs "rule
  221             # whose value evalutates as False"
  222             style = rule[-1][1]
  223             return style[attrName]
  224         elif default is not NotImplemented:
  225             return default
  226         raise LookupError("Could not find style for '%s' in %r" % (attrName, rule))
  227 
  228 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  229 # ~ CSS Selectors
  230 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  231 
  232 class CSSSelectorBase(object):
  233     inline = False
  234     _hash = None
  235     _specificity = None
  236 
  237 
  238     def __init__(self, completeName='*'):
  239         if not isinstance(completeName, tuple):
  240             completeName = (None, '*', completeName)
  241         self.completeName = completeName
  242 
  243 
  244     def _updateHash(self):
  245         self._hash = hash((self.fullName, self.specificity(), self.qualifiers))
  246 
  247 
  248     def __hash__(self):
  249         if self._hash is None:
  250             return object.__hash__(self)
  251         return self._hash
  252 
  253 
  254     def getNSPrefix(self):
  255         return self.completeName[0]
  256 
  257 
  258     nsPrefix = property(getNSPrefix)
  259 
  260 
  261     def getName(self):
  262         return self.completeName[2]
  263 
  264 
  265     name = property(getName)
  266 
  267 
  268     def getNamespace(self):
  269         return self.completeName[1]
  270 
  271 
  272     namespace = property(getNamespace)
  273 
  274 
  275     def getFullName(self):
  276         return self.completeName[1:3]
  277 
  278 
  279     fullName = property(getFullName)
  280 
  281 
  282     def __repr__(self):
  283         strArgs = (self.__class__.__name__,) + self.specificity() + (self.asString(),)
  284         return '<%s %d:%d:%d:%d %s >' % strArgs
  285 
  286 
  287     def __str__(self):
  288         return self.asString()
  289 
  290 
  291     def _as_comparison_key(self):
  292         return (self.specificity(), self.fullName, self.qualifiers)
  293 
  294 
  295     def __eq__(self, other):
  296         """Python 3"""
  297         return self._as_comparison_key() == other._as_comparison_key()
  298 
  299     def __lt__(self, other):
  300         """Python 3"""
  301         return self._as_comparison_key() < other._as_comparison_key()
  302 
  303     def __cmp__(self, other):
  304         """Python 2"""
  305         return cmp(self._as_comparison_key(), other._as_comparison_key())
  306 
  307 
  308     def specificity(self):
  309         if self._specificity is None:
  310             self._specificity = self._calcSpecificity()
  311         return self._specificity
  312 
  313 
  314     def _calcSpecificity(self):
  315         """from http://www.w3.org/TR/CSS21/cascade.html#specificity"""
  316         hashCount = 0
  317         qualifierCount = 0
  318         elementCount = int(self.name != '*')
  319         for q in self.qualifiers:
  320             if q.isHash():
  321                 hashCount += 1
  322             elif q.isClass():
  323                 qualifierCount += 1
  324             elif q.isAttr():
  325                 qualifierCount += 1
  326             elif q.isPseudo():
  327                 elementCount += 1
  328             elif q.isCombiner():
  329                 i, h, q, e = q.selector.specificity()
  330                 hashCount += h
  331                 qualifierCount += q
  332                 elementCount += e
  333         return self.inline, hashCount, qualifierCount, elementCount
  334 
  335 
  336     def matches(self, element=None):
  337         if element is None:
  338             return False
  339 
  340         # with  CSSDOMElementInterface.matchesNode(self, (namespace, tagName)) replacement:
  341         if self.fullName[1] not in ('*', element.domElement.tagName):
  342             return False
  343         if self.fullName[0] not in (None, '', '*') and self.fullName[0] != element.domElement.namespaceURI:
  344             return False
  345 
  346         for qualifier in self.qualifiers:
  347             if not qualifier.matches(element):
  348                 return False
  349         else:
  350             return True
  351 
  352 
  353     def asString(self):
  354         result = []
  355         if self.nsPrefix is not None:
  356             result.append('%s|%s' % (self.nsPrefix, self.name))
  357         else:
  358             result.append(self.name)
  359 
  360         for q in self.qualifiers:
  361             if q.isCombiner():
  362                 result.insert(0, q.asString())
  363             else:
  364                 result.append(q.asString())
  365         return ''.join(result)
  366 
  367 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  368 
  369 class CSSInlineSelector(CSSSelectorBase):
  370     inline = True
  371 
  372 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  373 
  374 class CSSMutableSelector(CSSSelectorBase, cssParser.CSSSelectorAbstract):
  375     qualifiers = []
  376 
  377 
  378     def asImmutable(self):
  379         return CSSImmutableSelector(self.completeName, [q.asImmutable() for q in self.qualifiers])
  380 
  381 
  382     def combineSelectors(klass, selectorA, op, selectorB):
  383         selectorB.addCombination(op, selectorA)
  384         return selectorB
  385 
  386 
  387     combineSelectors = classmethod(combineSelectors)
  388 
  389 
  390     def addCombination(self, op, other):
  391         self._addQualifier(CSSSelectorCombinationQualifier(op, other))
  392 
  393 
  394     def addHashId(self, hashId):
  395         self._addQualifier(CSSSelectorHashQualifier(hashId))
  396 
  397 
  398     def addClass(self, class_):
  399         self._addQualifier(CSSSelectorClassQualifier(class_))
  400 
  401 
  402     def addAttribute(self, attrName):
  403         self._addQualifier(CSSSelectorAttributeQualifier(attrName))
  404 
  405 
  406     def addAttributeOperation(self, attrName, op, attrValue):
  407         self._addQualifier(CSSSelectorAttributeQualifier(attrName, op, attrValue))
  408 
  409 
  410     def addPseudo(self, name):
  411         self._addQualifier(CSSSelectorPseudoQualifier(name))
  412 
  413 
  414     def addPseudoFunction(self, name, params):
  415         self._addQualifier(CSSSelectorPseudoQualifier(name, params))
  416 
  417 
  418     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  419 
  420     def _addQualifier(self, qualifier):
  421         if self.qualifiers:
  422             self.qualifiers.append(qualifier)
  423         else:
  424             self.qualifiers = [qualifier]
  425 
  426 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  427 
  428 class CSSImmutableSelector(CSSSelectorBase):
  429     def __init__(self, completeName='*', qualifiers=()):
  430         # print completeName, qualifiers
  431         self.qualifiers = tuple(qualifiers)
  432         CSSSelectorBase.__init__(self, completeName)
  433         self._updateHash()
  434 
  435 
  436     def fromSelector(klass, selector):
  437         return klass(selector.completeName, selector.qualifiers)
  438 
  439 
  440     fromSelector = classmethod(fromSelector)
  441 
  442 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  443 
  444 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  445 # ~ CSS Selector Qualifiers -- see CSSImmutableSelector
  446 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  447 
  448 class CSSSelectorQualifierBase(object):
  449     def isHash(self):
  450         return False
  451 
  452 
  453     def isClass(self):
  454         return False
  455 
  456 
  457     def isAttr(self):
  458         return False
  459 
  460 
  461     def isPseudo(self):
  462         return False
  463 
  464 
  465     def isCombiner(self):
  466         return False
  467 
  468 
  469     def asImmutable(self):
  470         return self
  471 
  472 
  473     def __str__(self):
  474         return self.asString()
  475 
  476 
  477 class CSSSelectorHashQualifier(CSSSelectorQualifierBase):
  478     def __init__(self, hashId):
  479         self.hashId = hashId
  480 
  481 
  482     def isHash(self):
  483         return True
  484 
  485 
  486     def __hash__(self):
  487         return hash((self.hashId,))
  488 
  489 
  490     def asString(self):
  491         return '#' + self.hashId
  492 
  493 
  494     def matches(self, element):
  495         return element.getIdAttr() == self.hashId
  496 
  497     def __eq__(self, other):
  498         """Python 3"""
  499         return self.hashId == other.hashId
  500 
  501     def __lt__(self, other):
  502         """Python 3"""
  503         return self.hashId < other.hashId
  504 
  505     def __cmp__(self, other):
  506         """Python 2"""
  507         return cmp(self.hashId, other.hashId)
  508 
  509 
  510 class CSSSelectorClassQualifier(CSSSelectorQualifierBase):
  511     def __init__(self, classId):
  512         self.classId = classId
  513 
  514 
  515     def isClass(self):
  516         return True
  517 
  518 
  519     def __hash__(self):
  520         return hash((self.classId,))
  521 
  522 
  523     def asString(self):
  524         return '.' + self.classId
  525 
  526 
  527     def matches(self, element):
  528         # return self.classId in element.getClassAttr().split()
  529         attrValue = element.domElement.attributes.get('class')
  530         if attrValue is not None:
  531             return self.classId in attrValue.value.split()
  532         return False
  533 
  534     def __eq__(self, other):
  535         """Python 3"""
  536         return self.classId == other.classId
  537 
  538     def __lt__(self, other):
  539         """Python 3"""
  540         return self.classId < other.classId
  541 
  542     def __cmp__(self, other):
  543         """Python 2"""
  544         return cmp(self.classId, other.classId)
  545 
  546 
  547 class CSSSelectorAttributeQualifier(CSSSelectorQualifierBase):
  548     name, op, value = None, None, NotImplemented
  549 
  550     def __init__(self, attrName, op=None, attrValue=NotImplemented):
  551         self.name = attrName
  552         if op is not self.op:
  553             self.op = op
  554         if attrValue is not self.value:
  555             self.value = attrValue
  556 
  557 
  558     def isAttr(self):
  559         return True
  560 
  561     def __hash__(self):
  562         return hash((self.name, self.op, self.value))
  563 
  564     def asString(self):
  565         if self.value is NotImplemented:
  566             return '[%s]' % (self.name,)
  567         return '[%s%s%s]' % (self.name, self.op, self.value)
  568 
  569     def matches(self, element):
  570         if self.op is None:
  571             return element.getAttr(self.name, NotImplemented) != NotImplemented
  572         elif self.op == '=':
  573             return self.value == element.getAttr(self.name, NotImplemented)
  574         elif self.op == '~=':
  575             # return self.value in element.getAttr(self.name, '').split()
  576             attrValue = element.domElement.attributes.get(self.name)
  577             if attrValue is not None:
  578                 return self.value in attrValue.value.split()
  579             return False
  580         elif self.op == '|=':
  581             # return self.value in element.getAttr(self.name, '').split('-')
  582             attrValue = element.domElement.attributes.get(self.name)
  583             if attrValue is not None:
  584                 return self.value in attrValue.value.split('-')
  585             return False
  586         raise RuntimeError("Unknown operator %r for %r" % (self.op, self))
  587 
  588 
  589 class CSSSelectorPseudoQualifier(CSSSelectorQualifierBase):
  590     def __init__(self, attrName, params=()):
  591         self.name = attrName
  592         self.params = tuple(params)
  593 
  594     def isPseudo(self):
  595         return True
  596 
  597     def __hash__(self):
  598         return hash((self.name, self.params))
  599 
  600     def asString(self):
  601         if self.params:
  602             return ':' + self.name
  603         else:
  604             return ':%s(%s)' % (self.name, self.params)
  605 
  606     def matches(self, element):
  607         return element.inPseudoState(self.name, self.params)
  608 
  609 
  610 class CSSSelectorCombinationQualifier(CSSSelectorQualifierBase):
  611     def __init__(self, op, selector):
  612         self.op = op
  613         self.selector = selector
  614 
  615 
  616     def isCombiner(self):
  617         return True
  618 
  619 
  620     def __hash__(self):
  621         return hash((self.op, self.selector))
  622 
  623 
  624     def asImmutable(self):
  625         return self.__class__(self.op, self.selector.asImmutable())
  626 
  627 
  628     def asString(self):
  629         return '%s%s' % (self.selector.asString(), self.op)
  630 
  631     def matches(self, element):
  632         op, selector = self.op, self.selector
  633         if op == ' ':
  634             return any(selector.matches(parent) for parent in element.iterXMLParents())
  635         elif op == '>':
  636             parent = next(element.iterXMLParents(), None)
  637             if parent is None:
  638                 return False
  639             return selector.matches(parent)
  640         elif op == '+':
  641             return selector.matches(element.getPreviousSibling())
  642 
  643 
  644 
  645 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  646 # ~ CSS Misc
  647 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  648 
  649 class CSSTerminalFunction(object):
  650     def __init__(self, name, params):
  651         self.name = name
  652         self.params = params
  653 
  654 
  655     def __repr__(self):
  656         return '<CSS function: %s(%s)>' % (self.name, ', '.join(self.params))
  657 
  658 
  659 class CSSTerminalOperator(tuple):
  660     def __new__(klass, *args):
  661         return tuple.__new__(klass, args)
  662 
  663 
  664     def __repr__(self):
  665         return 'op' + tuple.__repr__(self)
  666 
  667 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  668 # ~ CSS Objects
  669 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  670 
  671 class CSSDeclarations(dict):
  672     pass
  673 
  674     def __eq__(self, other):
  675         """Python 3"""
  676         return False
  677 
  678     def __lt__(self, other):
  679         """Python 3"""
  680         return False
  681 
  682 
  683 class CSSRuleset(dict):
  684     def findCSSRulesFor(self, element, attrName):
  685         ruleResults = [(nodeFilter, declarations) for nodeFilter, declarations in six.iteritems(self) if
  686                        (attrName in declarations) and (nodeFilter.matches(element))]
  687         ruleResults.sort()
  688         return ruleResults
  689 
  690 
  691     def findCSSRuleFor(self, element, attrName):
  692         # rule is packed in a list to differentiate from "no rule" vs "rule
  693         # whose value evalutates as False"
  694         return self.findCSSRulesFor(element, attrName)[-1:]
  695 
  696 
  697     def mergeStyles(self, styles):
  698         " XXX Bugfix for use in PISA "
  699         for k, v in six.iteritems(styles):
  700             if k in self and self[k]:
  701                 self[k] = copy.copy(self[k])
  702                 self[k].update(v)
  703             else:
  704                 self[k] = v
  705 
  706 
  707 class CSSInlineRuleset(CSSRuleset, CSSDeclarations):
  708     def findCSSRulesFor(self, element, attrName):
  709         if attrName in self:
  710             return [(CSSInlineSelector(), self)]
  711         return []
  712 
  713 
  714     def findCSSRuleFor(self, *args, **kw):
  715         # rule is packed in a list to differentiate from "no rule" vs "rule
  716         # whose value evalutates as False"
  717         return self.findCSSRulesFor(*args, **kw)[-1:]
  718 
  719 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  720 # ~ CSS Builder
  721 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  722 
  723 class CSSBuilder(cssParser.CSSBuilderAbstract):
  724     RulesetFactory = CSSRuleset
  725     SelectorFactory = CSSMutableSelector
  726     MediumSetFactory = set
  727     DeclarationsFactory = CSSDeclarations
  728     TermFunctionFactory = CSSTerminalFunction
  729     TermOperatorFactory = CSSTerminalOperator
  730     xmlnsSynonyms = {}
  731     mediumSet = None
  732     trackImportance = True
  733     charset = None
  734 
  735 
  736     def __init__(self, mediumSet=mediumSet, trackImportance=trackImportance):
  737         self.setMediumSet(mediumSet)
  738         self.setTrackImportance(trackImportance)
  739 
  740 
  741     def isValidMedium(self, mediums):
  742         if not mediums:
  743             return False
  744         if 'all' in mediums:
  745             return True
  746 
  747         mediums = self.MediumSetFactory(mediums)
  748         return bool(self.getMediumSet().intersection(mediums))
  749 
  750 
  751     def getMediumSet(self):
  752         return self.mediumSet
  753 
  754 
  755     def setMediumSet(self, mediumSet):
  756         self.mediumSet = self.MediumSetFactory(mediumSet)
  757 
  758 
  759     def updateMediumSet(self, mediumSet):
  760         self.getMediumSet().update(mediumSet)
  761 
  762 
  763     def getTrackImportance(self):
  764         return self.trackImportance
  765 
  766 
  767     def setTrackImportance(self, trackImportance=True):
  768         self.trackImportance = trackImportance
  769 
  770 
  771     #~ helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  772 
  773     def _pushState(self):
  774         _restoreState = self.__dict__
  775         self.__dict__ = self.__dict__.copy()
  776         self._restoreState = _restoreState
  777         self.namespaces = {}
  778 
  779 
  780     def _popState(self):
  781         self.__dict__ = self._restoreState
  782 
  783 
  784     def _declarations(self, declarations, DeclarationsFactory=None):
  785         DeclarationsFactory = DeclarationsFactory or self.DeclarationsFactory
  786         if self.trackImportance:
  787             normal, important = [], []
  788             for d in declarations:
  789                 if d[-1]:
  790                     important.append(d[:-1])
  791                 else:
  792                     normal.append(d[:-1])
  793             return DeclarationsFactory(normal), DeclarationsFactory(important)
  794         else:
  795             return DeclarationsFactory(declarations)
  796 
  797 
  798     def _xmlnsGetSynonym(self, uri):
  799         # Don't forget to substitute our namespace synonyms!
  800         return self.xmlnsSynonyms.get(uri or None, uri) or None
  801 
  802 
  803     #~ css results ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  804 
  805     def beginStylesheet(self):
  806         self._pushState()
  807 
  808 
  809     def endStylesheet(self):
  810         self._popState()
  811 
  812 
  813     def stylesheet(self, stylesheetElements, stylesheetImports):
  814         # XXX Updated for PISA
  815         if self.trackImportance:
  816             normal, important = self.RulesetFactory(), self.RulesetFactory()
  817             for normalStylesheet, importantStylesheet in stylesheetImports:
  818                 normal.mergeStyles(normalStylesheet)
  819                 important.mergeStyles(importantStylesheet)
  820             for normalStyleElement, importantStyleElement in stylesheetElements:
  821                 normal.mergeStyles(normalStyleElement)
  822                 important.mergeStyles(importantStyleElement)
  823             return normal, important
  824         else:
  825             result = self.RulesetFactory()
  826             for stylesheet in stylesheetImports:
  827                 result.mergeStyles(stylesheet)
  828 
  829             for styleElement in stylesheetElements:
  830                 result.mergeStyles(styleElement)
  831             return result
  832 
  833 
  834     def beginInline(self):
  835         self._pushState()
  836 
  837 
  838     def endInline(self):
  839         self._popState()
  840 
  841 
  842     def specialRules(self, declarations):
  843         return cssSpecial.parseSpecialRules(declarations)
  844 
  845 
  846     def inline(self, declarations):
  847         declarations = self.specialRules(declarations)
  848         return self._declarations(declarations, CSSInlineRuleset)
  849 
  850 
  851     def ruleset(self, selectors, declarations):
  852 
  853         # XXX Modified for pisa!
  854         declarations = self.specialRules(declarations)
  855         # XXX Modified for pisa!
  856 
  857         if self.trackImportance:
  858             normalDecl, importantDecl = self._declarations(declarations)
  859             normal, important = self.RulesetFactory(), self.RulesetFactory()
  860             for s in selectors:
  861                 s = s.asImmutable()
  862                 if normalDecl:
  863                     normal[s] = normalDecl
  864                 if importantDecl:
  865                     important[s] = importantDecl
  866             return normal, important
  867         else:
  868             declarations = self._declarations(declarations)
  869             result = [(s.asImmutable(), declarations) for s in selectors]
  870             return self.RulesetFactory(result)
  871 
  872 
  873     #~ css namespaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  874 
  875     def resolveNamespacePrefix(self, nsPrefix, name):
  876         if nsPrefix == '*':
  877             return (nsPrefix, '*', name)
  878         xmlns = self.namespaces.get(nsPrefix, None)
  879         xmlns = self._xmlnsGetSynonym(xmlns)
  880         return (nsPrefix, xmlns, name)
  881 
  882 
  883     #~ css @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  884 
  885     def atCharset(self, charset):
  886         self.charset = charset
  887 
  888 
  889     def atImport(self, import_, mediums, cssParser):
  890         if self.isValidMedium(mediums):
  891             return cssParser.parseExternal(import_)
  892         return None
  893 
  894 
  895     def atNamespace(self, nsprefix, uri):
  896         self.namespaces[nsprefix] = uri
  897 
  898 
  899     def atMedia(self, mediums, ruleset):
  900         if self.isValidMedium(mediums):
  901             return ruleset
  902         return None
  903 
  904 
  905     def atPage(self, page, pseudopage, declarations):
  906         """
  907         This is overriden by xhtml2pdf.context.pisaCSSBuilder
  908         """
  909         return self.ruleset([self.selector('*')], declarations)
  910 
  911 
  912     def atFontFace(self, declarations):
  913         """
  914         This is overriden by xhtml2pdf.context.pisaCSSBuilder
  915         """
  916         return self.ruleset([self.selector('*')], declarations)
  917 
  918 
  919     def atIdent(self, atIdent, cssParser, src):
  920         return src, NotImplemented
  921 
  922 
  923     #~ css selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  924 
  925     def selector(self, name):
  926         return self.SelectorFactory(name)
  927 
  928 
  929     def combineSelectors(self, selectorA, op, selectorB):
  930         return self.SelectorFactory.combineSelectors(selectorA, op, selectorB)
  931 
  932 
  933     #~ css declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  934 
  935     def property(self, name, value, important=False):
  936         if self.trackImportance:
  937             return (name, value, important)
  938         return (name, value)
  939 
  940 
  941     def combineTerms(self, termA, op, termB):
  942         if op in (',', ' '):
  943             if isinstance(termA, list):
  944                 termA.append(termB)
  945                 return termA
  946             return [termA, termB]
  947         elif op is None and termB is None:
  948             return [termA]
  949         else:
  950             if isinstance(termA, list):
  951                 # Bind these "closer" than the list operators -- i.e. work on
  952                 # the (recursively) last element of the list
  953                 termA[-1] = self.combineTerms(termA[-1], op, termB)
  954                 return termA
  955             return self.TermOperatorFactory(termA, op, termB)
  956 
  957 
  958     def termIdent(self, value):
  959         return value
  960 
  961 
  962     def termNumber(self, value, units=None):
  963         if units:
  964             return value, units
  965         return value
  966 
  967 
  968     def termRGB(self, value):
  969         return value
  970 
  971 
  972     def termURI(self, value):
  973         return value
  974 
  975 
  976     def termString(self, value):
  977         return value
  978 
  979 
  980     def termUnicodeRange(self, value):
  981         return value
  982 
  983 
  984     def termFunction(self, name, value):
  985         return self.TermFunctionFactory(name, value)
  986 
  987 
  988     def termUnknown(self, src):
  989         return src, NotImplemented
  990 
  991 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  992 # ~ CSS Parser -- finally!
  993 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  994 
  995 class CSSParser(cssParser.CSSParser):
  996     CSSBuilderFactory = CSSBuilder
  997 
  998 
  999     def __init__(self, cssBuilder=None, create=True, **kw):
 1000         if not cssBuilder and create:
 1001             assert cssBuilder is None
 1002             cssBuilder = self.createCSSBuilder(**kw)
 1003         cssParser.CSSParser.__init__(self, cssBuilder)
 1004 
 1005 
 1006     def createCSSBuilder(self, **kw):
 1007         return self.CSSBuilderFactory(**kw)
 1008 
 1009 
 1010     def parseExternal(self, cssResourceName):
 1011         if os.path.isfile(cssResourceName):
 1012             cssFile = open(cssResourceName, 'r')
 1013             return self.parseFile(cssFile, True)
 1014         raise RuntimeError("Cannot resolve external CSS file: \"%s\"" % cssResourceName)
 1015