Edgewall Software

Changeset 954

Show
Ignore:
Timestamp:
09/10/08 22:53:09 (10 months ago)
Author:
cmlenz
Message:

Merged the custom-directives branch back into trunk.

Location:
trunk
Files:
8 modified

Legend:

Unmodified
Added
Removed
  • trunk

  • trunk/genshi/filters/i18n.py

    r932 r954  
    2424from genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \ 
    2525                        END_NS, XML_NAMESPACE, _ensure 
    26 from genshi.template.base import Template, EXPR, SUB 
     26from genshi.template.base import DirectiveFactory, EXPR, SUB, _apply_directives 
     27from genshi.template.directives import Directive 
    2728from genshi.template.markup import MarkupTemplate, EXEC 
    2829 
     
    3334 
    3435 
    35 class Translator(object): 
     36class CommentDirective(Directive): 
     37 
     38    __slots__ = [] 
     39 
     40    @classmethod 
     41    def attach(cls, template, stream, value, namespaces, pos): 
     42        return None, stream 
     43 
     44 
     45class MsgDirective(Directive): 
     46 
     47    __slots__ = ['params'] 
     48 
     49    def __init__(self, value, template, hints=None, namespaces=None, 
     50                 lineno=-1, offset=-1): 
     51        Directive.__init__(self, None, template, namespaces, lineno, offset) 
     52        self.params = [name.strip() for name in value.split(',')] 
     53 
     54    def __call__(self, stream, directives, ctxt, **vars): 
     55        msgbuf = MessageBuffer(self.params) 
     56 
     57        stream = iter(stream) 
     58        yield stream.next() # the outer start tag 
     59        previous = stream.next() 
     60        for event in stream: 
     61            msgbuf.append(*previous) 
     62            previous = event 
     63 
     64        gettext = ctxt.get('_i18n.gettext') 
     65        for event in msgbuf.translate(gettext(msgbuf.format())): 
     66            yield event 
     67 
     68        yield previous # the outer end tag 
     69 
     70 
     71class Translator(DirectiveFactory): 
    3672    """Can extract and translate localizable strings from markup streams and 
    3773    templates. 
     
    86122    """ 
    87123 
     124    directives = [ 
     125        ('comment', CommentDirective), 
     126        ('msg', MsgDirective) 
     127    ] 
     128 
    88129    IGNORE_TAGS = frozenset([ 
    89130        QName('script'), QName('http://www.w3.org/1999/xhtml}script'), 
     
    92133    INCLUDE_ATTRS = frozenset(['abbr', 'alt', 'label', 'prompt', 'standby', 
    93134                               'summary', 'title']) 
     135    NAMESPACE = I18N_NAMESPACE 
    94136 
    95137    def __init__(self, translate=NullTranslations(), ignore_tags=IGNORE_TAGS, 
     
    114156        self.extract_text = extract_text 
    115157 
    116     def __call__(self, stream, ctxt=None, search_text=True, msgbuf=None): 
     158    def __call__(self, stream, ctxt=None, search_text=True): 
    117159        """Translate any localizable strings in the given stream. 
    118160         
     
    127169        :param search_text: whether text nodes should be translated (used 
    128170                            internally) 
    129         :param msgbuf: a `MessageBuffer` object or `None` (used internally) 
    130171        :return: the localized stream 
    131172        """ 
    132173        ignore_tags = self.ignore_tags 
    133174        include_attrs = self.include_attrs 
     175        skip = 0 
     176        xml_lang = XML_NAMESPACE['lang'] 
     177 
    134178        if type(self.translate) is FunctionType: 
    135179            gettext = self.translate 
    136180        else: 
    137181            gettext = self.translate.ugettext 
    138         if not self.extract_text: 
     182        if ctxt: 
     183            ctxt['_i18n.gettext'] = gettext 
     184 
     185        extract_text = self.extract_text 
     186        if not extract_text: 
    139187            search_text = False 
    140  
    141         ns_prefixes = [] 
    142         skip = 0 
    143         i18n_comment = I18N_NAMESPACE['comment'] 
    144         i18n_msg = I18N_NAMESPACE['msg'] 
    145         xml_lang = XML_NAMESPACE['lang'] 
    146188 
    147189        for kind, data, pos in stream: 
     
    169211                for name, value in attrs: 
    170212                    newval = value 
    171                     if search_text and isinstance(value, basestring): 
     213                    if extract_text and isinstance(value, basestring): 
    172214                        if name in include_attrs: 
    173215                            newval = gettext(value) 
     
    183225                    attrs = Attrs(new_attrs) 
    184226 
    185                 if msgbuf: 
    186                     msgbuf.append(kind, data, pos) 
    187                     continue 
    188                 elif i18n_msg in attrs: 
    189                     params = attrs.get(i18n_msg) 
    190                     if params and type(params) is list: # event tuple 
    191                         params = params[0][1] 
    192                     msgbuf = MessageBuffer(params) 
    193                 attrs -= (i18n_comment, i18n_msg) 
    194  
    195227                yield kind, (tag, attrs), pos 
    196228 
    197229            elif search_text and kind is TEXT: 
    198                 if not msgbuf: 
    199                     text = data.strip() 
    200                     if text: 
    201                         data = data.replace(text, unicode(gettext(text))) 
    202                     yield kind, data, pos 
    203                 else: 
    204                     msgbuf.append(kind, data, pos) 
    205  
    206             elif msgbuf and kind is EXPR: 
    207                 msgbuf.append(kind, data, pos) 
    208  
    209             elif not skip and msgbuf and kind is END: 
    210                 msgbuf.append(kind, data, pos) 
    211                 if not msgbuf.depth: 
    212                     for event in msgbuf.translate(gettext(msgbuf.format())): 
    213                         yield event 
    214                     msgbuf = None 
    215                     yield kind, data, pos 
     230                text = data.strip() 
     231                if text: 
     232                    data = data.replace(text, unicode(gettext(text))) 
     233                yield kind, data, pos 
    216234 
    217235            elif kind is SUB: 
    218                 subkind, substream = data 
    219                 new_substream = list(self(substream, ctxt, msgbuf=msgbuf)) 
    220                 yield kind, (subkind, new_substream), pos 
    221  
    222             elif kind is START_NS and data[1] == I18N_NAMESPACE: 
    223                 ns_prefixes.append(data[0]) 
    224  
    225             elif kind is END_NS and data in ns_prefixes: 
    226                 ns_prefixes.remove(data) 
     236                directives, substream = data 
     237                # If this is an i18n:msg directive, no need to translate text 
     238                # nodes here 
     239                is_msg = filter(None, [isinstance(d, MsgDirective) 
     240                                       for d in directives]) 
     241                substream = list(self(substream, ctxt, 
     242                                      search_text=not is_msg)) 
     243                yield kind, (directives, substream), pos 
    227244 
    228245            else: 
     
    373390                       belonging to the message was found 
    374391        """ 
    375         self.params = [name.strip() for name in params.split(',')] 
     392        if isinstance(params, basestring): 
     393            params = [name.strip() for name in params.split(',')] 
     394        self.params = params 
    376395        self.comment = comment 
    377396        self.lineno = lineno 
  • trunk/genshi/filters/tests/i18n.py

    r932 r954  
    175175        </html>""") 
    176176        gettext = lambda s: u"Für Details siehe bitte [1:Hilfe]." 
    177         tmpl.filters.insert(0, Translator(gettext)) 
     177        translator = Translator(gettext) 
     178        tmpl.filters.insert(0, translator) 
     179        tmpl.add_directives(Translator.NAMESPACE, translator) 
    178180        self.assertEqual("""<html> 
    179181          <p>Für Details siehe bitte <a href="help.html">Hilfe</a>.</p> 
     
    201203        </html>""") 
    202204        gettext = lambda s: u"Für Details siehe bitte [1:[2:Hilfeseite]]." 
    203         tmpl.filters.insert(0, Translator(gettext)) 
     205        translator = Translator(gettext) 
     206        tmpl.filters.insert(0, translator) 
     207        tmpl.add_directives(Translator.NAMESPACE, translator) 
    204208        self.assertEqual("""<html> 
    205209          <p>Für Details siehe bitte <a href="help.html"><em>Hilfeseite</em></a>.</p> 
     
    226230        </html>""") 
    227231        gettext = lambda s: u"[1:] Einträge pro Seite anzeigen." 
    228         tmpl.filters.insert(0, Translator(gettext)) 
     232        translator = Translator(gettext) 
     233        tmpl.filters.insert(0, translator) 
     234        tmpl.add_directives(Translator.NAMESPACE, translator) 
    229235        self.assertEqual("""<html> 
    230236          <p><input type="text" name="num"/> Einträge pro Seite anzeigen.</p> 
     
    251257        </html>""") 
    252258        gettext = lambda s: u"Für [2:Details] siehe bitte [1:Hilfe]." 
    253         tmpl.filters.insert(0, Translator(gettext)) 
     259        translator = Translator(gettext) 
     260        tmpl.filters.insert(0, translator) 
     261        tmpl.add_directives(Translator.NAMESPACE, translator) 
    254262        self.assertEqual("""<html> 
    255263          <p>Für <em>Details</em> siehe bitte <a href="help.html">Hilfe</a>.</p> 
     
    277285        </html>""") 
    278286        gettext = lambda s: u"[1:] Einträge pro Seite, beginnend auf Seite [2:]." 
    279         tmpl.filters.insert(0, Translator(gettext)) 
     287        translator = Translator(gettext) 
     288        tmpl.filters.insert(0, translator) 
     289        tmpl.add_directives(Translator.NAMESPACE, translator) 
    280290        self.assertEqual("""<html> 
    281291          <p><input type="text" name="num"/> Eintr\xc3\xa4ge pro Seite, beginnend auf Seite <input type="text" name="num"/>.</p> 
     
    302312        </html>""") 
    303313        gettext = lambda s: u"Hallo, %(name)s!" 
    304         tmpl.filters.insert(0, Translator(gettext)) 
     314        translator = Translator(gettext) 
     315        tmpl.filters.insert(0, translator) 
     316        tmpl.add_directives(Translator.NAMESPACE, translator) 
    305317        self.assertEqual("""<html> 
    306318          <p>Hallo, Jim!</p> 
     
    315327        </html>""") 
    316328        gettext = lambda s: u"%(name)s, sei gegrüßt!" 
    317         tmpl.filters.insert(0, Translator(gettext)) 
     329        translator = Translator(gettext) 
     330        tmpl.filters.insert(0, translator) 
     331        tmpl.add_directives(Translator.NAMESPACE, translator) 
    318332        self.assertEqual("""<html> 
    319333          <p>Jim, sei gegrüßt!</p> 
     
    328342        </html>""") 
    329343        gettext = lambda s: u"Sei gegrüßt, [1:Alter]!" 
    330         tmpl.filters.insert(0, Translator(gettext)) 
     344        translator = Translator(gettext) 
     345        tmpl.filters.insert(0, translator) 
     346        tmpl.add_directives(Translator.NAMESPACE, translator) 
    331347        self.assertEqual("""<html> 
    332348          <p>Sei gegrüßt, <a href="#42">Alter</a>!</p> 
     
    353369        </html>""") 
    354370        gettext = lambda s: u"%(name)s schrieb dies um %(time)s" 
    355         tmpl.filters.insert(0, Translator(gettext)) 
     371        translator = Translator(gettext) 
     372        tmpl.filters.insert(0, translator) 
     373        tmpl.add_directives(Translator.NAMESPACE, translator) 
    356374        entry = { 
    357375            'author': 'Jim', 
     
    404422        </html>""") 
    405423        gettext = lambda s: u"Voh" 
    406         tmpl.filters.insert(0, Translator(gettext)) 
     424        translator = Translator(gettext) 
     425        tmpl.filters.insert(0, translator) 
     426        tmpl.add_directives(Translator.NAMESPACE, translator) 
    407427        self.assertEqual("""<html> 
    408428          <p>Voh</p> 
    409429        </html>""", tmpl.generate().render()) 
    410430 
     431    def test_extract_i18n_msg_with_attr(self): 
     432        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     433            xmlns:i18n="http://genshi.edgewall.org/i18n"> 
     434          <p i18n:msg="" title="Foo bar">Foo</p> 
     435        </html>""") 
     436        translator = Translator() 
     437        messages = list(translator.extract(tmpl.stream)) 
     438        self.assertEqual(2, len(messages)) 
     439        self.assertEqual((3, None, u'Foo bar', []), messages[0]) 
     440        self.assertEqual((3, None, u'Foo', []), messages[1]) 
     441 
     442    def test_translate_i18n_msg_with_attr(self): 
     443        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     444            xmlns:i18n="http://genshi.edgewall.org/i18n"> 
     445          <p i18n:msg="" title="Foo bar">Foo</p> 
     446        </html>""") 
     447        gettext = lambda s: u"Voh" 
     448        translator = Translator(DummyTranslations({ 
     449            'Foo': u'Voh', 
     450            'Foo bar': u'Voh bär' 
     451        })) 
     452        tmpl.filters.insert(0, translator) 
     453        tmpl.add_directives(Translator.NAMESPACE, translator) 
     454        self.assertEqual("""<html> 
     455          <p title="Voh bär">Voh</p> 
     456        </html>""", tmpl.generate().render()) 
     457 
    411458    def test_translate_with_translations_object(self): 
    412459        tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" 
     
    414461          <p i18n:msg="" i18n:comment="As in foo bar">Foo</p> 
    415462        </html>""") 
    416         translations = DummyTranslations({'Foo': 'Voh'}) 
    417         tmpl.filters.insert(0, Translator(translations)) 
     463        translator = Translator(DummyTranslations({'Foo': 'Voh'})) 
     464        tmpl.filters.insert(0, translator) 
     465        tmpl.add_directives(Translator.NAMESPACE, translator) 
    418466        self.assertEqual("""<html> 
    419467          <p>Voh</p> 
  • trunk/genshi/template/base.py

    r876 r954  
    2727from genshi.input import ParseError 
    2828 
    29 __all__ = ['Context', 'Template', 'TemplateError', 'TemplateRuntimeError', 
    30            'TemplateSyntaxError', 'BadDirectiveError'] 
     29__all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError', 
     30           'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError'] 
    3131__docformat__ = 'restructuredtext en' 
    3232 
     
    302302 
    303303 
    304 class TemplateMeta(type): 
    305     """Meta class for templates.""" 
     304class DirectiveFactoryMeta(type): 
     305    """Meta class for directive factories.""" 
    306306 
    307307    def __new__(cls, name, bases, d): 
     
    313313 
    314314 
    315 class Template(object): 
     315class DirectiveFactory(object): 
     316    """Base for classes that provide a set of template directives. 
     317     
     318    :since: version 0.6 
     319    """ 
     320    __metaclass__ = DirectiveFactoryMeta 
     321 
     322    directives = [] 
     323    """A list of `(name, cls)` tuples that define the set of directives 
     324    provided by this factory. 
     325    """ 
     326 
     327    def compare_directives(self): 
     328        """Return a function that takes two directive classes and compares 
     329        them to determine their relative ordering. 
     330        """ 
     331        def _get_index(cls): 
     332            if cls in self._dir_order: 
     333                return self._dir_order.index(cls) 
     334            return 0 
     335        return lambda a, b: cmp(_get_index(a[0]), _get_index(b[0])) 
     336 
     337    def get_directive(self, name): 
     338        """Return the directive class for the given name. 
     339         
     340        :param name: the directive name as used in the template 
     341        :return: the directive class 
     342        :see: `Directive` 
     343        """ 
     344        return self._dir_by_name.get(name) 
     345 
     346 
     347class Template(DirectiveFactory): 
    316348    """Abstract template base class. 
    317349     
     
    319351    specify the syntax of templates. 
    320352    """ 
    321     __metaclass__ = TemplateMeta 
    322353 
    323354    EXEC = StreamEventKind('EXEC') 
     
    364395        self.allow_exec = allow_exec 
    365396        self._init_filters() 
     397        self._prepared = False 
    366398 
    367399        if isinstance(source, basestring): 
     
    370402            source = source 
    371403        try: 
    372             self.stream = list(self._prepare(self._parse(source, encoding))) 
     404            self._stream = self._parse(source, encoding) 
    373405        except ParseError, e: 
    374406            raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) 
     
    390422        if self.loader: 
    391423            self.filters.append(self._include) 
     424 
     425    def _get_stream(self): 
     426        if not self._prepared: 
     427            self._stream = list(self._prepare(self._stream)) 
     428            self._prepared = True 
     429        return self._stream 
     430    stream = property(_get_stream) 
    392431 
    393432    def _parse(self, source, encoding): 
  • trunk/genshi/template/markup.py

    r914 r954  
    4343    """ 
    4444 
    45     DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/') 
    46     XINCLUDE_NAMESPACE = Namespace('http://www.w3.org/2001/XInclude') 
     45    DIRECTIVE_NAMESPACE = 'http://genshi.edgewall.org/' 
     46    XINCLUDE_NAMESPACE = 'http://www.w3.org/2001/XInclude' 
    4747 
    4848    directives = [('def', DefDirective), 
     
    6161    _number_conv = Markup 
    6262 
     63    def __init__(self, source, filepath=None, filename=None, loader=None, 
     64                 encoding=None, lookup='strict', allow_exec=True): 
     65        Template.__init__(self, source, filepath=filepath, filename=filename, 
     66                          loader=loader, encoding=encoding, lookup=lookup, 
     67                          allow_exec=allow_exec) 
     68        self.add_directives(self.DIRECTIVE_NAMESPACE, self) 
     69 
    6370    def _init_filters(self): 
    6471        Template._init_filters(self) 
     
    7178 
    7279    def _parse(self, source, encoding): 
    73         streams = [[]] # stacked lists of events of the "compiled" template 
    74         dirmap = {} # temporary mapping of directives to elements 
    75         ns_prefix = {} 
    76         depth = 0 
    77         fallbacks = [] 
    78         includes = [] 
    79  
    8080        if not isinstance(source, Stream): 
    8181            source = XMLParser(source, filename=self.filename, 
    8282                               encoding=encoding) 
     83        stream = [] 
    8384 
    8485        for kind, data, pos in source: 
    85             stream = streams[-1] 
    86  
    87             if kind is START_NS: 
    88                 # Strip out the namespace declaration for template directives 
    89                 prefix, uri = data 
    90                 ns_prefix[prefix] = uri 
    91                 if uri not in (self.DIRECTIVE_NAMESPACE, 
    92                                self.XINCLUDE_NAMESPACE): 
     86 
     87            if kind is TEXT: 
     88                for kind, data, pos in interpolate(data, self.filepath, pos[1], 
     89                                                   pos[2], lookup=self.lookup): 
    9390                    stream.append((kind, data, pos)) 
    9491 
    95             elif kind is END_NS: 
    96                 uri = ns_prefix.pop(data, None) 
    97                 if uri and uri not in (self.DIRECTIVE_NAMESPACE, 
    98                                        self.XINCLUDE_NAMESPACE): 
     92            elif kind is PI and data[0] == 'python': 
     93                if not self.allow_exec: 
     94                    raise TemplateSyntaxError('Python code blocks not allowed', 
     95                                              self.filepath, *pos[1:]) 
     96                try: 
     97                    suite = Suite(data[1], self.filepath, pos[1], 
     98                                  lookup=self.lookup) 
     99                except SyntaxError, err: 
     100                    raise TemplateSyntaxError(err, self.filepath, 
     101                                              pos[1] + (err.lineno or 1) - 1, 
     102                                              pos[2] + (err.offset or 0)) 
     103                stream.append((EXEC, suite, pos)) 
     104 
     105            elif kind is COMMENT: 
     106                if not data.lstrip().startswith('!'): 
    99107                    stream.append((kind, data, pos)) 
    100108 
    101             elif kind is START: 
    102                 # Record any directive attributes in start tags 
     109            else: 
     110                stream.append((kind, data, pos)) 
     111 
     112        return stream 
     113 
     114    def _extract_directives(self, stream, namespace, factory): 
     115        depth = 0 
     116        dirmap = {} # temporary mapping of directives to elements 
     117        new_stream = [] 
     118        ns_prefix = {} # namespace prefixes in use 
     119 
     120        for kind, data, pos in stream: 
     121 
     122            if kind is START: 
    103123                tag, attrs = data 
    104124                directives = [] 
    105125                strip = False 
    106126 
    107                 if tag in self.DIRECTIVE_NAMESPACE: 
    108                     cls = self._dir_by_name.get(tag.localname) 
     127                if tag.namespace == namespace: 
     128                    cls = factory.get_directive(tag.localname) 
    109129                    if cls is None: 
    110                         raise BadDirectiveError(tag.localname, self.filepath, 
    111                                                 pos[1]) 
     130                        raise BadDirectiveError(tag.localname, 
     131                                                self.filepath, pos[1]) 
    112132                    args = dict([(name.localname, value) for name, value 
    113133                                 in attrs if not name.namespace]) 
     
    117137                new_attrs = [] 
    118138                for name, value in attrs: 
    119                     if name in self.DIRECTIVE_NAMESPACE: 
    120                         cls = self._dir_by_name.get(name.localname) 
     139                    if name.namespace == namespace: 
     140                        cls = factory.get_directive(name.localname) 
    121141                        if cls is None: 
    122142                            raise BadDirectiveError(name.localname, 
    123143                                                    self.filepath, pos[1]) 
    124                         directives.append((cls, value, ns_prefix.copy(), pos)) 
     144                        if type(value) is list and len(value) == 1: 
     145                            value = value[0][1] 
     146                        directives.append((cls, value, ns_prefix.copy(), 
     147                                           pos)) 
    125148                    else: 
    126                         if value: 
    127                             value = list(interpolate(value, self.filepath, 
    128                                                      pos[1], pos[2], 
    129                                                      lookup=self.lookup)) 
    130                             if len(value) == 1 and value[0][0] is TEXT: 
    131                                 value = value[0][1] 
    132                         else: 
    133                             value = [(TEXT, u'', pos)] 
    134149                        new_attrs.append((name, value)) 
    135150                new_attrs = Attrs(new_attrs) 
    136151 
    137152                if directives: 
    138                     index = self._dir_order.index 
    139                     directives.sort(lambda a, b: cmp(index(a[0]), index(b[0]))) 
    140                     dirmap[(depth, tag)] = (directives, len(stream), strip) 
    141  
    142                 if tag in self.XINCLUDE_NAMESPACE: 
     153                    directives.sort(self.compare_directives()) 
     154                    dirmap[(depth, tag)] = (directives, len(new_stream), 
     155                                            strip) 
     156 
     157                new_stream.append((kind, (tag, new_attrs), pos)) 
     158                depth += 1 
     159 
     160            elif kind is END: 
     161                depth -= 1 
     162                new_stream.append((kind, data, pos)) 
     163 
     164                # If there have have directive attributes with the 
     165                # corresponding start tag, move the events inbetween into 
     166                # a "subprogram" 
     167                if (depth, data) in dirmap: 
     168                    directives, offset, strip = dirmap.pop((depth, data)) 
     169                    substream = new_stream[offset:] 
     170                    if strip: 
     171                        substream = substream[1:-1] 
     172                    new_stream[offset:] = [ 
     173                        (SUB, (directives, substream), pos) 
     174                    ] 
     175 
     176            elif kind is SUB: 
     177                directives, substream = data 
     178                substream = self._extract_directives(substream, namespace, 
     179                                                     factory) 
     180 
     181                if len(substream) == 1 and substream[0][0] is SUB: 
     182                    added_directives, substream = substream[0][1] 
     183                    directives += added_directives 
     184 
     185                new_stream.append((kind, (directives, substream), pos)) 
     186 
     187            elif kind is START_NS: 
     188                # Strip out the namespace declaration for template 
     189                # directives 
     190                prefix, uri = data 
     191                ns_prefix[prefix] = uri 
     192                if uri != namespace: 
     193                    new_stream.append((kind, data, pos)) 
     194 
     195            elif kind is END_NS: 
     196                uri = ns_prefix.pop(data, None) 
     197                if uri and uri != namespace: 
     198                    new_stream.append((kind, data, pos)) 
     199 
     200            else: 
     201                new_stream.append((kind, data, pos)) 
     202 
     203        return new_stream 
     204 
     205    def _extract_includes(self, stream): 
     206        streams = [[]] # stacked lists of events of the "compiled" template 
     207        prefixes = {} 
     208        fallbacks = [] 
     209        includes = [] 
     210        xinclude_ns = Namespace(self.XINCLUDE_NAMESPACE) 
     211 
     212        for kind, data, pos in stream: 
     213            stream = streams[-1] 
     214 
     215            if kind is START: 
     216                # Record any directive attributes in start tags 
     217                tag, attrs = data 
     218                if tag in xinclude_ns: 
    143219                    if tag.localname == 'include': 
    144                         include_href = new_attrs.get('href') 
     220                        include_href = attrs.get('href') 
    145221                        if not include_href: 
    146222                            raise TemplateSyntaxError('Include misses required ' 
    147223                                                      'attribute "href"', 
    148224                                                      self.filepath, *pos[1:]) 
    149                         includes.append((include_href, new_attrs.get('parse'))) 
     225                        includes.append((include_href, attrs.get('parse'))) 
    150226                        streams.append([]) 
    151227                    elif tag.localname == 'fallback': 
    152228                        streams.append([]) 
    153229                        fallbacks.append(streams[-1]) 
    154  
    155230                else: 
    156                     stream.append((kind, (tag, new_attrs), pos)) 
    157  
    158                 depth += 1 
     231                    stream.append((kind, (tag, attrs), pos)) 
    159232 
    160233            elif kind is END: 
    161                 depth -= 1 
    162  
    163                 if fallbacks and data == self.XINCLUDE_NAMESPACE['fallback']: 
     234                if fallbacks and data == xinclude_ns['fallback']: 
    164235                    assert streams.pop() is fallbacks[-1] 
    165                 elif data == self.XINCLUDE_NAMESPACE['include']: 
     236                elif data == xinclude_ns['include']: 
    166237                    fallback = None 
    167238                    if len(fallbacks) == len(includes): 
     
    184255                    stream.append((kind, data, pos)) 
    185256 
    186                 # If there have have directive attributes with the corresponding 
    187                 # start tag, move the events inbetween into a "subprogram" 
    188                 if (depth, data) in dirmap: 
    189                     directives, start_offset, strip = dirmap.pop((depth, data)) 
    190                     substream = stream[start_offset:] 
    191                     if strip: 
    192                         substream = substream[1:-1] 
    193                     stream[start_offset:] = [(SUB, (directives, substream), 
    194                                               pos)] 
    195  
    196             elif kind is PI and data[0] == 'python': 
    197                 if not self.allow_exec: 
    198                     raise TemplateSyntaxError('Python code blocks not allowed', 
    199                                               self.filepath, *pos[1:]) 
    200                 try: 
    201                     suite = Suite(data[1], self.filepath, pos[1], 
    202                                   lookup=self.lookup) 
    203                 except SyntaxError, err: 
    204                     raise TemplateSyntaxError(err, self.filepath, 
    205                                               pos[1] + (err.lineno or 1) - 1, 
    206                                               pos[2] + (err.offset or 0)) 
    207                 stream.append((EXEC, suite, pos)) 
    208  
    209             elif kind is TEXT: 
    210                 for kind, data, pos in interpolate(data, self.filepath, pos[1], 
    211                                                    pos[2], lookup=self.lookup): 
    212                     stream.append((kind, data, pos)) 
    213  
    214             elif kind is COMMENT: 
    215                 if not data.lstrip().startswith('!'): 
    216                     stream.append((kind, data, pos)) 
     257            elif kind is START_NS and data[1] == xinclude_ns: 
     258                # Strip out the XInclude namespace 
     259                prefixes[data[0]] = data[1] 
     260 
     261            elif kind is END_NS and data in prefixes: 
     262                prefixes.pop(data) 
    217263 
    218264            else: 
     
    221267        assert len(streams) == 1 
    222268        return streams[0] 
     269 
     270    def _interpolate_attrs(self, stream): 
     271        for kind, data, pos in stream: 
     272 
     273            if kind is START: 
     274                # Record any directive attributes in start tags 
     275                tag, attrs = data 
     276                new_attrs = [] 
     277                for name, value in attrs: 
     278                    if value: 
     279                        value = list(interpolate(value, self.filepath, pos[1], 
     280                                                 pos[2], lookup=self.lookup)) 
     281                        if len(value) == 1 and value[0][0] is TEXT: 
     282                            value = value[0][1] 
     283                    else: 
     284                        value = [(TEXT, u'', pos)] 
     285                    new_attrs.append((name, value)) 
     286                data = tag, Attrs(new_attrs) 
     287 
     288            yield kind, data, pos 
     289 
     290    def _prepare(self, stream): 
     291        return Template._prepare(self, 
     292            self._extract_includes(self._interpolate_attrs(stream)) 
     293        ) 
     294 
     295    def add_directives(self, namespace, factory): 
     296        """Register a custom `DirectiveFactory` for a given namespace. 
     297         
     298        :param namespace: the namespace URI 
     299        :type namespace: `basestring` 
     300        :param factory: the directive factory to register 
     301        :type factory: `DirectiveFactory` 
     302        :since: version 0.6 
     303        """ 
     304        assert not self._prepared, 'Too late for adding directives, ' \ 
     305                                   'template already prepared' 
     306        self._stream = self._extract_directives(self._stream, namespace, 
     307                                                factory) 
    223308 
    224309    def _match(self, stream, ctxt, start=0, end=None, **vars): 
  • trunk/genshi/template/tests/directives.py

    r876 r954  
    505505        try: 
    506506            MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> 
    507           <py:for each=""> 
    508             empty 
    509           </py:for> 
    510         </doc>""", filename='test.html') 
     507              <py:for each=""> 
     508                empty 
     509              </py:for> 
     510            </doc>""", filename='test.html').generate() 
    511511            self.fail('ExpectedTemplateSyntaxError') 
    512512        except TemplateSyntaxError, e: 
     
    964964    def test_as_element(self): 
    965965        try: 
    966             tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> 
     966            MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> 
    967967              <py:content foo="">Foo</py:content> 
    968             </doc>""", filename='test.html') 
     968            </doc>""", filename='test.html').generate() 
    969969            self.fail('Expected TemplateSyntaxError') 
    970970        except TemplateSyntaxError, e: 
     
    982982        """ 
    983983        try: 
    984             tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> 
     984            MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> 
    985985              <elem py:replace="">Foo</elem> 
    986             </doc>""", filename='test.html') 
     986            </doc>""", filename='test.html').generate() 
    987987            self.fail('Expected TemplateSyntaxError') 
    988988        except TemplateSyntaxError, e: 
  • trunk/genshi/template/tests/markup.py

    r897 r954  
    8787        xml = """<p xmlns:py="http://genshi.edgewall.org/" py:if="bar'" />""" 
    8888        try: 
    89             tmpl = MarkupTemplate(xml, filename='test.html') 
    90             self.fail('Expected SyntaxError') 
     89            tmpl = MarkupTemplate(xml, filename='test.html').generate() 
     90            self.fail('Expected TemplateSyntaxError') 
    9191        except TemplateSyntaxError, e: 
    9292            self.assertEqual('test.html', e.filename) 
     
    9999        try: 
    100100            tmpl = MarkupTemplate(xml, filename='test.html') 
    101             self.fail('Expected SyntaxError') 
     101            self.fail('Expected TemplateSyntaxError') 
    102102        except TemplateSyntaxError, e: 
    103103            self.assertEqual('test.html', e.filename) 
     
    112112        try: 
    113113            tmpl = MarkupTemplate(xml, filename='test.html') 
    114             self.fail('Expected SyntaxError') 
     114            self.fail('Expected TemplateSyntaxError') 
    115115        except TemplateSyntaxError, e: 
    116116            self.assertEqual('test.html', e.filename) 
     
    131131    def test_text_noescape_quotes(self): 
    132132        """ 
    133         Verify that outputting context data in text nodes doesn't escape quotes. 
     133        Verify that outputting context data in text nodes doesn't escape 
     134        quotes. 
    134135        """ 
    135136        tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> 
  • trunk/genshi/template/text.py

    r835 r954  
    217217 
    218218            elif command: 
    219                 cls = self._dir_by_name.get(command) 
     219                cls = self.get_directive(command) 
    220220                if cls is None: 
    221221                    raise BadDirectiveError(command) 
     
    313313                stream.append((INCLUDE, (value.strip(), None, []), pos)) 
    314314            elif command != '#': 
    315                 cls = self._dir_by_name.get(command) 
     315                cls = self.get_directive(command) 
    316316                if cls is None: 
    317317                    raise BadDirectiveError(command)