Edgewall Software

Changes in / [20:30]


Ignore:
Location:
/trunk
Files:
17 added
2 deleted
22 edited

Legend:

Unmodified
Added
Removed
  • /trunk/examples/basic

    • Property svn:ignore set to
      *.pyc
  • /trunk/examples/basic/kidrun.py

    r20 r30  
    77def test():
    88    base_path = os.path.dirname(os.path.abspath(__file__))
    9     kid.path = kid.TemplatePath([os.path.join(base_path, 'common'),
    10                                  os.path.join(base_path, 'module')])
     9    kid.path = kid.TemplatePath([base_path])
    1110
    1211    ctxt = dict(hello='<world>', hey='ZYX', bozz=None,
  • /trunk/examples/basic/run.py

    r20 r30  
    1010def test():
    1111    base_path = os.path.dirname(os.path.abspath(__file__))
    12     loader = TemplateLoader([os.path.join(base_path, 'common'),
    13                              os.path.join(base_path, 'module')],
    14                             auto_reload=True)
     12    loader = TemplateLoader([base_path], auto_reload=True)
    1513
    1614    start = datetime.now()
  • /trunk/markup/__init__.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414"""This package provides various means for generating and processing web markup
     
    4848
    4949>>> from markup.builder import tag
    50 >>> doc = tag.DOC(tag.TITLE('My document'), lang='en')
     50>>> doc = tag.doc(tag.title('My document'), lang='en')
    5151>>> doc.generate().render(method='html')
    5252'<doc lang="en"><title>My document</title></doc>'
    53 
    5453"""
    5554
    5655from markup.core import *
    57 from markup.input import XML, HTML
     56from markup.input import ParseError, XML, HTML
  • /trunk/markup/builder.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414from markup.core import Attributes, Namespace, QName, Stream
     
    1818
    1919class Fragment(object):
     20    """Represents a markup fragment, which is basically just a list of element
     21    or text nodes.
     22    """
    2023    __slots__ = ['children']
    2124
     
    4952
    5053    def generate(self):
    51         """Generator that yield tags and text nodes as strings."""
     54        """Return a markup event stream for the fragment."""
    5255        def _generate():
    5356            for child in self.children:
     
    161164
    162165    def generate(self):
    163         """Generator that yield tags and text nodes as strings."""
     166        """Return a markup event stream for the fragment."""
    164167        def _generate():
    165168            yield Stream.START, (self.tag, self.attrib), (-1, -1)
     
    171174
    172175class ElementFactory(object):
     176    """Factory for `Element` objects.
     177   
     178    A new element is created simply by accessing a correspondingly named
     179    attribute of the factory object:
     180   
     181    >>> factory = ElementFactory()
     182    >>> print factory.foo
     183    <foo/>
     184    >>> print factory.foo(id=2)
     185    <foo id="2"/>
     186   
     187    A factory can also be bound to a specific namespace:
     188   
     189    >>> factory = ElementFactory('http://www.w3.org/1999/xhtml')
     190    >>> print factory.html(lang="en")
     191    <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/>
     192   
     193    The namespace for a specific element can be altered on an existing factory
     194    by specifying the new namespace using item access:
     195   
     196    >>> factory = ElementFactory()
     197    >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3))
     198    <html><g id="3" xmlns="http://www.w3.org/2000/svg"/></html>
     199   
     200    Usually, the `ElementFactory` class is not be used directly. Rather, the
     201    `tag` instance should be used to create elements.
     202    """
    173203
    174204    def __init__(self, namespace=None):
    175         if not isinstance(namespace, Namespace):
     205        """Create the factory, optionally bound to the given namespace.
     206       
     207        @param namespace: the namespace URI for any created elements, or `None`
     208            for no namespace
     209        """
     210        if namespace and not isinstance(namespace, Namespace):
    176211            namespace = Namespace(namespace)
    177212        self.namespace = namespace
    178213
    179214    def __getitem__(self, namespace):
     215        """Return a new factory that is bound to the specified namespace."""
    180216        return ElementFactory(namespace)
    181217
    182218    def __getattr__(self, name):
     219        """Create an `Element` with the given name."""
    183220        return Element(self.namespace and self.namespace[name] or name)
    184221
  • /trunk/markup/core.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414"""Core classes for markup processing."""
     
    5858        """Initialize the stream with a sequence of markup events.
    5959       
    60         @oaram events: a sequence or iterable providing the events
     60        @param events: a sequence or iterable providing the events
    6161        """
    6262        self.events = events
     
    112112                   'html': output.HTMLSerializer}[method]
    113113        else:
    114             assert issubclass(cls, serializers.Serializer)
     114            assert issubclass(cls, output.Serializer)
    115115        serializer = cls(**kwargs)
    116116
     
    169169        of `(name, value)` tuples.
    170170        """
    171         list.__init__(self, map(lambda (k, v): (QName(k), v), attrib or []))
     171        if attrib is None:
     172            attrib = []
     173        list.__init__(self, [(QName(name), value) for name, value in attrib])
    172174
    173175    def __contains__(self, name):
     
    175177        name.
    176178        """
    177         return name in [attr for attr, value in self]
     179        return name in [attr for attr, _ in self]
    178180
    179181    def get(self, name, default=None):
     
    217219    __slots__ = []
    218220
    219     def __new__(self, text='', *args):
     221    def __new__(cls, text='', *args):
    220222        if args:
    221223            text %= tuple([escape(arg) for arg in args])
    222         return unicode.__new__(self, text)
     224        return unicode.__new__(cls, text)
    223225
    224226    def __add__(self, other):
     
    258260            else: # character entity
    259261                ref = match.group(2)
    260                 if keepxmlentities and ref in ('amp', 'apos', 'gt', 'lt', 'quot'):
     262                if keepxmlentities and ref in ('amp', 'apos', 'gt', 'lt',
     263                                               'quot'):
    261264                    return '&%s;' % ref
    262265                try:
  • /trunk/markup/eval.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
     13
     14"""Support for "safe" evaluation of Python expressions."""
    1315
    1416import __builtin__
     
    102104
    103105    def __init__(self, source):
     106        """Create the expression.
     107       
     108        @param source: the expression as string
     109        """
    104110        self.source = source
    105111        self.ast = None
    106112
    107113    def evaluate(self, data):
     114        """Evaluate the expression against the given data dictionary.
     115       
     116        @param data: a mapping containing the data to evaluate against
     117        @return: the result of the evaluation
     118        """
    108119        if not self.ast:
    109120            self.ast = compiler.parse(self.source, 'eval')
  • /trunk/markup/filters.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414"""Implementation of a number of stream filters."""
     
    5454        in_fallback = False
    5555        include_href, fallback_stream = None, None
    56         indent = 0
    5756
    5857        for kind, data, pos in stream:
     
    6362                if tag.localname == 'include':
    6463                    include_href = attrib.get('href')
    65                     indent = pos[1]
    6664                elif tag.localname == 'fallback':
    6765                    in_fallback = True
     
    7472                            raise TemplateError('Include misses required '
    7573                                                'attribute "href"')
    76                         template = self.loader.load(include_href)
     74                        template = self.loader.load(include_href,
     75                                                    relative_to=pos[0])
    7776                        for event in template.generate(ctxt):
    7877                            yield event
     
    8685                    include_href = None
    8786                    fallback_stream = None
    88                     indent = 0
    8987
    9088                elif data.localname == 'fallback':
  • /trunk/markup/input.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414from xml.parsers import expat
     
    1919import HTMLParser as html
    2020import htmlentitydefs
    21 import re
    2221from StringIO import StringIO
    2322
    2423from markup.core import Attributes, Markup, QName, Stream
     24
     25
     26class ParseError(Exception):
     27    """Exception raised when fatal syntax errors are found in the input being
     28    parsed."""
     29
     30    def __init__(self, message, filename='<string>', lineno=-1, offset=-1):
     31        Exception.__init__(self, message)
     32        self.filename = filename
     33        self.lineno = lineno
     34        self.offset = offset
    2535
    2636
    2737class XMLParser(object):
    2838    """Generator-based XML parser based on roughly equivalent code in
    29     Kid/ElementTree."""
    30 
    31     def __init__(self, source):
     39    Kid/ElementTree.
     40   
     41    The parsing is initiated by iterating over the parser object:
     42   
     43    >>> parser = XMLParser(StringIO('<root id="2"><child>Foo</child></root>'))
     44    >>> for kind, data, pos in parser:
     45    ...     print kind, data
     46    START (u'root', [(u'id', u'2')])
     47    START (u'child', [])
     48    TEXT Foo
     49    END child
     50    END root
     51    """
     52
     53    def __init__(self, source, filename=None):
     54        """Initialize the parser for the given XML text.
     55       
     56        @param source: the XML text as a file-like object
     57        @param filename: the name of the file, if appropriate
     58        """
    3259        self.source = source
     60        self.filename = filename
    3361
    3462        # Setup the Expat parser
     
    4977        # Location reporting is only support in Python >= 2.4
    5078        if not hasattr(parser, 'CurrentLineNumber'):
    51             self.getpos = self._getpos_unknown
     79            self._getpos = self._getpos_unknown
    5280
    5381        self.expat = parser
    54         self.queue = []
     82        self._queue = []
    5583
    5684    def __iter__(self):
    57         bufsize = 4 * 1024 # 4K
    58         done = False
    59         while True:
    60             while not done and len(self.queue) == 0:
    61                 data = self.source.read(bufsize)
    62                 if data == '': # end of data
    63                     if hasattr(self, 'expat'):
    64                         self.expat.Parse('', True)
    65                         del self.expat # get rid of circular references
    66                     done = True
    67                 else:
    68                     self.expat.Parse(data, False)
    69             for event in self.queue:
    70                 yield event
    71             self.queue = []
    72             if done:
    73                 break
     85        try:
     86            bufsize = 4 * 1024 # 4K
     87            done = False
     88            while True:
     89                while not done and len(self._queue) == 0:
     90                    data = self.source.read(bufsize)
     91                    if data == '': # end of data
     92                        if hasattr(self, 'expat'):
     93                            self.expat.Parse('', True)
     94                            del self.expat # get rid of circular references
     95                        done = True
     96                    else:
     97                        self.expat.Parse(data, False)
     98                for event in self._queue:
     99                    yield event
     100                self._queue = []
     101                if done:
     102                    break
     103        except expat.ExpatError, e:
     104            msg = str(e)
     105            if self.filename:
     106                msg += ', in ' + self.filename
     107            raise ParseError(msg, self.filename, e.lineno, e.offset)
     108
     109    def _enqueue(self, kind, data, pos=None):
     110        if pos is None:
     111            pos = self._getpos()
     112        self._queue.append((kind, data, pos))
    74113
    75114    def _getpos_unknown(self):
    76         return (-1, -1)
    77 
    78     def getpos(self):
    79         return self.expat.CurrentLineNumber, self.expat.CurrentColumnNumber
     115        return (self.filename or '<string>', -1, -1)
     116
     117    def _getpos(self):
     118        return (self.filename or '<string>', self.expat.CurrentLineNumber,
     119                self.expat.CurrentColumnNumber)
    80120
    81121    def _handle_start(self, tag, attrib):
    82         self.queue.append((Stream.START, (QName(tag), Attributes(attrib.items())),
    83                            self.getpos()))
     122        self._enqueue(Stream.START, (QName(tag), Attributes(attrib.items())))
    84123
    85124    def _handle_end(self, tag):
    86         self.queue.append((Stream.END, QName(tag), self.getpos()))
     125        self._enqueue(Stream.END, QName(tag))
    87126
    88127    def _handle_data(self, text):
    89         self.queue.append((Stream.TEXT, text, self.getpos()))
     128        self._enqueue(Stream.TEXT, text)
    90129
    91130    def _handle_prolog(self, version, encoding, standalone):
    92         self.queue.append((Stream.PROLOG, (version, encoding, standalone),
    93                            self.getpos()))
     131        self._enqueue(Stream.PROLOG, (version, encoding, standalone))
    94132
    95133    def _handle_doctype(self, name, sysid, pubid, has_internal_subset):
    96         self.queue.append((Stream.DOCTYPE, (name, pubid, sysid), self.getpos()))
     134        self._enqueue(Stream.DOCTYPE, (name, pubid, sysid))
    97135
    98136    def _handle_start_ns(self, prefix, uri):
    99         self.queue.append((Stream.START_NS, (prefix or '', uri), self.getpos()))
     137        self._enqueue(Stream.START_NS, (prefix or '', uri))
    100138
    101139    def _handle_end_ns(self, prefix):
    102         self.queue.append((Stream.END_NS, prefix or '', self.getpos()))
     140        self._enqueue(Stream.END_NS, prefix or '')
    103141
    104142    def _handle_pi(self, target, data):
    105         self.queue.append((Stream.PI, (target, data), self.getpos()))
     143        self._enqueue(Stream.PI, (target, data))
    106144
    107145    def _handle_comment(self, text):
    108         self.queue.append((Stream.COMMENT, text, self.getpos()))
     146        self._enqueue(Stream.COMMENT, text)
    109147
    110148    def _handle_other(self, text):
     
    113151            try:
    114152                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
    115                 self.queue.append((Stream.TEXT, text, self.getpos()))
     153                self._enqueue(Stream.TEXT, text)
    116154            except KeyError:
    117                 lineno, offset = self.getpos()
     155                lineno, offset = self._getpos()
    118156                raise expat.error("undefined entity %s: line %d, column %d" %
    119157                                  (text, lineno, offset))
     
    124162
    125163
    126 class HTMLParser(html.HTMLParser):
     164class HTMLParser(html.HTMLParser, object):
    127165    """Parser for HTML input based on the Python `HTMLParser` module.
    128166   
    129167    This class provides the same interface for generating stream events as
    130168    `XMLParser`, and attempts to automatically balance tags.
     169   
     170    The parsing is initiated by iterating over the parser object:
     171   
     172    >>> parser = HTMLParser(StringIO('<UL compact><LI>Foo</UL>'))
     173    >>> for kind, data, pos in parser:
     174    ...     print kind, data
     175    START (u'ul', [(u'compact', u'compact')])
     176    START (u'li', [])
     177    TEXT Foo
     178    END li
     179    END ul
    131180    """
    132181
     
    135184                              'param'])
    136185
    137     def __init__(self, source):
     186    def __init__(self, source, filename=None):
    138187        html.HTMLParser.__init__(self)
    139188        self.source = source
    140         self.queue = []
     189        self.filename = filename
     190        self._queue = []
    141191        self._open_tags = []
    142192
    143193    def __iter__(self):
    144         bufsize = 4 * 1024 # 4K
    145         done = False
    146         while True:
    147             while not done and len(self.queue) == 0:
    148                 data = self.source.read(bufsize)
    149                 if data == '': # end of data
    150                     self.close()
    151                     done = True
    152                 else:
    153                     self.feed(data)
    154             for kind, data, pos in self.queue:
    155                 yield kind, data, pos
    156             self.queue = []
    157             if done:
    158                 open_tags = self._open_tags
    159                 open_tags.reverse()
    160                 for tag in open_tags:
    161                     yield Stream.END, QName(tag), pos
    162                 break
     194        try:
     195            bufsize = 4 * 1024 # 4K
     196            done = False
     197            while True:
     198                while not done and len(self._queue) == 0:
     199                    data = self.source.read(bufsize)
     200                    if data == '': # end of data
     201                        self.close()
     202                        done = True
     203                    else:
     204                        self.feed(data)
     205                for kind, data, pos in self._queue:
     206                    yield kind, data, pos
     207                self._queue = []
     208                if done:
     209                    open_tags = self._open_tags
     210                    open_tags.reverse()
     211                    for tag in open_tags:
     212                        yield Stream.END, QName(tag), pos
     213                    break
     214        except html.HTMLParseError, e:
     215            msg = '%s: line %d, column %d' % (e.msg, e.lineno, e.offset)
     216            if self.filename:
     217                msg += ', in %s' % self.filename
     218            raise ParseError(msg, self.filename, e.lineno, e.offset)
     219
     220    def _enqueue(self, kind, data, pos=None):
     221        if pos is None:
     222            pos = self._getpos()
     223        self._queue.append((kind, data, pos))
     224
     225    def _getpos(self):
     226        lineno, column = self.getpos()
     227        return (self.filename, lineno, column)
    163228
    164229    def handle_starttag(self, tag, attrib):
    165         pos = self.getpos()
    166         self.queue.append((Stream.START, (QName(tag), Attributes(attrib)), pos))
     230        fixed_attrib = []
     231        for name, value in attrib: # Fixup minimized attributes
     232            if value is None:
     233                value = name
     234            fixed_attrib.append((name, unicode(value)))
     235
     236        self._enqueue(Stream.START, (QName(tag), Attributes(fixed_attrib)))
    167237        if tag in self._EMPTY_ELEMS:
    168             self.queue.append((Stream.END, QName(tag), pos))
     238            self._enqueue(Stream.END, QName(tag))
    169239        else:
    170240            self._open_tags.append(tag)
     
    172242    def handle_endtag(self, tag):
    173243        if tag not in self._EMPTY_ELEMS:
    174             pos = self.getpos()
    175244            while self._open_tags:
    176245                open_tag = self._open_tags.pop()
    177246                if open_tag.lower() == tag.lower():
    178247                    break
    179                 self.queue.append((Stream.END, QName(open_tag), pos))
    180             self.queue.append((Stream.END, QName(tag), pos))
     248                self._enqueue(Stream.END, QName(open_tag))
     249            self._enqueue(Stream.END, QName(tag))
    181250
    182251    def handle_data(self, text):
    183         self.queue.append((Stream.TEXT, text, self.getpos()))
     252        self._enqueue(Stream.TEXT, text)
    184253
    185254    def handle_charref(self, name):
    186         self.queue.append((Stream.TEXT, Markup('&#%s;' % name), self.getpos()))
     255        self._enqueue(Stream.TEXT, Markup('&#%s;' % name))
    187256
    188257    def handle_entityref(self, name):
    189         self.queue.append((Stream.TEXT, Markup('&%s;' % name), self.getpos()))
     258        self._enqueue(Stream.TEXT, Markup('&%s;' % name))
    190259
    191260    def handle_pi(self, data):
    192261        target, data = data.split(maxsplit=1)
    193262        data = data.rstrip('?')
    194         self.queue.append((Stream.PI, (target.strip(), data.strip()),
    195                            self.getpos()))
     263        self._enqueue(Stream.PI, (target.strip(), data.strip()))
    196264
    197265    def handle_comment(self, text):
    198         self.queue.append((Stream.COMMENT, text, self.getpos()))
     266        self._enqueue(Stream.COMMENT, text)
    199267
    200268
  • /trunk/markup/output.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414"""This module provides different kinds of serialization methods for XML event
     
    2222
    2323from markup.core import Markup, Namespace, QName, Stream
    24 from markup.filters import WhitespaceFilter
    2524
    2625__all__ = ['Serializer', 'XMLSerializer', 'HTMLSerializer']
     
    3130
    3231    def serialize(self, stream):
     32        """Must be implemented by concrete subclasses to serialize the given
     33        stream.
     34       
     35        This method must be implemented as a generator, producing the
     36        serialized output incrementally as unicode strings.
     37        """
    3338        raise NotImplementedError
    3439
     
    3843   
    3944    >>> from markup.builder import tag
    40     >>> elem = tag.DIV(tag.A(href='foo'), tag.BR, tag.HR(noshade=True))
     45    >>> elem = tag.div(tag.a(href='foo'), tag.br, tag.hr(noshade=True))
    4146    >>> print ''.join(XMLSerializer().serialize(elem.generate()))
    4247    <div><a href="foo"/><br/><hr noshade="True"/></div>
     
    4752        ns_mapping = {}
    4853
    49         stream = PushbackIterator(stream)
     54        stream = _PushbackIterator(stream)
    5055        for kind, data, pos in stream:
    5156
     
    8287                    attrname = attr.localname
    8388                    if attr.namespace:
    84                         try:
    85                             prefix = ns_mapping[attr.namespace]
    86                         except KeyError:
    87                             # FIXME: synthesize a prefix for the attribute?
    88                             prefix = ''
     89                        prefix = ns_mapping.get(attr.namespace)
    8990                        if prefix:
    9091                            attrname = prefix + ':' + attrname
     
    104105                tagname = tag.localname
    105106                if tag.namespace:
    106                     try:
    107                         prefix = ns_mapping[tag.namespace]
    108                         if prefix:
    109                             tagname = prefix + ':' + tag.localname
    110                     except KeyError:
    111                         pass
     107                    prefix = ns_mapping.get(tag.namespace)
     108                    if prefix:
     109                        tagname = prefix + ':' + tag.localname
    112110                yield Markup('</%s>' % tagname)
    113111
     
    120118   
    121119    >>> from markup.builder import tag
    122     >>> elem = tag.DIV(tag.A(href='foo'), tag.BR, tag.HR(noshade=True))
     120    >>> elem = tag.div(tag.a(href='foo'), tag.br, tag.hr(noshade=True))
    123121    >>> print ''.join(HTMLSerializer().serialize(elem.generate()))
    124122    <div><a href="foo"></a><br><hr noshade></div>
     
    137135        ns_mapping = {}
    138136
    139         stream = PushbackIterator(stream)
     137        stream = _PushbackIterator(stream)
    140138        for kind, data, pos in stream:
    141139
     
    180178
    181179
    182 class PushbackIterator(object):
     180class _PushbackIterator(object):
    183181    """A simple wrapper for iterators that allows pushing items back on the
    184182    queue via the `pushback()` method.
  • /trunk/markup/path.py

    r20 r30  
    11# -*- coding: utf-8 -*-
    22#
    3 # Copyright (C) 2006 Edgewall Software
     3# Copyright (C) 2006 Christopher Lenz
    44# All rights reserved.
    55#
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414"""Basic support for evaluating XPath expressions against streams."""
     
    2020__all__ = ['Path']
    2121
    22 _QUOTES = (("'", "'"), ('"', '"'))
    2322
    2423class Path(object):
    25     """Basic XPath support on markup event streams.
     24    """Implements basic XPath support on streams.
    2625   
    27     >>> from markup.input import XML
    28    
    29     Selecting specific tags:
    30    
    31     >>> Path('root').select(XML('<root/>')).render()
    32     '<root/>'
    33     >>> Path('//root').select(XML('<root/>')).render()
    34     '<root/>'
    35    
    36     Using wildcards for tag names:
    37    
    38     >>> Path('*').select(XML('<root/>')).render()
    39     '<root/>'
    40     >>> Path('//*').select(XML('<root/>')).render()
    41     '<root/>'
    42    
    43     Selecting attribute values:
    44    
    45     >>> Path('@foo').select(XML('<root/>')).render()
    46     ''
    47     >>> Path('@foo').select(XML('<root foo="bar"/>')).render()
    48     'bar'
    49    
    50     Selecting descendants:
    51    
    52     >>> Path("root/*").select(XML('<root><foo/><bar/></root>')).render()
    53     '<foo/><bar/>'
    54     >>> Path("root/bar").select(XML('<root><foo/><bar/></root>')).render()
    55     '<bar/>'
    56     >>> Path("root/baz").select(XML('<root><foo/><bar/></root>')).render()
    57     ''
    58     >>> Path("root/foo/*").select(XML('<root><foo><bar/></foo></root>')).render()
    59     '<bar/>'
    60    
    61     Selecting text nodes:
    62     >>> Path("item/text()").select(XML('<root><item>Foo</item></root>')).render()
    63     'Foo'
    64     >>> Path("item/text()").select(XML('<root><item>Foo</item><item>Bar</item></root>')).render()
    65     'FooBar'
    66    
    67     Skipping ancestors:
    68    
    69     >>> Path("foo/bar").select(XML('<root><foo><bar/></foo></root>')).render()
    70     '<bar/>'
    71     >>> Path("foo/*").select(XML('<root><foo><bar/></foo></root>')).render()
    72     '<bar/>'
    73     >>> Path("root/bar").select(XML('<root><foo><bar/></foo></root>')).render()
    74     ''
    75     >>> Path("root/bar").select(XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>')).render()
    76     '<bar id="2"/>'
    77     >>> Path("root/*/bar").select(XML('<root><foo><bar/></foo></root>')).render()
    78     '<bar/>'
    79     >>> Path("root//bar").select(XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>')).render()
    80     '<bar id="1"/><bar id="2"/>'
    81     >>> Path("root//bar").select(XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>')).render()
    82     '<bar id="1"/><bar id="2"/>'
    83    
    84     Using simple attribute predicates:
    85     >>> Path("root/item[@important]").select(XML('<root><item/><item important="very"/></root>')).render()
    86     '<item important="very"/>'
    87     >>> Path('root/item[@important="very"]').select(XML('<root><item/><item important="very"/></root>')).render()
    88     '<item important="very"/>'
    89     >>> Path("root/item[@important='very']").select(XML('<root><item/><item important="notso"/></root>')).render()
    90     ''
    91     >>> Path("root/item[@important!='very']").select(
    92     ...     XML('<root><item/><item important="notso"/></root>')).render()
    93     '<item/><item important="notso"/>'
     26    Instances of this class represent a "compiled" XPath expression, and provide
     27    methods for testing the path against a stream, as well as extracting a
     28    substream matching that path.
    9429    """
    95 
    9630    _TOKEN_RE = re.compile('(::|\.\.|\(\)|[/.:\[\]\(\)@=!])|'
    9731                           '([^/:\[\]\(\)@=!\s]+)|'
    9832                           '\s+')
     33    _QUOTES = (("'", "'"), ('"', '"'))
    9934
    10035    def __init__(self, text):
     36        """Create the path object from a string.
     37       
     38        @param text: the path expression
     39        """
    10140        self.source = text
    10241
     
    11352                elif op.startswith('('):
    11453                    if cur_tag == 'text':
    115                         steps[-1] = (False, self.fn_text(), [])
     54                        steps[-1] = (False, self._FunctionText(), [])
    11655                    else:
    11756                        raise NotImplementedError('XPath function "%s" not '
     
    12463                if cur_op == '@':
    12564                    if tag == '*':
    126                         node_test = self.any_attribute()
     65                        node_test = self._AnyAttribute()
    12766                    else:
    128                         node_test = self.attribute_by_name(tag)
     67                        node_test = self._AttributeByName(tag)
    12968                else:
    13069                    if tag == '*':
    131                         node_test = self.any_element()
     70                        node_test = self._AnyElement()
    13271                    elif in_predicate:
    133                         if len(tag) > 1 and (tag[0], tag[-1]) in _QUOTES:
    134                             node_test = self.literal_string(tag[1:-1])
     72                        if len(tag) > 1 and (tag[0], tag[-1]) in self._QUOTES:
     73                            node_test = self._LiteralString(tag[1:-1])
    13574                        if cur_op == '=':
    136                             node_test = self.op_eq(steps[-1][2][-1], node_test)
     75                            node_test = self._OperatorEq(steps[-1][2][-1],
     76                                                         node_test)
    13777                            steps[-1][2].pop()
    13878                        elif cur_op == '!=':
    139                             node_test = self.op_neq(steps[-1][2][-1], node_test)
     79                            node_test = self._OperatorNeq(steps[-1][2][-1],
     80                                                          node_test)
    14081                            steps[-1][2].pop()
    14182                    else:
    142                         node_test = self.element_by_name(tag)
     83                        node_test = self._ElementByName(tag)
    14384                if in_predicate:
    14485                    steps[-1][2].append(node_test)
     
    15394
    15495    def select(self, stream):
     96        """Returns a substream of the given stream that matches the path.
     97       
     98        If there are no matches, this method returns an empty stream.
     99       
     100        @param stream: the stream to select from
     101        @return: the substream matching the path, or an empty stream
     102        """
    155103        stream = iter(stream)
    156         def _generate(tests):
     104        def _generate():
    157105            test = self.test()
    158106            for kind, data, pos in stream:
     
    171119                elif result:
    172120                    yield result
    173         return Stream(_generate(self.steps))
     121        return Stream(_generate())
    174122
    175123    def test(self):
     124        """Returns a function that can be used to track whether the path matches
     125        a specific stream event.
     126       
     127        The function returned expects the positional arguments `kind`, `data`,
     128        and `pos`, i.e. basically an unpacked stream event. If the path matches
     129        the event, the function returns the match (for example, a `START` or
     130        `TEXT` event.) Otherwise, it returns `None` or `False`.
     131        """
    176132        stack = [0] # stack of cursors into the location path
    177133
    178134        def _test(kind, data, pos):
    179             #print '\nTracker %r test [%s] %r' % (self, kind, data)
    180 
    181135            if not stack:
    182136                return False
     
    192146            closure, node_test, predicates = self.steps[stack[-1]]
    193147
    194             #print '  Testing against %r' % node_test
    195148            matched = node_test(kind, data, pos)
    196149            if matched and predicates:
     
    202155            if matched:
    203156                if stack[-1] == len(self.steps) - 1:
    204                     #print '  Last step %r... returned %r' % (node_test, matched)
    205157                    return matched
    206158
    207                 #print '  Matched intermediate step %r... proceed to next step %r' % (node_test, self.steps[stack[-1] + 1])
    208159                stack[-1] += 1
    209160
    210161            elif kind is Stream.START and not closure:
    211                 # FIXME: If this step is not a closure, it cannot be matched
    212                 #        until the current element is closed... so we need to
    213                 #        move the cursor back to the last closure and retest
    214                 #        that against the current element
     162                # If this step is not a closure, it cannot be matched until the
     163                # current element is closed... so we need to move the cursor
     164                # back to the last closure and retest that against the current
     165                # element
    215166                closures = [step for step in self.steps[:stack[-1]] if step[0]]
    216167                closures.reverse()
     
    227178        return _test
    228179
    229     class any_element(object):
    230         def __call__(self, kind, data, pos):
     180    class _AnyElement(object):
     181        """Node test that matches any element."""
     182        def __call__(self, kind, *_):
    231183            if kind is Stream.START:
    232184                return True
     
    235187            return '<%s>' % self.__class__.__name__
    236188
    237     class element_by_name(object):
     189    class _ElementByName(object):
     190        """Node test that matches an element with a specific tag name."""
    238191        def __init__(self, name):
    239192            self.name = QName(name)
    240         def __call__(self, kind, data, pos):
     193        def __call__(self, kind, data, _):
    241194            if kind is Stream.START:
    242195                return data[0].localname == self.name
     
    245198            return '<%s "%s">' % (self.__class__.__name__, self.name)
    246199
    247     class any_attribute(object):
    248         def __call__(self, kind, data, pos):
    249             if kind is Stream.START:
    250                 text = ''.join([val for name, val in data[1]])
     200    class _AnyAttribute(object):
     201        """Node test that matches any attribute."""
     202        def __call__(self, kind, data, pos):
     203            if kind is Stream.START:
     204                text = ''.join([val for _, val in data[1]])
    251205                if text:
    252206                    return Stream.TEXT, text, pos
     
    256210            return '<%s>' % (self.__class__.__name__)
    257211
    258     class attribute_by_name(object):
     212    class _AttributeByName(object):
     213        """Node test that matches an attribute with a specific name."""
    259214        def __init__(self, name):
    260215            self.name = QName(name)
     
    268223            return '<%s "%s">' % (self.__class__.__name__, self.name)
    269224
    270     class fn_text(object):
     225    class _FunctionText(object):
     226        """Function that returns text content."""
    271227        def __call__(self, kind, data, pos):
    272228            if kind is Stream.TEXT:
     
    276232            return '<%s>' % (self.__class__.__name__)
    277233
    278     class literal_string(object):
     234    class _LiteralString(object):
     235        """Always returns a literal string."""
    279236        def __init__(self, value):
    280237            self.value = value
    281         def __call__(self, kind, data, pos):
     238        def __call__(self, *_):
    282239            return Stream.TEXT, self.value, (-1, -1)
    283240        def __repr__(self):
    284241            return '<%s>' % (self.__class__.__name__)
    285242
    286     class op_eq(object):
     243    class _OperatorEq(object):
     244        """Equality comparison operator."""
    287245        def __init__(self, lval, rval):
    288246            self.lval = lval
     
    296254                                     self.rval)
    297255
    298     class op_neq(object):
     256    class _OperatorNeq(object):
     257        """Inequality comparison operator."""
    299258        def __init__(self, lval, rval):
    300259            self.lval = lval
  • /trunk/markup/plugin.py

    r20 r30  
    11# -*- coding: utf-8 -*-
    22#
    3 # Copyright (C) 2006 Mattew Good
     3# Copyright (C) 2006 Matthew Good
     4# Copyright (C) 2006 Christopher Lenz
    45# All rights reserved.
    56#
    67# This software is licensed as described in the file COPYING, which
    78# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     9# are also available at http://markup.cmlenz.net/wiki/License.
    910#
    1011# This software consists of voluntary contributions made by many
    1112# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     13# history and logs, available at http://markup.cmlenz.net/log/.
     14
     15"""Basic support for the template engine plugin API used by TurboGears and
     16CherryPy/Buffet.
     17"""
    1318
    1419import os
     
    1924
    2025class TemplateEnginePlugin(object):
     26    """Implementation of the plugin API."""
    2127
    2228    def __init__(self, extra_vars_func=None, options=None):
     
    3541            package = templatename[:divider]
    3642            basename = templatename[divider + 1:] + '.html'
    37             fullpath = resource_filename(package, basename)
    38             dirname, templatename = os.path.split(fullpath)
    39             self.loader.search_path.append(dirname) # Kludge
     43            templatename = resource_filename(package, basename)
    4044
    4145        return self.loader.load(templatename)
    4246
    4347    def render(self, info, format='html', fragment=False, template=None):
    44         """Renders the template to a string using the provided info."""
     48        """Render the template to a string using the provided info."""
    4549        return self.transform(info, template).render(method=format)
    4650
    4751    def transform(self, info, template):
    48         "Render the output to Elements"
     52        """Render the output to an event stream."""
    4953        if not isinstance(template, Template):
    5054            template = self.load_template(template)
  • /trunk/markup/template.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414"""Template engine that is compatible with Kid (http://kid.lesscode.org) to a
     
    4444import compiler
    4545import os
     46import posixpath
    4647import re
    4748from StringIO import StringIO
     
    4950from markup.core import Attributes, Namespace, Stream, StreamEventKind
    5051from markup.eval import Expression
    51 from markup.filters import IncludeFilter
    5252from markup.input import HTML, XMLParser, XML
    5353from markup.path import Path
     
    123123
    124124    def __getitem__(self, key):
    125         """Get a variable's value, starting at the current context and going
    126         upward.
     125        """Get a variable's value, starting at the current context frame and
     126        going upward.
    127127        """
    128128        return self.get(key)
     
    136136
    137137    def get(self, key):
     138        """Get a variable's value, starting at the current context frame and
     139        going upward.
     140        """
    138141        for frame in self.stack:
    139142            if key in frame:
     
    141144
    142145    def push(self, **data):
     146        """Push a new context frame on the stack."""
    143147        self.stack.insert(0, data)
    144148
    145149    def pop(self):
     150        """Pop the top-most context frame from the stack.
     151       
     152        If the stack is empty, an `AssertionError` is raised.
     153        """
    146154        assert self.stack, 'Pop from empty context stack'
    147155        self.stack.pop(0)
     
    168176    __slots__ = ['expr']
    169177
    170     def __init__(self, template, value, pos):
     178    def __init__(self, value):
    171179        self.expr = value and Expression(value) or None
    172180
     
    290298    __slots__ = ['name', 'args', 'defaults', 'stream']
    291299
    292     def __init__(self, template, args, pos):
    293         Directive.__init__(self, template, None, pos)
     300    def __init__(self, args):
     301        Directive.__init__(self, None)
    294302        ast = compiler.parse(args, 'eval').node
    295303        self.args = []
     
    341349    __slots__ = ['targets']
    342350
    343     def __init__(self, template, value, pos):
    344         targets, expr_source = value.split(' in ', 1)
     351    def __init__(self, value):
     352        targets, value = value.split(' in ', 1)
    345353        self.targets = [str(name.strip()) for name in targets.split(',')]
    346         Directive.__init__(self, template, expr_source, pos)
     354        Directive.__init__(self, value)
    347355
    348356    def __call__(self, stream, ctxt):
     
    447455    __slots__ = ['path', 'stream']
    448456
    449     def __init__(self, template, value, pos):
    450         Directive.__init__(self, template, None, pos)
     457    def __init__(self, value):
     458        Directive.__init__(self, None)
    451459        self.path = Path(value)
    452460        self.stream = []
     
    575583    _dir_order = [directive[1] for directive in directives]
    576584
    577     def __init__(self, source, filename=None):
     585    def __init__(self, source, basedir=None, filename=None):
    578586        """Initialize a template from either a string or a file-like object."""
    579587        if isinstance(source, basestring):
     
    581589        else:
    582590            self.source = source
     591        self.basedir = basedir
    583592        self.filename = filename or '<string>'
    584 
    585         self.filters = [self._eval, self._match]
     593        if basedir and filename:
     594            self.filepath = os.path.join(basedir, filename)
     595        else:
     596            self.filepath = '<string>'
     597
     598        self.filters = []
    586599        self.parse()
    587600
    588601    def __repr__(self):
    589         return '<%s "%s">' % (self.__class__.__name__,
    590                               os.path.basename(self.filename))
     602        return '<%s "%s">' % (self.__class__.__name__, self.filename)
    591603
    592604    def parse(self):
     
    604616        depth = 0
    605617
    606         for kind, data, pos in XMLParser(self.source):
     618        for kind, data, pos in XMLParser(self.source, filename=self.filename):
    607619
    608620            if kind is Stream.START_NS:
     
    629641                        cls = self._dir_by_name.get(name.localname)
    630642                        if cls is None:
    631                             raise BadDirectiveError(name, self.filename, pos[0])
     643                            raise BadDirectiveError(name, self.filename, pos[1])
    632644                        else:
    633                             directives.append(cls(self, value, pos))
     645                            directives.append(cls(value))
    634646                    else:
    635647                        value = list(self._interpolate(value, *pos))
     
    667679    _SHORT_EXPR_RE = re.compile(r'(?<!\$)\$([a-zA-Z][a-zA-Z0-9_\.]*)')
    668680
    669     def _interpolate(cls, text, lineno=-1, offset=-1):
     681    def _interpolate(cls, text, filename=None, lineno=-1, offset=-1):
    670682        """Parse the given string and extract expressions.
    671683       
     
    689701                    else:
    690702                        yield Stream.TEXT, group.replace('$$', '$'), \
    691                               (lineno, offset)
     703                              (filename, lineno, offset)
    692704        return _interpolate(text)
    693705    _interpolate = classmethod(_interpolate)
    694706
    695707    def generate(self, ctxt=None):
    696         """Transform the template based on the given context data."""
     708        """Apply the template to the given context data.
     709       
     710        @param ctxt: a `Context` instance containing the data for the template
     711        @return: a markup event stream representing the result of applying
     712            the template to the context data.
     713        """
    697714        if ctxt is None:
    698715            ctxt = Context()
     
    700717            ctxt._match_templates = []
    701718
    702         return Stream(self._flatten(self.stream, ctxt))
     719        stream = self._match(self._eval(self.stream, ctxt), ctxt)
     720        return Stream(self._flatten(stream, ctxt))
    703721
    704722    def _eval(self, stream, ctxt=None):
     723        """Internal stream filter that evaluates any expressions in `START` and
     724        `TEXT` events.
     725        """
    705726        for kind, data, pos in stream:
    706727
     
    723744                        if not value:
    724745                            continue
    725                     new_attrib.append((name, ''.join(value)))
     746                    new_attrib.append((name, u''.join(value)))
    726747                yield kind, (tag, Attributes(new_attrib)), pos
    727748
     
    735756                # characters
    736757                if isinstance(result, basestring):
    737                     yield Stream.TEXT, result, pos
     758                    yield Stream.TEXT, unicode(result), pos
    738759                else:
    739760                    # Test if the expression evaluated to an iterable, in which
     
    749770                yield kind, data, pos
    750771
    751     def _flatten(self, stream, ctxt=None, apply_filters=True):
    752         if apply_filters:
    753             for filter_ in self.filters:
    754                 stream = filter_(iter(stream), ctxt)
     772    def _flatten(self, stream, ctxt=None):
     773        """Internal stream filter that expands `SUB` events in the stream."""
     774        for filter_ in self.filters:
     775            stream = filter_(iter(stream), ctxt)
    755776        try:
    756777            for kind, data, pos in stream:
     
    763784                    for directive in directives:
    764785                        substream = directive(iter(substream), ctxt)
     786                    substream = self._match(self._eval(substream, ctxt), ctxt)
    765787                    for event in self._flatten(substream, ctxt):
    766788                        yield event
     
    769791                    yield kind, data, pos
    770792        except SyntaxError, err:
    771             raise TemplateSyntaxError(err, self.filename, pos[0],
    772                                       pos[1] + (err.offset or 0))
     793            raise TemplateSyntaxError(err, self.filename, pos[1],
     794                                      pos[2] + (err.offset or 0))
    773795
    774796    def _match(self, stream, ctxt=None):
     797        """Internal stream filter that applies any defined match templates
     798        to the stream.
     799        """
    775800        for kind, data, pos in stream:
    776801
     
    784809                if (kind, data, pos) in template[::len(template)]:
    785810                    # This is the event this match template produced itself, so
    786                     # matching it  again would result in an infinite loop
     811                    # matching it again would result in an infinite loop
    787812                    continue
    788813
     
    805830                        test(*event)
    806831
    807                     content = list(self._flatten(content, ctxt, apply_filters=False))
     832                    content = list(self._flatten(content, ctxt))
    808833
    809834                    def _apply(stream, ctxt):
    810                         stream = list(stream)
    811835                        ctxt.push(select=lambda path: Stream(stream).select(path))
    812836                        for event in template:
     
    866890        self._mtime = {}
    867891
    868     def load(self, filename):
     892    def load(self, filename, relative_to=None):
    869893        """Load the template with the given name.
    870894       
    871         This method searches the search path trying to locate a template
    872         matching the given name. If no such template is found, a
    873         `TemplateNotFound` exception is raised. Otherwise, a `Template` object
    874         representing the requested template is returned.
    875        
    876         Template searches are cached to avoid having to parse the same template
    877         file more than once. Thus, subsequent calls of this method with the
    878         same template file name will return the same `Template` object.
     895        If the `filename` parameter is relative, this method searches the search
     896        path trying to locate a template matching the given name. If the file
     897        name is an absolute path, the search path is not bypassed.
     898       
     899        If requested template is not found, a `TemplateNotFound` exception is
     900        raised. Otherwise, a `Template` object is returned that represents the
     901        parsed template.
     902       
     903        Template instances are cached to avoid having to parse the same
     904        template file more than once. Thus, subsequent calls of this method
     905        with the same template file name will return the same `Template`
     906        object (unless the `auto_reload` option is enabled and the file was
     907        changed since the last parse.)
     908       
     909        If the `relative_to` parameter is provided, the `filename` is
     910        interpreted as being relative to that path.
    879911       
    880912        @param filename: the relative path of the template file to load
     913        @param relative_to: the filename of the template from which the new
     914            template is being loaded, or `None` if the template is being loaded
     915            directly
    881916        """
     917        if relative_to:
     918            filename = posixpath.join(posixpath.dirname(relative_to), filename)
    882919        filename = os.path.normpath(filename)
     920
     921        # First check the cache to avoid reparsing the same file
    883922        try:
    884923            tmpl = self._cache[filename]
    885924            if not self.auto_reload or \
    886                     os.path.getmtime(tmpl.filename) == self._mtime[filename]:
     925                    os.path.getmtime(tmpl.filepath) == self._mtime[filename]:
    887926                return tmpl
    888927        except KeyError:
    889928            pass
    890         for dirname in self.search_path:
     929
     930        # Bypass the search path if the filename is absolute
     931        search_path = self.search_path
     932        if os.path.isabs(filename):
     933            search_path = [os.path.dirname(filename)]
     934
     935        for dirname in search_path:
    891936            filepath = os.path.join(dirname, filename)
    892937            try:
    893938                fileobj = file(filepath, 'rt')
    894939                try:
    895                     tmpl = Template(fileobj, filename=filepath)
     940                    from markup.filters import IncludeFilter
     941                    tmpl = Template(fileobj, basedir=dirname, filename=filename)
    896942                    tmpl.filters.append(IncludeFilter(self))
    897943                finally:
  • /trunk/markup/tests/__init__.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414import doctest
  • /trunk/markup/tests/builder.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414import doctest
     
    2323
    2424    def test_link(self):
    25         link = tag.A(href='#', title='Foo', accesskey=None)('Bar')
     25        link = tag.a(href='#', title='Foo', accesskey=None)('Bar')
    2626        bits = iter(link.generate())
    2727        self.assertEqual((Stream.START, ('a', [('href', "#"), ('title', "Foo")]),
  • /trunk/markup/tests/core.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414import doctest
    15 from HTMLParser import HTMLParseError
    1615import unittest
    1716
    1817from markup.core import *
     18from markup.input import ParseError
    1919
    2020
     
    124124        self.assertEquals('', str(markup.sanitize()))
    125125        markup = Markup('<SCR\0IPT>alert("foo")</SCR\0IPT>')
    126         self.assertRaises(HTMLParseError, markup.sanitize().render)
     126        self.assertRaises(ParseError, markup.sanitize().render)
    127127        markup = Markup('<SCRIPT&XYZ SRC="http://example.com/"></SCRIPT>')
    128         self.assertRaises(HTMLParseError, markup.sanitize().render)
     128        self.assertRaises(ParseError, markup.sanitize().render)
    129129
    130130    def test_sanitize_remove_onclick_attr(self):
     
    157157        # Grave accents (not parsed)
    158158        markup = Markup('<IMG SRC=`javascript:alert("RSnake says, \'foo\'")`>')
    159         self.assertRaises(HTMLParseError, markup.sanitize().render)
     159        self.assertRaises(ParseError, markup.sanitize().render)
    160160        # Protocol encoded using UTF-8 numeric entities
    161161        markup = Markup('<IMG SRC=\'&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;'
  • /trunk/markup/tests/eval.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at hhttp://markup.cmlenz.net/log/.
    1313
    1414import doctest
    1515import unittest
    1616
    17 from markup import eval
    18 
     17from markup.eval import Expression
    1918
    2019def suite():
    2120    suite = unittest.TestSuite()
    22     suite.addTest(doctest.DocTestSuite(eval))
     21    suite.addTest(doctest.DocTestSuite(Expression.__module__))
    2322    return suite
    2423
  • /trunk/markup/tests/input.py

    r20 r30  
    11# -*- coding: utf-8 -*-
    22#
    3 # Copyright (C) 2006 Edgewall Software
     3# Copyright (C) 2006 Christopher Lenz
    44# All rights reserved.
    55#
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
     14import doctest
    1415import unittest
    1516
     
    1819
    1920
    20 class XMLParserTestCase(unittest.TestCase):
    21     pass
    22 
    23 
    24 
    2521def suite():
    2622    suite = unittest.TestSuite()
    27     suite.addTest(unittest.makeSuite(XMLParserTestCase, 'test'))
     23    suite.addTest(doctest.DocTestSuite(XMLParser.__module__))
    2824    return suite
    2925
  • /trunk/markup/tests/output.py

    r20 r30  
    11# -*- coding: utf-8 -*-
    22#
    3 # Copyright (C) 2006 Edgewall Software
     3# Copyright (C) 2006 Christopher Lenz
    44# All rights reserved.
    55#
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414import doctest
  • /trunk/markup/tests/path.py

    r20 r30  
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414import doctest
    1515import unittest
    1616
    17 from markup import path
     17from markup.input import XML
     18from markup.path import Path
     19
     20
     21class PathTestCase(unittest.TestCase):
     22
     23    def test_1step(self):
     24        xml = XML('<root/>')
     25        self.assertEqual('<root/>', Path('root').select(xml).render())
     26        self.assertEqual('<root/>', Path('//root').select(xml).render())
     27
     28    def test_1step_wildcard(self):
     29        xml = XML('<root/>')
     30        self.assertEqual('<root/>', Path('*').select(xml).render())
     31        self.assertEqual('<root/>', Path('//*').select(xml).render())
     32
     33    def test_1step_attribute(self):
     34        path = Path('@foo')
     35        self.assertEqual('', path.select(XML('<root/>')).render())
     36        self.assertEqual('bar', path.select(XML('<root foo="bar"/>')).render())
     37
     38    def test_1step_attribute(self):
     39        path = Path('@foo')
     40        self.assertEqual('', path.select(XML('<root/>')).render())
     41        self.assertEqual('bar', path.select(XML('<root foo="bar"/>')).render())
     42
     43    def test_2step(self):
     44        xml = XML('<root><foo/><bar/></root>')
     45        self.assertEqual('<foo/><bar/>', Path('root/*').select(xml).render())
     46        self.assertEqual('<bar/>', Path('root/bar').select(xml).render())
     47        self.assertEqual('', Path('root/baz').select(xml).render())
     48
     49    def test_2step_complex(self):
     50        xml = XML('<root><foo><bar/></foo></root>')
     51        self.assertEqual('<bar/>', Path('foo/bar').select(xml).render())
     52        self.assertEqual('<bar/>', Path('foo/*').select(xml).render())
     53        self.assertEqual('', Path('root/bar').select(xml).render())
     54
     55        xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>')
     56        self.assertEqual('<bar id="2"/>', Path('root/bar').select(xml).render())
     57
     58    def test_2step_text(self):
     59        xml = XML('<root><item>Foo</item></root>')
     60        self.assertEqual('Foo', Path('item/text()').select(xml).render())
     61        xml = XML('<root><item>Foo</item><item>Bar</item></root>')
     62        self.assertEqual('FooBar', Path('item/text()').select(xml).render())
     63
     64    def test_3step(self):
     65        xml = XML('<root><foo><bar/></foo></root>')
     66        self.assertEqual('<bar/>', Path('root/foo/*').select(xml).render())
     67
     68    def test_3step_complex(self):
     69        xml = XML('<root><foo><bar/></foo></root>')
     70        self.assertEqual('<bar/>', Path('root/*/bar').select(xml).render())
     71        xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>')
     72        self.assertEqual('<bar id="1"/><bar id="2"/>',
     73                         Path('root//bar').select(xml).render())
     74
     75    def test_predicate_attr(self):
     76        xml = XML('<root><item/><item important="very"/></root>')
     77        self.assertEqual('<item important="very"/>',
     78                         Path('root/item[@important]').select(xml).render())
     79        self.assertEqual('<item important="very"/>',
     80                         Path('root/item[@important="very"]').select(xml).render())
     81
     82        xml = XML('<root><item/><item important="notso"/></root>')
     83        self.assertEqual('',
     84                         Path('root/item[@important="very"]').select(xml).render())
     85        self.assertEqual('<item/><item important="notso"/>',
     86                         Path('root/item[@important!="very"]').select(xml).render())
    1887
    1988
    2089def suite():
    2190    suite = unittest.TestSuite()
    22     suite.addTest(doctest.DocTestSuite(path))
     91    suite.addTest(doctest.DocTestSuite(Path.__module__))
     92    suite.addTest(unittest.makeSuite(PathTestCase, 'test'))
    2393    return suite
    2494
  • /trunk/markup/tests/template.py

    r20 r30  
    11# -*- coding: utf-8 -*-
    22#
    3 # Copyright (C) 2006 Edgewall Software
     3# Copyright (C) 2006 Christopher Lenz
    44# All rights reserved.
    55#
    66# This software is licensed as described in the file COPYING, which
    77# you should have received as part of this distribution. The terms
    8 # are also available at http://trac.edgewall.com/license.html.
     8# are also available at http://markup.cmlenz.net/wiki/License.
    99#
    1010# This software consists of voluntary contributions made by many
    1111# individuals. For the exact contribution history, see the revision
    12 # history and logs, available at http://projects.edgewall.com/trac/.
     12# history and logs, available at http://markup.cmlenz.net/log/.
    1313
    1414import doctest
     
    7070        xml = '<p xmlns:py="http://purl.org/kid/ns#" py:do="nothing" />'
    7171        try:
    72             tmpl = Template(xml, 'test.html')
     72            tmpl = Template(xml, filename='test.html')
    7373        except BadDirectiveError, e:
    7474            self.assertEqual('test.html', e.filename)
     
    7878    def test_directive_value_syntax_error(self):
    7979        xml = '<p xmlns:py="http://purl.org/kid/ns#" py:if="bar\'" />'
    80         tmpl = Template(xml, 'test.html')
     80        tmpl = Template(xml, filename='test.html')
    8181        try:
    8282            list(tmpl.generate(Context()))
  • /trunk/setup.py

    r20 r30  
    77# This software is licensed as described in the file COPYING, which
    88# you should have received as part of this distribution. The terms
    9 # are also available at http://trac.edgewall.com/license.html.
     9# are also available at http://markup.cmlenz.net/wiki/License.
    1010#
    1111# This software consists of voluntary contributions made by many
    1212# individuals. For the exact contribution history, see the revision
    13 # history and logs, available at http://projects.edgewall.com/trac/.
     13# history and logs, available at http://markup.cmlenz.net/log/.
    1414
    1515from setuptools import setup, find_packages
Note: See TracChangeset for help on using the changeset viewer.