"Fossies" - the Fresh Open Source Software Archive

Member "xhtml2pdf-0.2.8/xhtml2pdf/w3c/cssParser.py" (16 Jun 2022, 42690 Bytes) of package /linux/www/xhtml2pdf-0.2.8.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 "cssParser.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.2.7_vs_0.2.8.

    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 reportlab.lib.pagesizes import landscape
   12 
   13 import xhtml2pdf.default
   14 from xhtml2pdf.util import getSize
   15 
   16 try:
   17     from future_builtins import filter
   18 except ImportError:
   19     pass
   20 
   21 """CSS-2.1 parser.
   22 
   23 The CSS 2.1 Specification this parser was derived from can be found at http://www.w3.org/TR/CSS21/
   24 
   25 Primary Classes:
   26     * CSSParser
   27         Parses CSS source forms into results using a Builder Pattern.  Must
   28         provide concrete implemenation of CSSBuilderAbstract.
   29 
   30     * CSSBuilderAbstract
   31         Outlines the interface between CSSParser and it's rule-builder.
   32         Compose CSSParser with a concrete implementation of the builder to get
   33         usable results from the CSS parser.
   34 
   35 Dependencies:
   36     python 2.3 (or greater)
   37     re
   38 """
   39 
   40 import re
   41 from . import cssSpecial
   42 
   43 
   44 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   45 #~ Definitions
   46 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   47 
   48 def isAtRuleIdent(src, ident):
   49     return re.match(r'^@' + ident + r'\s*', src)
   50 
   51 
   52 def stripAtRuleIdent(src):
   53     return re.sub(r'^@[a-z\-]+\s*', '', src)
   54 
   55 
   56 class CSSSelectorAbstract(object):
   57     """Outlines the interface between CSSParser and it's rule-builder for selectors.
   58 
   59     CSSBuilderAbstract.selector and CSSBuilderAbstract.combineSelectors must
   60     return concrete implementations of this abstract.
   61 
   62     See css.CSSMutableSelector for an example implementation.
   63     """
   64 
   65 
   66     def addHashId(self, hashId):
   67         raise NotImplementedError('Subclass responsibility')
   68 
   69 
   70     def addClass(self, class_):
   71         raise NotImplementedError('Subclass responsibility')
   72 
   73 
   74     def addAttribute(self, attrName):
   75         raise NotImplementedError('Subclass responsibility')
   76 
   77 
   78     def addAttributeOperation(self, attrName, op, attrValue):
   79         raise NotImplementedError('Subclass responsibility')
   80 
   81 
   82     def addPseudo(self, name):
   83         raise NotImplementedError('Subclass responsibility')
   84 
   85 
   86     def addPseudoFunction(self, name, value):
   87         raise NotImplementedError('Subclass responsibility')
   88 
   89 
   90 class CSSBuilderAbstract(object):
   91     """Outlines the interface between CSSParser and it's rule-builder.  Compose
   92     CSSParser with a concrete implementation of the builder to get usable
   93     results from the CSS parser.
   94 
   95     See css.CSSBuilder for an example implementation
   96     """
   97 
   98 
   99     def setCharset(self, charset):
  100         raise NotImplementedError('Subclass responsibility')
  101 
  102 
  103     #~ css results ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  104 
  105     def beginStylesheet(self):
  106         raise NotImplementedError('Subclass responsibility')
  107 
  108 
  109     def stylesheet(self, elements):
  110         raise NotImplementedError('Subclass responsibility')
  111 
  112 
  113     def endStylesheet(self):
  114         raise NotImplementedError('Subclass responsibility')
  115 
  116 
  117     def beginInline(self):
  118         raise NotImplementedError('Subclass responsibility')
  119 
  120 
  121     def inline(self, declarations):
  122         raise NotImplementedError('Subclass responsibility')
  123 
  124 
  125     def endInline(self):
  126         raise NotImplementedError('Subclass responsibility')
  127 
  128 
  129     def ruleset(self, selectors, declarations):
  130         raise NotImplementedError('Subclass responsibility')
  131 
  132 
  133     #~ css namespaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  134 
  135     def resolveNamespacePrefix(self, nsPrefix, name):
  136         raise NotImplementedError('Subclass responsibility')
  137 
  138 
  139     #~ css @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  140 
  141     def atCharset(self, charset):
  142         raise NotImplementedError('Subclass responsibility')
  143 
  144 
  145     def atImport(self, import_, mediums, cssParser):
  146         raise NotImplementedError('Subclass responsibility')
  147 
  148 
  149     def atNamespace(self, nsPrefix, uri):
  150         raise NotImplementedError('Subclass responsibility')
  151 
  152 
  153     def atMedia(self, mediums, ruleset):
  154         raise NotImplementedError('Subclass responsibility')
  155 
  156 
  157     def atPage(self, page, pseudopage, declarations):
  158         raise NotImplementedError('Subclass responsibility')
  159 
  160 
  161     def atFontFace(self, declarations):
  162         raise NotImplementedError('Subclass responsibility')
  163 
  164 
  165     def atIdent(self, atIdent, cssParser, src):
  166         return src, NotImplemented
  167 
  168 
  169     #~ css selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  170 
  171     def combineSelectors(self, selectorA, combiner, selectorB):
  172         """Return value must implement CSSSelectorAbstract"""
  173         raise NotImplementedError('Subclass responsibility')
  174 
  175 
  176     def selector(self, name):
  177         """Return value must implement CSSSelectorAbstract"""
  178         raise NotImplementedError('Subclass responsibility')
  179 
  180 
  181     #~ css declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  182 
  183     def property(self, name, value, important=False):
  184         raise NotImplementedError('Subclass responsibility')
  185 
  186 
  187     def combineTerms(self, termA, combiner, termB):
  188         raise NotImplementedError('Subclass responsibility')
  189 
  190 
  191     def termIdent(self, value):
  192         raise NotImplementedError('Subclass responsibility')
  193 
  194 
  195     def termNumber(self, value, units=None):
  196         raise NotImplementedError('Subclass responsibility')
  197 
  198 
  199     def termRGB(self, value):
  200         raise NotImplementedError('Subclass responsibility')
  201 
  202 
  203     def termURI(self, value):
  204         raise NotImplementedError('Subclass responsibility')
  205 
  206 
  207     def termString(self, value):
  208         raise NotImplementedError('Subclass responsibility')
  209 
  210 
  211     def termUnicodeRange(self, value):
  212         raise NotImplementedError('Subclass responsibility')
  213 
  214 
  215     def termFunction(self, name, value):
  216         raise NotImplementedError('Subclass responsibility')
  217 
  218 
  219     def termUnknown(self, src):
  220         raise NotImplementedError('Subclass responsibility')
  221 
  222 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  223 #~ CSS Parser
  224 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  225 
  226 class CSSParseError(Exception):
  227     src = None
  228     ctxsrc = None
  229     fullsrc = None
  230     inline = False
  231     srcCtxIdx = None
  232     srcFullIdx = None
  233     ctxsrcFullIdx = None
  234 
  235 
  236     def __init__(self, msg, src, ctxsrc=None):
  237         Exception.__init__(self, msg)
  238         self.src = src
  239         self.ctxsrc = ctxsrc or src
  240         if self.ctxsrc:
  241             self.srcCtxIdx = self.ctxsrc.find(self.src)
  242             if self.srcCtxIdx < 0:
  243                 del self.srcCtxIdx
  244 
  245 
  246     def __str__(self):
  247         if self.ctxsrc:
  248             return Exception.__str__(self) + ':: (' + repr(self.ctxsrc[:self.srcCtxIdx]) + ', ' + repr(
  249                 self.ctxsrc[self.srcCtxIdx:self.srcCtxIdx + 20]) + ')'
  250         else:
  251             return Exception.__str__(self) + ':: ' + repr(self.src[:40])
  252 
  253 
  254     def setFullCSSSource(self, fullsrc, inline=False):
  255         self.fullsrc = fullsrc
  256         if type(self.fullsrc) == bytes:
  257             self.fullsrc = str(self.fullsrc, 'utf-8')
  258         if inline:
  259             self.inline = inline
  260         if self.fullsrc:
  261             self.srcFullIdx = self.fullsrc.find(self.src)
  262             if self.srcFullIdx < 0:
  263                 del self.srcFullIdx
  264             self.ctxsrcFullIdx = self.fullsrc.find(self.ctxsrc)
  265             if self.ctxsrcFullIdx < 0:
  266                 del self.ctxsrcFullIdx
  267 
  268 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  269 
  270 class CSSParser(object):
  271     """CSS-2.1 parser dependent only upon the re module.
  272 
  273     Implemented directly from http://www.w3.org/TR/CSS21/grammar.html
  274     Tested with some existing CSS stylesheets for portability.
  275 
  276     CSS Parsing API:
  277         * setCSSBuilder()
  278             To set your concrete implementation of CSSBuilderAbstract
  279 
  280         * parseFile()
  281             Use to parse external stylesheets using a file-like object
  282 
  283             >>> cssFile = open('test.css', 'r')
  284             >>> stylesheets = myCSSParser.parseFile(cssFile)
  285 
  286         * parse()
  287             Use to parse embedded stylesheets using source string
  288 
  289             >>> cssSrc = '''
  290                 body,body.body {
  291                     font: 110%, "Times New Roman", Arial, Verdana, Helvetica, serif;
  292                     background: White;
  293                     color: Black;
  294                 }
  295                 a {text-decoration: underline;}
  296             '''
  297             >>> stylesheets = myCSSParser.parse(cssSrc)
  298 
  299         * parseInline()
  300             Use to parse inline stylesheets using attribute source string
  301 
  302             >>> style = 'font: 110%, "Times New Roman", Arial, Verdana, Helvetica, serif; background: White; color: Black'
  303             >>> stylesheets = myCSSParser.parseInline(style)
  304 
  305         * parseAttributes()
  306             Use to parse attribute string values into inline stylesheets
  307 
  308             >>> stylesheets = myCSSParser.parseAttributes(
  309                     font='110%, "Times New Roman", Arial, Verdana, Helvetica, serif',
  310                     background='White',
  311                     color='Black')
  312 
  313         * parseSingleAttr()
  314             Use to parse a single string value into a CSS expression
  315 
  316             >>> fontValue = myCSSParser.parseSingleAttr('110%, "Times New Roman", Arial, Verdana, Helvetica, serif')
  317     """
  318 
  319     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  320     #~ Constants / Variables / Etc.
  321     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  322 
  323     ParseError = CSSParseError
  324 
  325     AttributeOperators = ['=', '~=', '|=', '&=', '^=', '!=', '<>']
  326     SelectorQualifiers = ('#', '.', '[', ':')
  327     SelectorCombiners = ['+', '>']
  328     ExpressionOperators = ('/', '+', ',')
  329 
  330     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  331     # ~ Regular expressions
  332     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  333 
  334     if True:  # makes the following code foldable
  335         _orRule = lambda *args: '|'.join(args)
  336         _reflags = re.I | re.M | re.U
  337         i_hex = '[0-9a-fA-F]'
  338         i_nonascii = '[\200-\377]'
  339         i_unicode = r'\\(?:%s){1,6}\s?' % i_hex
  340         i_escape = _orRule(i_unicode, r'\\[ -~\200-\377]')
  341         # i_nmstart = _orRule('[A-Za-z_]', i_nonascii, i_escape)
  342         i_nmstart = _orRule(r'\-[^0-9]|[A-Za-z_]', i_nonascii,
  343                             i_escape)  # XXX Added hyphen, http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
  344         i_nmchar = _orRule('[-0-9A-Za-z_]', i_nonascii, i_escape)
  345         i_ident = '((?:%s)(?:%s)*)' % (i_nmstart, i_nmchar)
  346         re_ident = re.compile(i_ident, _reflags)
  347         # Caution: treats all characters above 0x7f as legal for an identifier.
  348         i_unicodeid = r'([^\u0000-\u007f]+)'
  349         re_unicodeid = re.compile(i_unicodeid, _reflags)
  350         i_unicodestr1 = r'(\'[^\u0000-\u007f]+\')'
  351         i_unicodestr2 = r'(\"[^\u0000-\u007f]+\")'
  352         i_unicodestr = _orRule(i_unicodestr1, i_unicodestr2)
  353         re_unicodestr = re.compile(i_unicodestr, _reflags)
  354         i_element_name = r'((?:%s)|\*)' % (i_ident[1:-1],)
  355         re_element_name = re.compile(i_element_name, _reflags)
  356         i_namespace_selector = r'((?:%s)|\*|)\|(?!=)' % (i_ident[1:-1],)
  357         re_namespace_selector = re.compile(i_namespace_selector, _reflags)
  358         i_class = r'\.' + i_ident
  359         re_class = re.compile(i_class, _reflags)
  360         i_hash = '#((?:%s)+)' % i_nmchar
  361         re_hash = re.compile(i_hash, _reflags)
  362         i_rgbcolor = '(#%s{8}|#%s{6}|#%s{3})' % (i_hex, i_hex, i_hex)
  363         re_rgbcolor = re.compile(i_rgbcolor, _reflags)
  364         i_nl = '\n|\r\n|\r|\f'
  365         i_escape_nl = r'\\(?:%s)' % i_nl
  366         i_string_content = _orRule('[\t !#$%&(-~]', i_escape_nl, i_nonascii, i_escape)
  367         i_string1 = '\"((?:%s|\')*)\"' % i_string_content
  368         i_string2 = '\'((?:%s|\")*)\'' % i_string_content
  369         i_string = _orRule(i_string1, i_string2)
  370         re_string = re.compile(i_string, _reflags)
  371         i_uri = (r'url\(\s*(?:(?:%s)|((?:%s)+))\s*\)'
  372                  % (i_string, _orRule('[!#$%&*-~]', i_nonascii, i_escape)))
  373         # XXX For now
  374         # i_uri = '(url\\(.*?\\))'
  375         re_uri = re.compile(i_uri, _reflags)
  376         i_num = r'(([-+]?[0-9]+(?:\.[0-9]+)?)|([-+]?\.[0-9]+))'  # XXX Added out paranthesis, because e.g. .5em was not parsed correctly
  377         re_num = re.compile(i_num, _reflags)
  378         i_unit = '(%%|%s)?' % i_ident
  379         re_unit = re.compile(i_unit, _reflags)
  380         i_function = i_ident + r'\('
  381         re_function = re.compile(i_function, _reflags)
  382         i_functionterm = '[-+]?' + i_function
  383         re_functionterm = re.compile(i_functionterm, _reflags)
  384         i_unicoderange1 = r"(?:U\+%s{1,6}-%s{1,6})" % (i_hex, i_hex)
  385         i_unicoderange2 = r"(?:U\+\?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}(\?{0,3}|{h}(\?{0,2}|{h}(\??|{h}))))))"
  386         i_unicoderange = i_unicoderange1 # '(%s|%s)' % (i_unicoderange1, i_unicoderange2)
  387         re_unicoderange = re.compile(i_unicoderange, _reflags)
  388 
  389         # i_comment = '(?:\/\*[^*]*\*+([^/*][^*]*\*+)*\/)|(?://.*)'
  390         # gabriel: only C convention for comments is allowed in CSS
  391         i_comment = r'(?:\/\*[^*]*\*+([^/*][^*]*\*+)*\/)'
  392         re_comment = re.compile(i_comment, _reflags)
  393         i_important = r'!\s*(important)'
  394         re_important = re.compile(i_important, _reflags)
  395         del _orRule
  396 
  397     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  398     # ~ Public
  399     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  400 
  401     def __init__(self, cssBuilder=None):
  402         self.setCSSBuilder(cssBuilder)
  403 
  404 
  405     # ~ CSS Builder to delegate to ~~~~~~~~~~~~~~~~~~~~~~~~
  406 
  407     def getCSSBuilder(self):
  408         """A concrete instance implementing CSSBuilderAbstract"""
  409         return self._cssBuilder
  410 
  411 
  412     def setCSSBuilder(self, cssBuilder):
  413         """A concrete instance implementing CSSBuilderAbstract"""
  414         self._cssBuilder = cssBuilder
  415 
  416 
  417     cssBuilder = property(getCSSBuilder, setCSSBuilder)
  418 
  419     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  420     # ~ Public CSS Parsing API
  421     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  422 
  423     def parseFile(self, srcFile):
  424         """Parses CSS file-like objects using the current cssBuilder.
  425         Use for external stylesheets."""
  426 
  427         with open(srcFile, "r") as file_handler:
  428             file_content = file_handler.read()
  429 
  430         return self.parse(file_content)
  431 
  432     def parse(self, src):
  433         """Parses CSS string source using the current cssBuilder.
  434         Use for embedded stylesheets."""
  435 
  436         self.cssBuilder.beginStylesheet()
  437         try:
  438 
  439             # XXX Some simple preprocessing
  440             src = cssSpecial.cleanupCSS(src)
  441 
  442             try:
  443                 src, stylesheet = self._parseStylesheet(src)
  444             except self.ParseError as err:
  445                 err.setFullCSSSource(src)
  446                 raise
  447         finally:
  448             self.cssBuilder.endStylesheet()
  449         return stylesheet
  450 
  451 
  452     def parseInline(self, src):
  453         """Parses CSS inline source string using the current cssBuilder.
  454         Use to parse a tag's 'sytle'-like attribute."""
  455 
  456         self.cssBuilder.beginInline()
  457         try:
  458             try:
  459                 src, properties = self._parseDeclarationGroup(src.strip(), braces=False)
  460             except self.ParseError as err:
  461                 err.setFullCSSSource(src, inline=True)
  462                 raise
  463 
  464             result = self.cssBuilder.inline(properties)
  465         finally:
  466             self.cssBuilder.endInline()
  467         return result
  468 
  469     def parseAttributes(self, attributes=None, **kwAttributes):
  470         """Parses CSS attribute source strings, and return as an inline stylesheet.
  471         Use to parse a tag's highly CSS-based attributes like 'font'.
  472 
  473         See also: parseSingleAttr
  474         """
  475         attributes = attributes if attributes is not None else {}
  476         if attributes:
  477             kwAttributes.update(attributes)
  478 
  479         self.cssBuilder.beginInline()
  480         try:
  481             properties = []
  482             try:
  483                 for propertyName, src in kwAttributes.items():
  484                     src, single_property = self._parseDeclarationProperty(src.strip(), propertyName)
  485                     properties.append(single_property)
  486 
  487             except self.ParseError as err:
  488                 err.setFullCSSSource(src, inline=True)
  489                 raise
  490 
  491             result = self.cssBuilder.inline(properties)
  492         finally:
  493             self.cssBuilder.endInline()
  494         return result
  495 
  496 
  497     def parseSingleAttr(self, attrValue):
  498         """Parse a single CSS attribute source string, and returns the built CSS expression.
  499         Use to parse a tag's highly CSS-based attributes like 'font'.
  500 
  501         See also: parseAttributes
  502         """
  503 
  504         results = self.parseAttributes(temp=attrValue)
  505         if 'temp' in results[1]:
  506             return results[1]['temp']
  507         else:
  508             return results[0]['temp']
  509 
  510 
  511     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  512     # ~ Internal _parse methods
  513     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  514 
  515     def _parseStylesheet(self, src):
  516         """stylesheet
  517         : [ CHARSET_SYM S* STRING S* ';' ]?
  518             [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
  519             [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]*
  520         ;
  521         """
  522         # FIXME: BYTES to STR 
  523         if type(src) == bytes:
  524             src=src.decode()
  525         # Get rid of the comments
  526         src = self.re_comment.sub('', src)
  527 
  528         # [ CHARSET_SYM S* STRING S* ';' ]?
  529         src = self._parseAtCharset(src)
  530 
  531         # [S|CDO|CDC]*
  532         src = self._parseSCDOCDC(src)
  533         #  [ import [S|CDO|CDC]* ]*
  534         src, stylesheetImports = self._parseAtImports(src)
  535 
  536         # [ namespace [S|CDO|CDC]* ]*
  537         src = self._parseAtNamespace(src)
  538 
  539         stylesheetElements = []
  540 
  541         # [ [ ruleset | atkeywords ] [S|CDO|CDC]* ]*
  542         while src: # due to ending with ]*
  543             if src.startswith('@'):
  544                 # @media, @page, @font-face
  545                 src, atResults = self._parseAtKeyword(src)
  546                 if atResults is not None and atResults != NotImplemented:
  547                     stylesheetElements.extend(atResults)
  548             else:
  549                 # ruleset
  550                 src, ruleset = self._parseRuleset(src)
  551                 stylesheetElements.append(ruleset)
  552 
  553             # [S|CDO|CDC]*
  554             src = self._parseSCDOCDC(src)
  555 
  556         stylesheet = self.cssBuilder.stylesheet(stylesheetElements, stylesheetImports)
  557         return src, stylesheet
  558 
  559 
  560     def _parseSCDOCDC(self, src):
  561         """[S|CDO|CDC]*"""
  562         while 1:
  563             src = src.lstrip()
  564             if src.startswith('<!--'):
  565                 src = src[4:]
  566             elif src.startswith('-->'):
  567                 src = src[3:]
  568             else:
  569                 break
  570         return src
  571 
  572 
  573     # ~ CSS @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  574 
  575     def _parseAtCharset(self, src):
  576         """[ CHARSET_SYM S* STRING S* ';' ]?"""
  577         if isAtRuleIdent(src, 'charset'):
  578             src = stripAtRuleIdent(src)
  579             charset, src = self._getString(src)
  580             src = src.lstrip()
  581             if src[:1] != ';':
  582                 raise self.ParseError('@charset expected a terminating \';\'', src, self.ctxsrc)
  583             src = src[1:].lstrip()
  584 
  585             self.cssBuilder.atCharset(charset)
  586         return src
  587 
  588 
  589     def _parseAtImports(self, src):
  590         """[ import [S|CDO|CDC]* ]*"""
  591         result = []
  592         while isAtRuleIdent(src, 'import'):
  593             ctxsrc = src
  594             src = stripAtRuleIdent(src)
  595 
  596             import_, src = self._getStringOrURI(src)
  597             if import_ is None:
  598                 raise self.ParseError('Import expecting string or url', src, ctxsrc)
  599 
  600             mediums = []
  601             medium, src = self._getIdent(src.lstrip())
  602             while medium is not None:
  603                 mediums.append(medium)
  604                 if src[:1] == ',':
  605                     src = src[1:].lstrip()
  606                     medium, src = self._getIdent(src)
  607                 else:
  608                     break
  609 
  610             # XXX No medium inherits and then "all" is appropriate
  611             if not mediums:
  612                 mediums = ["all"]
  613 
  614             if src[:1] != ';':
  615                 raise self.ParseError('@import expected a terminating \';\'', src, ctxsrc)
  616             src = src[1:].lstrip()
  617 
  618             stylesheet = self.cssBuilder.atImport(import_, mediums, self)
  619             if stylesheet is not None:
  620                 result.append(stylesheet)
  621 
  622             src = self._parseSCDOCDC(src)
  623         return src, result
  624 
  625 
  626     def _parseAtNamespace(self, src):
  627         """namespace :
  628 
  629         @namespace S* [IDENT S*]? [STRING|URI] S* ';' S*
  630         """
  631 
  632         src = self._parseSCDOCDC(src)
  633         while isAtRuleIdent(src, 'namespace'):
  634             ctxsrc = src
  635             src = stripAtRuleIdent(src)
  636 
  637             namespace, src = self._getStringOrURI(src)
  638             if namespace is None:
  639                 nsPrefix, src = self._getIdent(src)
  640                 if nsPrefix is None:
  641                     raise self.ParseError('@namespace expected an identifier or a URI', src, ctxsrc)
  642                 namespace, src = self._getStringOrURI(src.lstrip())
  643                 if namespace is None:
  644                     raise self.ParseError('@namespace expected a URI', src, ctxsrc)
  645             else:
  646                 nsPrefix = None
  647 
  648             src = src.lstrip()
  649             if src[:1] != ';':
  650                 raise self.ParseError('@namespace expected a terminating \';\'', src, ctxsrc)
  651             src = src[1:].lstrip()
  652 
  653             self.cssBuilder.atNamespace(nsPrefix, namespace)
  654 
  655             src = self._parseSCDOCDC(src)
  656         return src
  657 
  658 
  659     def _parseAtKeyword(self, src):
  660         """[media | page | font_face | unknown_keyword]"""
  661         ctxsrc = src
  662         if isAtRuleIdent(src, 'media'):
  663             src, result = self._parseAtMedia(src)
  664         elif isAtRuleIdent(src, 'page'):
  665             src, result = self._parseAtPage(src)
  666         elif isAtRuleIdent(src, 'font-face'):
  667             src, result = self._parseAtFontFace(src)
  668         # XXX added @import, was missing!
  669         elif isAtRuleIdent(src, 'import'):
  670             src, result = self._parseAtImports(src)
  671         elif isAtRuleIdent(src, 'frame'):
  672             src, result = self._parseAtFrame(src)
  673         elif src.startswith('@'):
  674             src, result = self._parseAtIdent(src)
  675         else:
  676             raise self.ParseError('Unknown state in atKeyword', src, ctxsrc)
  677         return src, result
  678 
  679 
  680     def _parseAtMedia(self, src):
  681         """media
  682         : MEDIA_SYM S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S*
  683         ;
  684         """
  685         ctxsrc = src
  686         src = src[len('@media '):].lstrip()
  687         mediums = []
  688         while src and src[0] != '{':
  689             medium, src = self._getIdent(src)
  690             # make "and ... {" work
  691             if medium in (None, 'and'):
  692                 # default to mediatype "all"
  693                 if medium is None:
  694                     mediums.append('all')
  695                 # strip up to curly bracket
  696                 pattern = re.compile('.*({.*)')
  697                 match = re.match(pattern, src)
  698                 src = src[match.end()-1:]
  699                 break
  700             mediums.append(medium)
  701             if src[0] == ',':
  702                 src = src[1:].lstrip()
  703             else:
  704                 src = src.lstrip()
  705 
  706         if not src.startswith('{'):
  707             raise self.ParseError('Ruleset opening \'{\' not found', src, ctxsrc)
  708         src = src[1:].lstrip()
  709 
  710         stylesheetElements = []
  711         # while src and not src.startswith('}'):
  712         #    src, ruleset = self._parseRuleset(src)
  713         #    stylesheetElements.append(ruleset)
  714         #    src = src.lstrip()
  715 
  716         # Containing @ where not found and parsed
  717         while src and not src.startswith('}'):
  718             if src.startswith('@'):
  719                 # @media, @page, @font-face
  720                 src, atResults = self._parseAtKeyword(src)
  721                 if atResults is not None:
  722                     stylesheetElements.extend(atResults)
  723             else:
  724                 # ruleset
  725                 src, ruleset = self._parseRuleset(src)
  726                 stylesheetElements.append(ruleset)
  727             src = src.lstrip()
  728 
  729         if not src.startswith('}'):
  730             raise self.ParseError('Ruleset closing \'}\' not found', src, ctxsrc)
  731         else:
  732             src = src[1:].lstrip()
  733 
  734         result = self.cssBuilder.atMedia(mediums, stylesheetElements)
  735         return src, result
  736 
  737 
  738     def _parseAtPage(self, src):
  739         """page
  740         : PAGE_SYM S* IDENT? pseudo_page? S*
  741             '{' S* declaration [ ';' S* declaration ]* '}' S*
  742         ;
  743         """
  744 
  745         data = {}
  746         pageBorder = None
  747         isLandscape = False
  748 
  749         ctxsrc = src
  750         src = src[len('@page'):].lstrip()
  751         page, src = self._getIdent(src)
  752         if src[:1] == ':':
  753             pseudopage, src = self._getIdent(src[1:])
  754             page = page + '_' + pseudopage
  755         else:
  756             pseudopage = None
  757 
  758         # src, properties = self._parseDeclarationGroup(src.lstrip())
  759 
  760         # Containing @ where not found and parsed
  761         stylesheetElements = []
  762         src = src.lstrip()
  763         properties = []
  764 
  765         # XXX Extended for PDF use
  766         if not src.startswith('{'):
  767             raise self.ParseError('Ruleset opening \'{\' not found', src, ctxsrc)
  768         else:
  769             src = src[1:].lstrip()
  770 
  771         while src and not src.startswith('}'):
  772             if src.startswith('@'):
  773                 # @media, @page, @font-face
  774                 src, atResults = self._parseAtKeyword(src)
  775                 if atResults is not None:
  776                     stylesheetElements.extend(atResults)
  777             else:
  778                 src, nproperties = self._parseDeclarationGroup(src.lstrip(), braces=False)
  779                 properties += nproperties
  780 
  781                 # Set pagesize, orientation (landscape, portrait)
  782                 data = {}
  783                 pageBorder = None
  784 
  785                 if properties:
  786                     result = self.cssBuilder.ruleset([self.cssBuilder.selector('*')], properties)
  787                     try:
  788                         data = result[0].values()[0]
  789                     except Exception:
  790                         data = result[0].popitem()[1]
  791                     pageBorder = data.get("-pdf-frame-border", None)
  792 
  793                 if "-pdf-page-size" in data:
  794                     self.c.pageSize = xhtml2pdf.default.PML_PAGESIZES.get(
  795                         str(data["-pdf-page-size"]).lower(), self.c.pageSize)
  796 
  797                 isLandscape = False
  798                 if "size" in data:
  799                     size = data["size"]
  800                     if not isinstance(size, list):
  801                         size = [size]
  802                     sizeList = []
  803                     for value in size:
  804                         valueStr = str(value).lower()
  805                         if isinstance(value, tuple):
  806                             sizeList.append(getSize(value))
  807                         elif valueStr == "landscape":
  808                             isLandscape = True
  809                         elif valueStr == "portrait":
  810                             isLandscape = False
  811                         elif valueStr in xhtml2pdf.default.PML_PAGESIZES:
  812                             self.c.pageSize = xhtml2pdf.default.PML_PAGESIZES[valueStr]
  813                         else:
  814                             raise RuntimeError("Unknown size value for @page")
  815 
  816                     if len(sizeList) == 2:
  817                         self.c.pageSize = tuple(sizeList)
  818 
  819                     if isLandscape:
  820                         self.c.pageSize = landscape(self.c.pageSize)
  821 
  822             src = src.lstrip()
  823 
  824         result = [self.cssBuilder.atPage(page, pseudopage, data, isLandscape, pageBorder)]
  825 
  826         return src[1:].lstrip(), result
  827 
  828 
  829     def _parseAtFrame(self, src):
  830         """
  831         XXX Proprietary for PDF
  832         """
  833         src = src[len('@frame '):].lstrip()
  834         box, src = self._getIdent(src)
  835         src, properties = self._parseDeclarationGroup(src.lstrip())
  836         result = [self.cssBuilder.atFrame(box, properties)]
  837         return src.lstrip(), result
  838 
  839 
  840     def _parseAtFontFace(self, src):
  841         src = src[len('@font-face'):].lstrip()
  842         src, properties = self._parseDeclarationGroup(src)
  843         result = [self.cssBuilder.atFontFace(properties)]
  844         return src, result
  845 
  846 
  847     def _parseAtIdent(self, src):
  848         ctxsrc = src
  849         atIdent, src = self._getIdent(src[1:])
  850         if atIdent is None:
  851             raise self.ParseError('At-rule expected an identifier for the rule', src, ctxsrc)
  852 
  853         src, result = self.cssBuilder.atIdent(atIdent, self, src)
  854 
  855         if result is NotImplemented:
  856             # An at-rule consists of everything up to and including the next semicolon (;)
  857             # or the next block, whichever comes first
  858 
  859             semiIdx = src.find(';')
  860             if semiIdx < 0:
  861                 semiIdx = None
  862             blockIdx = src[:semiIdx].find('{')
  863             if blockIdx < 0:
  864                 blockIdx = None
  865 
  866             if semiIdx is not None and semiIdx < blockIdx:
  867                 src = src[semiIdx + 1:].lstrip()
  868             elif blockIdx is None:
  869                 # consume the rest of the content since we didn't find a block or a semicolon
  870                 src = src[-1:-1]
  871             elif blockIdx is not None:
  872                 # expecing a block...
  873                 src = src[blockIdx:]
  874                 try:
  875                     # try to parse it as a declarations block
  876                     src, declarations = self._parseDeclarationGroup(src)
  877                 except self.ParseError:
  878                     # try to parse it as a stylesheet block
  879                     src, stylesheet = self._parseStylesheet(src)
  880             else:
  881                 raise self.ParserError('Unable to ignore @-rule block', src, ctxsrc)
  882 
  883         return src.lstrip(), result
  884 
  885 
  886     # ~ ruleset - see selector and declaration groups ~~~~
  887 
  888     def _parseRuleset(self, src):
  889         """ruleset
  890         : selector [ ',' S* selector ]*
  891             '{' S* declaration [ ';' S* declaration ]* '}' S*
  892         ;
  893         """
  894         src, selectors = self._parseSelectorGroup(src)
  895         src, properties = self._parseDeclarationGroup(src.lstrip())
  896         result = self.cssBuilder.ruleset(selectors, properties)
  897         return src, result
  898 
  899 
  900     # ~ selector parsing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  901 
  902     def _parseSelectorGroup(self, src):
  903         selectors = []
  904         while src[:1] not in ('{', '}', ']', '(', ')', ';', ''):
  905             src, selector = self._parseSelector(src)
  906             if selector is None:
  907                 break
  908             selectors.append(selector)
  909             if src.startswith(','):
  910                 src = src[1:].lstrip()
  911         return src, selectors
  912 
  913 
  914     def _parseSelector(self, src):
  915         """selector
  916         : simple_selector [ combinator simple_selector ]*
  917         ;
  918         """
  919         src, selector = self._parseSimpleSelector(src)
  920         srcLen = len(src) # XXX
  921         while src[:1] not in ('', ',', ';', '{', '}', '[', ']', '(', ')'):
  922             for combiner in self.SelectorCombiners:
  923                 if src.startswith(combiner):
  924                     src = src[len(combiner):].lstrip()
  925                     break
  926             else:
  927                 combiner = ' '
  928             src, selectorB = self._parseSimpleSelector(src)
  929 
  930             # XXX Fix a bug that occured here e.g. : .1 {...}
  931             if len(src) >= srcLen:
  932                 src = src[1:]
  933                 while src and (src[:1] not in ('', ',', ';', '{', '}', '[', ']', '(', ')')):
  934                     src = src[1:]
  935                 return src.lstrip(), None
  936 
  937             selector = self.cssBuilder.combineSelectors(selector, combiner, selectorB)
  938 
  939         return src.lstrip(), selector
  940 
  941 
  942     def _parseSimpleSelector(self, src):
  943         """simple_selector
  944         : [ namespace_selector ]? element_name? [ HASH | class | attrib | pseudo ]* S*
  945         ;
  946         """
  947         ctxsrc = src.lstrip()
  948         nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
  949         name, src = self._getMatchResult(self.re_element_name, src)
  950         if name:
  951             pass # already *successfully* assigned
  952         elif src[:1] in self.SelectorQualifiers:
  953             name = '*'
  954         else:
  955             raise self.ParseError('Selector name or qualifier expected', src, ctxsrc)
  956 
  957         name = self.cssBuilder.resolveNamespacePrefix(nsPrefix, name)
  958         selector = self.cssBuilder.selector(name)
  959         while src and src[:1] in self.SelectorQualifiers:
  960             hash_, src = self._getMatchResult(self.re_hash, src)
  961             if hash_ is not None:
  962                 selector.addHashId(hash_)
  963                 continue
  964 
  965             class_, src = self._getMatchResult(self.re_class, src)
  966             if class_ is not None:
  967                 selector.addClass(class_)
  968                 continue
  969 
  970             if src.startswith('['):
  971                 src, selector = self._parseSelectorAttribute(src, selector)
  972             elif src.startswith(':'):
  973                 src, selector = self._parseSelectorPseudo(src, selector)
  974             else:
  975                 break
  976 
  977         return src.lstrip(), selector
  978 
  979 
  980     def _parseSelectorAttribute(self, src, selector):
  981         """attrib
  982         : '[' S* [ namespace_selector ]? IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
  983             [ IDENT | STRING ] S* ]? ']'
  984         ;
  985         """
  986         ctxsrc = src
  987         if not src.startswith('['):
  988             raise self.ParseError('Selector Attribute opening \'[\' not found', src, ctxsrc)
  989         src = src[1:].lstrip()
  990 
  991         nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
  992         attrName, src = self._getIdent(src)
  993 
  994         src = src.lstrip()
  995 
  996         if attrName is None:
  997             raise self.ParseError('Expected a selector attribute name', src, ctxsrc)
  998         if nsPrefix is not None:
  999             attrName = self.cssBuilder.resolveNamespacePrefix(nsPrefix, attrName)
 1000 
 1001         for op in self.AttributeOperators:
 1002             if src.startswith(op):
 1003                 break
 1004         else:
 1005             op = ''
 1006         src = src[len(op):].lstrip()
 1007 
 1008         if op:
 1009             attrValue, src = self._getIdent(src)
 1010             if attrValue is None:
 1011                 attrValue, src = self._getString(src)
 1012                 if attrValue is None:
 1013                     raise self.ParseError('Expected a selector attribute value', src, ctxsrc)
 1014         else:
 1015             attrValue = None
 1016 
 1017         if not src.startswith(']'):
 1018             raise self.ParseError('Selector Attribute closing \']\' not found', src, ctxsrc)
 1019         else:
 1020             src = src[1:]
 1021 
 1022         if op:
 1023             selector.addAttributeOperation(attrName, op, attrValue)
 1024         else:
 1025             selector.addAttribute(attrName)
 1026         return src, selector
 1027 
 1028 
 1029     def _parseSelectorPseudo(self, src, selector):
 1030         """pseudo
 1031         : ':' [ IDENT | function ]
 1032         ;
 1033         """
 1034         ctxsrc = src
 1035         if not src.startswith(':'):
 1036             raise self.ParseError('Selector Pseudo \':\' not found', src, ctxsrc)
 1037         src = re.search('^:{1,2}(.*)', src, re.M | re.S).group(1)
 1038 
 1039         name, src = self._getIdent(src)
 1040         if not name:
 1041             raise self.ParseError('Selector Pseudo identifier not found', src, ctxsrc)
 1042 
 1043         if src.startswith('('):
 1044             # function
 1045             src = src[1:].lstrip()
 1046             src, term = self._parseExpression(src, True)
 1047             if not src.startswith(')'):
 1048                 raise self.ParseError('Selector Pseudo Function closing \')\' not found', src, ctxsrc)
 1049             src = src[1:]
 1050             selector.addPseudoFunction(name, term)
 1051         else:
 1052             selector.addPseudo(name)
 1053 
 1054         return src, selector
 1055 
 1056 
 1057     # ~ declaration and expression parsing ~~~~~~~~~~~~~~~
 1058 
 1059     def _parseDeclarationGroup(self, src, braces=True):
 1060         ctxsrc = src
 1061         if src.startswith('{'):
 1062             src, braces = src[1:], True
 1063         elif braces:
 1064             raise self.ParseError('Declaration group opening \'{\' not found', src, ctxsrc)
 1065 
 1066         properties = []
 1067         src = src.lstrip()
 1068         while src[:1] not in ('', ',', '{', '}', '[', ']', '(', ')', '@'): # XXX @?
 1069             src, single_property = self._parseDeclaration(src)
 1070 
 1071             # XXX Workaround for styles like "*font: smaller"
 1072             if src.startswith("*"):
 1073                 src = "-nothing-" + src[1:]
 1074                 continue
 1075 
 1076             if single_property is None:
 1077                 src = src[1:].lstrip()
 1078                 break
 1079             properties.append(single_property)
 1080             if src.startswith(';'):
 1081                 src = src[1:].lstrip()
 1082             else:
 1083                 break
 1084 
 1085         if braces:
 1086             if not src.startswith('}'):
 1087                 raise self.ParseError('Declaration group closing \'}\' not found', src, ctxsrc)
 1088             src = src[1:]
 1089 
 1090         return src.lstrip(), properties
 1091 
 1092 
 1093     def _parseDeclaration(self, src):
 1094         """declaration
 1095         : ident S* ':' S* expr prio?
 1096         | /* empty */
 1097         ;
 1098         """
 1099         # property
 1100         propertyName, src = self._getIdent(src)
 1101 
 1102         if propertyName is not None:
 1103             src = src.lstrip()
 1104             # S* : S*
 1105             if src[:1] in (':', '='):
 1106                 # Note: we are being fairly flexable here...  technically, the
 1107                 # ":" is *required*, but in the name of flexibility we
 1108                 # suppor a null transition, as well as an "=" transition
 1109                 src = src[1:].lstrip()
 1110 
 1111             src, single_property = self._parseDeclarationProperty(src, propertyName)
 1112         else:
 1113             single_property = None
 1114 
 1115         return src, single_property
 1116 
 1117 
 1118     def _parseDeclarationProperty(self, src, propertyName):
 1119         # expr
 1120         src, expr = self._parseExpression(src)
 1121 
 1122         # prio?
 1123         important, src = self._getMatchResult(self.re_important, src)
 1124         src = src.lstrip()
 1125 
 1126         single_property = self.cssBuilder.property(propertyName, expr, important)
 1127         return src, single_property
 1128 
 1129 
 1130     def _parseExpression(self, src, returnList=False):
 1131         """
 1132         expr
 1133         : term [ operator term ]*
 1134         ;
 1135         """
 1136         src, term = self._parseExpressionTerm(src)
 1137         operator = None
 1138         while src[:1] not in ('', ';', '{', '}', '[', ']', ')'):
 1139             for operator in self.ExpressionOperators:
 1140                 if src.startswith(operator):
 1141                     src = src[len(operator):]
 1142                     break
 1143             else:
 1144                 operator = ' '
 1145             src, term2 = self._parseExpressionTerm(src.lstrip())
 1146             if term2 is NotImplemented:
 1147                 break
 1148             else:
 1149                 term = self.cssBuilder.combineTerms(term, operator, term2)
 1150 
 1151         if operator is None and returnList:
 1152             term = self.cssBuilder.combineTerms(term, None, None)
 1153             return src, term
 1154         else:
 1155             return src, term
 1156 
 1157 
 1158     def _parseExpressionTerm(self, src):
 1159         """term
 1160         : unary_operator?
 1161             [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
 1162             TIME S* | FREQ S* | function ]
 1163         | STRING S* | IDENT S* | URI S* | RGB S* | UNICODERANGE S* | hexcolor
 1164         ;
 1165         """
 1166         ctxsrc = src
 1167 
 1168         result, src = self._getMatchResult(self.re_num, src)
 1169         if result is not None:
 1170             units, src = self._getMatchResult(self.re_unit, src)
 1171             term = self.cssBuilder.termNumber(result, units)
 1172             return src.lstrip(), term
 1173 
 1174         result, src = self._getString(src, self.re_uri)
 1175         if result is not None:
 1176             # XXX URL!!!!
 1177             term = self.cssBuilder.termURI(result)
 1178             return src.lstrip(), term
 1179 
 1180         result, src = self._getString(src)
 1181         if result is not None:
 1182             term = self.cssBuilder.termString(result)
 1183             return src.lstrip(), term
 1184 
 1185         result, src = self._getMatchResult(self.re_functionterm, src)
 1186         if result is not None:
 1187             src, params = self._parseExpression(src, True)
 1188             if src[0] != ')':
 1189                 raise self.ParseError('Terminal function expression expected closing \')\'', src, ctxsrc)
 1190             src = src[1:].lstrip()
 1191             term = self.cssBuilder.termFunction(result, params)
 1192             return src, term
 1193 
 1194         result, src = self._getMatchResult(self.re_rgbcolor, src)
 1195         if result is not None:
 1196             term = self.cssBuilder.termRGB(result)
 1197             return src.lstrip(), term
 1198 
 1199         result, src = self._getMatchResult(self.re_unicoderange, src)
 1200         if result is not None:
 1201             term = self.cssBuilder.termUnicodeRange(result)
 1202             return src.lstrip(), term
 1203 
 1204         nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
 1205         result, src = self._getIdent(src)
 1206         if result is not None:
 1207             if nsPrefix is not None:
 1208                 result = self.cssBuilder.resolveNamespacePrefix(nsPrefix, result)
 1209             term = self.cssBuilder.termIdent(result)
 1210             return src.lstrip(), term
 1211 
 1212         result, src = self._getMatchResult(self.re_unicodeid, src)
 1213         if result is not None:
 1214             term = self.cssBuilder.termIdent(result)
 1215             return src.lstrip(), term
 1216 
 1217         result, src = self._getMatchResult(self.re_unicodestr, src)
 1218         if result is not None:
 1219             term = self.cssBuilder.termString(result)
 1220             return src.lstrip(), term
 1221 
 1222         return self.cssBuilder.termUnknown(src)
 1223 
 1224 
 1225     # ~ utility methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1226 
 1227     def _getIdent(self, src, default=None):
 1228         return self._getMatchResult(self.re_ident, src, default)
 1229 
 1230 
 1231     def _getString(self, src, rexpression=None, default=None):
 1232         if rexpression is None:
 1233             rexpression = self.re_string
 1234         result = rexpression.match(src)
 1235         if result:
 1236             strres = tuple(filter(None, result.groups()))
 1237             if strres:
 1238                 try:
 1239                     strres = strres[0]
 1240                 except Exception:
 1241                     strres = result.groups()[0]
 1242             else:
 1243                 strres = ''
 1244             return strres, src[result.end():]
 1245         else:
 1246             return default, src
 1247 
 1248 
 1249     def _getStringOrURI(self, src):
 1250         result, src = self._getString(src, self.re_uri)
 1251         if result is None:
 1252             result, src = self._getString(src)
 1253         return result, src
 1254 
 1255 
 1256     def _getMatchResult(self, rexpression, src, default=None, group=1):
 1257         result = rexpression.match(src)
 1258         if result:
 1259             return result.group(group), src[result.end():]
 1260         else:
 1261             return default, src
 1262