- Location:
- /trunk
- Files:
-
- 17 added
- 2 deleted
- 22 edited
-
COPYING (added)
-
README.txt (added)
-
examples/basic (modified) (1 prop)
-
examples/basic/common (deleted)
-
examples/basic/kidrun.py (modified) (1 diff)
-
examples/basic/layout.html (added)
-
examples/basic/layout.kid (added)
-
examples/basic/module (deleted)
-
examples/basic/run.py (modified) (1 diff)
-
examples/basic/test.html (added)
-
examples/basic/test.kid (added)
-
examples/includes (added)
-
examples/includes/common (added)
-
examples/includes/common/macros.html (added)
-
examples/includes/module (added)
-
examples/includes/module/test.html (added)
-
examples/includes/run.py (added)
-
examples/includes/skins (added)
-
examples/includes/skins/default (added)
-
examples/includes/skins/default/footer.html (added)
-
examples/includes/skins/default/header.html (added)
-
examples/includes/skins/default/layout.html (added)
-
markup/__init__.py (modified) (2 diffs)
-
markup/builder.py (modified) (5 diffs)
-
markup/core.py (modified) (7 diffs)
-
markup/eval.py (modified) (2 diffs)
-
markup/filters.py (modified) (5 diffs)
-
markup/input.py (modified) (7 diffs)
-
markup/output.py (modified) (10 diffs)
-
markup/path.py (modified) (15 diffs)
-
markup/plugin.py (modified) (3 diffs)
-
markup/template.py (modified) (25 diffs)
-
markup/tests/__init__.py (modified) (1 diff)
-
markup/tests/builder.py (modified) (2 diffs)
-
markup/tests/core.py (modified) (3 diffs)
-
markup/tests/eval.py (modified) (1 diff)
-
markup/tests/input.py (modified) (2 diffs)
-
markup/tests/output.py (modified) (1 diff)
-
markup/tests/path.py (modified) (1 diff)
-
markup/tests/template.py (modified) (3 diffs)
-
setup.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
/trunk/examples/basic
-
Property
svn:ignore
set to
*.pyc
-
Property
svn:ignore
set to
-
/trunk/examples/basic/kidrun.py
r20 r30 7 7 def test(): 8 8 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]) 11 10 12 11 ctxt = dict(hello='<world>', hey='ZYX', bozz=None, -
/trunk/examples/basic/run.py
r20 r30 10 10 def test(): 11 11 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) 15 13 16 14 start = datetime.now() -
/trunk/markup/__init__.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 """This package provides various means for generating and processing web markup … … 48 48 49 49 >>> 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') 51 51 >>> doc.generate().render(method='html') 52 52 '<doc lang="en"><title>My document</title></doc>' 53 54 53 """ 55 54 56 55 from markup.core import * 57 from markup.input import XML, HTML56 from markup.input import ParseError, XML, HTML -
/trunk/markup/builder.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 from markup.core import Attributes, Namespace, QName, Stream … … 18 18 19 19 class Fragment(object): 20 """Represents a markup fragment, which is basically just a list of element 21 or text nodes. 22 """ 20 23 __slots__ = ['children'] 21 24 … … 49 52 50 53 def generate(self): 51 """ Generator that yield tags and text nodes as strings."""54 """Return a markup event stream for the fragment.""" 52 55 def _generate(): 53 56 for child in self.children: … … 161 164 162 165 def generate(self): 163 """ Generator that yield tags and text nodes as strings."""166 """Return a markup event stream for the fragment.""" 164 167 def _generate(): 165 168 yield Stream.START, (self.tag, self.attrib), (-1, -1) … … 171 174 172 175 class 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 """ 173 203 174 204 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): 176 211 namespace = Namespace(namespace) 177 212 self.namespace = namespace 178 213 179 214 def __getitem__(self, namespace): 215 """Return a new factory that is bound to the specified namespace.""" 180 216 return ElementFactory(namespace) 181 217 182 218 def __getattr__(self, name): 219 """Create an `Element` with the given name.""" 183 220 return Element(self.namespace and self.namespace[name] or name) 184 221 -
/trunk/markup/core.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 """Core classes for markup processing.""" … … 58 58 """Initialize the stream with a sequence of markup events. 59 59 60 @ oaram events: a sequence or iterable providing the events60 @param events: a sequence or iterable providing the events 61 61 """ 62 62 self.events = events … … 112 112 'html': output.HTMLSerializer}[method] 113 113 else: 114 assert issubclass(cls, serializers.Serializer)114 assert issubclass(cls, output.Serializer) 115 115 serializer = cls(**kwargs) 116 116 … … 169 169 of `(name, value)` tuples. 170 170 """ 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]) 172 174 173 175 def __contains__(self, name): … … 175 177 name. 176 178 """ 177 return name in [attr for attr, valuein self]179 return name in [attr for attr, _ in self] 178 180 179 181 def get(self, name, default=None): … … 217 219 __slots__ = [] 218 220 219 def __new__( self, text='', *args):221 def __new__(cls, text='', *args): 220 222 if args: 221 223 text %= tuple([escape(arg) for arg in args]) 222 return unicode.__new__( self, text)224 return unicode.__new__(cls, text) 223 225 224 226 def __add__(self, other): … … 258 260 else: # character entity 259 261 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'): 261 264 return '&%s;' % ref 262 265 try: -
/trunk/markup/eval.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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.""" 13 15 14 16 import __builtin__ … … 102 104 103 105 def __init__(self, source): 106 """Create the expression. 107 108 @param source: the expression as string 109 """ 104 110 self.source = source 105 111 self.ast = None 106 112 107 113 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 """ 108 119 if not self.ast: 109 120 self.ast = compiler.parse(self.source, 'eval') -
/trunk/markup/filters.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 """Implementation of a number of stream filters.""" … … 54 54 in_fallback = False 55 55 include_href, fallback_stream = None, None 56 indent = 057 56 58 57 for kind, data, pos in stream: … … 63 62 if tag.localname == 'include': 64 63 include_href = attrib.get('href') 65 indent = pos[1]66 64 elif tag.localname == 'fallback': 67 65 in_fallback = True … … 74 72 raise TemplateError('Include misses required ' 75 73 'attribute "href"') 76 template = self.loader.load(include_href) 74 template = self.loader.load(include_href, 75 relative_to=pos[0]) 77 76 for event in template.generate(ctxt): 78 77 yield event … … 86 85 include_href = None 87 86 fallback_stream = None 88 indent = 089 87 90 88 elif data.localname == 'fallback': -
/trunk/markup/input.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 from xml.parsers import expat … … 19 19 import HTMLParser as html 20 20 import htmlentitydefs 21 import re22 21 from StringIO import StringIO 23 22 24 23 from markup.core import Attributes, Markup, QName, Stream 24 25 26 class 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 25 35 26 36 27 37 class XMLParser(object): 28 38 """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 """ 32 59 self.source = source 60 self.filename = filename 33 61 34 62 # Setup the Expat parser … … 49 77 # Location reporting is only support in Python >= 2.4 50 78 if not hasattr(parser, 'CurrentLineNumber'): 51 self. getpos = self._getpos_unknown79 self._getpos = self._getpos_unknown 52 80 53 81 self.expat = parser 54 self. queue = []82 self._queue = [] 55 83 56 84 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)) 74 113 75 114 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) 80 120 81 121 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()))) 84 123 85 124 def _handle_end(self, tag): 86 self. queue.append((Stream.END, QName(tag), self.getpos()))125 self._enqueue(Stream.END, QName(tag)) 87 126 88 127 def _handle_data(self, text): 89 self. queue.append((Stream.TEXT, text, self.getpos()))128 self._enqueue(Stream.TEXT, text) 90 129 91 130 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)) 94 132 95 133 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)) 97 135 98 136 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)) 100 138 101 139 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 '') 103 141 104 142 def _handle_pi(self, target, data): 105 self. queue.append((Stream.PI, (target, data), self.getpos()))143 self._enqueue(Stream.PI, (target, data)) 106 144 107 145 def _handle_comment(self, text): 108 self. queue.append((Stream.COMMENT, text, self.getpos()))146 self._enqueue(Stream.COMMENT, text) 109 147 110 148 def _handle_other(self, text): … … 113 151 try: 114 152 text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) 115 self. queue.append((Stream.TEXT, text, self.getpos()))153 self._enqueue(Stream.TEXT, text) 116 154 except KeyError: 117 lineno, offset = self. getpos()155 lineno, offset = self._getpos() 118 156 raise expat.error("undefined entity %s: line %d, column %d" % 119 157 (text, lineno, offset)) … … 124 162 125 163 126 class HTMLParser(html.HTMLParser ):164 class HTMLParser(html.HTMLParser, object): 127 165 """Parser for HTML input based on the Python `HTMLParser` module. 128 166 129 167 This class provides the same interface for generating stream events as 130 168 `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 131 180 """ 132 181 … … 135 184 'param']) 136 185 137 def __init__(self, source ):186 def __init__(self, source, filename=None): 138 187 html.HTMLParser.__init__(self) 139 188 self.source = source 140 self.queue = [] 189 self.filename = filename 190 self._queue = [] 141 191 self._open_tags = [] 142 192 143 193 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) 163 228 164 229 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))) 167 237 if tag in self._EMPTY_ELEMS: 168 self. queue.append((Stream.END, QName(tag), pos))238 self._enqueue(Stream.END, QName(tag)) 169 239 else: 170 240 self._open_tags.append(tag) … … 172 242 def handle_endtag(self, tag): 173 243 if tag not in self._EMPTY_ELEMS: 174 pos = self.getpos()175 244 while self._open_tags: 176 245 open_tag = self._open_tags.pop() 177 246 if open_tag.lower() == tag.lower(): 178 247 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)) 181 250 182 251 def handle_data(self, text): 183 self. queue.append((Stream.TEXT, text, self.getpos()))252 self._enqueue(Stream.TEXT, text) 184 253 185 254 def handle_charref(self, name): 186 self. queue.append((Stream.TEXT, Markup('&#%s;' % name), self.getpos()))255 self._enqueue(Stream.TEXT, Markup('&#%s;' % name)) 187 256 188 257 def handle_entityref(self, name): 189 self. queue.append((Stream.TEXT, Markup('&%s;' % name), self.getpos()))258 self._enqueue(Stream.TEXT, Markup('&%s;' % name)) 190 259 191 260 def handle_pi(self, data): 192 261 target, data = data.split(maxsplit=1) 193 262 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())) 196 264 197 265 def handle_comment(self, text): 198 self. queue.append((Stream.COMMENT, text, self.getpos()))266 self._enqueue(Stream.COMMENT, text) 199 267 200 268 -
/trunk/markup/output.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 """This module provides different kinds of serialization methods for XML event … … 22 22 23 23 from markup.core import Markup, Namespace, QName, Stream 24 from markup.filters import WhitespaceFilter25 24 26 25 __all__ = ['Serializer', 'XMLSerializer', 'HTMLSerializer'] … … 31 30 32 31 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 """ 33 38 raise NotImplementedError 34 39 … … 38 43 39 44 >>> 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)) 41 46 >>> print ''.join(XMLSerializer().serialize(elem.generate())) 42 47 <div><a href="foo"/><br/><hr noshade="True"/></div> … … 47 52 ns_mapping = {} 48 53 49 stream = PushbackIterator(stream)54 stream = _PushbackIterator(stream) 50 55 for kind, data, pos in stream: 51 56 … … 82 87 attrname = attr.localname 83 88 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) 89 90 if prefix: 90 91 attrname = prefix + ':' + attrname … … 104 105 tagname = tag.localname 105 106 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 112 110 yield Markup('</%s>' % tagname) 113 111 … … 120 118 121 119 >>> 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)) 123 121 >>> print ''.join(HTMLSerializer().serialize(elem.generate())) 124 122 <div><a href="foo"></a><br><hr noshade></div> … … 137 135 ns_mapping = {} 138 136 139 stream = PushbackIterator(stream)137 stream = _PushbackIterator(stream) 140 138 for kind, data, pos in stream: 141 139 … … 180 178 181 179 182 class PushbackIterator(object):180 class _PushbackIterator(object): 183 181 """A simple wrapper for iterators that allows pushing items back on the 184 182 queue via the `pushback()` method. -
/trunk/markup/path.py
r20 r30 1 1 # -*- coding: utf-8 -*- 2 2 # 3 # Copyright (C) 2006 Edgewall Software3 # Copyright (C) 2006 Christopher Lenz 4 4 # All rights reserved. 5 5 # 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 """Basic support for evaluating XPath expressions against streams.""" … … 20 20 __all__ = ['Path'] 21 21 22 _QUOTES = (("'", "'"), ('"', '"'))23 22 24 23 class Path(object): 25 """ Basic XPath support on markup eventstreams.24 """Implements basic XPath support on streams. 26 25 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. 94 29 """ 95 96 30 _TOKEN_RE = re.compile('(::|\.\.|\(\)|[/.:\[\]\(\)@=!])|' 97 31 '([^/:\[\]\(\)@=!\s]+)|' 98 32 '\s+') 33 _QUOTES = (("'", "'"), ('"', '"')) 99 34 100 35 def __init__(self, text): 36 """Create the path object from a string. 37 38 @param text: the path expression 39 """ 101 40 self.source = text 102 41 … … 113 52 elif op.startswith('('): 114 53 if cur_tag == 'text': 115 steps[-1] = (False, self. fn_text(), [])54 steps[-1] = (False, self._FunctionText(), []) 116 55 else: 117 56 raise NotImplementedError('XPath function "%s" not ' … … 124 63 if cur_op == '@': 125 64 if tag == '*': 126 node_test = self. any_attribute()65 node_test = self._AnyAttribute() 127 66 else: 128 node_test = self. attribute_by_name(tag)67 node_test = self._AttributeByName(tag) 129 68 else: 130 69 if tag == '*': 131 node_test = self. any_element()70 node_test = self._AnyElement() 132 71 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]) 135 74 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) 137 77 steps[-1][2].pop() 138 78 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) 140 81 steps[-1][2].pop() 141 82 else: 142 node_test = self. element_by_name(tag)83 node_test = self._ElementByName(tag) 143 84 if in_predicate: 144 85 steps[-1][2].append(node_test) … … 153 94 154 95 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 """ 155 103 stream = iter(stream) 156 def _generate( tests):104 def _generate(): 157 105 test = self.test() 158 106 for kind, data, pos in stream: … … 171 119 elif result: 172 120 yield result 173 return Stream(_generate( self.steps))121 return Stream(_generate()) 174 122 175 123 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 """ 176 132 stack = [0] # stack of cursors into the location path 177 133 178 134 def _test(kind, data, pos): 179 #print '\nTracker %r test [%s] %r' % (self, kind, data)180 181 135 if not stack: 182 136 return False … … 192 146 closure, node_test, predicates = self.steps[stack[-1]] 193 147 194 #print ' Testing against %r' % node_test195 148 matched = node_test(kind, data, pos) 196 149 if matched and predicates: … … 202 155 if matched: 203 156 if stack[-1] == len(self.steps) - 1: 204 #print ' Last step %r... returned %r' % (node_test, matched)205 157 return matched 206 158 207 #print ' Matched intermediate step %r... proceed to next step %r' % (node_test, self.steps[stack[-1] + 1])208 159 stack[-1] += 1 209 160 210 161 elif kind is Stream.START and not closure: 211 # FIXME: If this step is not a closure, it cannot be matched212 # until the current element is closed... so we need to213 # move the cursor back to the last closure and retest214 # that against the currentelement162 # 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 215 166 closures = [step for step in self.steps[:stack[-1]] if step[0]] 216 167 closures.reverse() … … 227 178 return _test 228 179 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, *_): 231 183 if kind is Stream.START: 232 184 return True … … 235 187 return '<%s>' % self.__class__.__name__ 236 188 237 class element_by_name(object): 189 class _ElementByName(object): 190 """Node test that matches an element with a specific tag name.""" 238 191 def __init__(self, name): 239 192 self.name = QName(name) 240 def __call__(self, kind, data, pos):193 def __call__(self, kind, data, _): 241 194 if kind is Stream.START: 242 195 return data[0].localname == self.name … … 245 198 return '<%s "%s">' % (self.__class__.__name__, self.name) 246 199 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]]) 251 205 if text: 252 206 return Stream.TEXT, text, pos … … 256 210 return '<%s>' % (self.__class__.__name__) 257 211 258 class attribute_by_name(object): 212 class _AttributeByName(object): 213 """Node test that matches an attribute with a specific name.""" 259 214 def __init__(self, name): 260 215 self.name = QName(name) … … 268 223 return '<%s "%s">' % (self.__class__.__name__, self.name) 269 224 270 class fn_text(object): 225 class _FunctionText(object): 226 """Function that returns text content.""" 271 227 def __call__(self, kind, data, pos): 272 228 if kind is Stream.TEXT: … … 276 232 return '<%s>' % (self.__class__.__name__) 277 233 278 class literal_string(object): 234 class _LiteralString(object): 235 """Always returns a literal string.""" 279 236 def __init__(self, value): 280 237 self.value = value 281 def __call__(self, kind, data, pos):238 def __call__(self, *_): 282 239 return Stream.TEXT, self.value, (-1, -1) 283 240 def __repr__(self): 284 241 return '<%s>' % (self.__class__.__name__) 285 242 286 class op_eq(object): 243 class _OperatorEq(object): 244 """Equality comparison operator.""" 287 245 def __init__(self, lval, rval): 288 246 self.lval = lval … … 296 254 self.rval) 297 255 298 class op_neq(object): 256 class _OperatorNeq(object): 257 """Inequality comparison operator.""" 299 258 def __init__(self, lval, rval): 300 259 self.lval = lval -
/trunk/markup/plugin.py
r20 r30 1 1 # -*- coding: utf-8 -*- 2 2 # 3 # Copyright (C) 2006 Mattew Good 3 # Copyright (C) 2006 Matthew Good 4 # Copyright (C) 2006 Christopher Lenz 4 5 # All rights reserved. 5 6 # 6 7 # This software is licensed as described in the file COPYING, which 7 8 # 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. 9 10 # 10 11 # This software consists of voluntary contributions made by many 11 12 # 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 16 CherryPy/Buffet. 17 """ 13 18 14 19 import os … … 19 24 20 25 class TemplateEnginePlugin(object): 26 """Implementation of the plugin API.""" 21 27 22 28 def __init__(self, extra_vars_func=None, options=None): … … 35 41 package = templatename[:divider] 36 42 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) 40 44 41 45 return self.loader.load(templatename) 42 46 43 47 def render(self, info, format='html', fragment=False, template=None): 44 """Render sthe template to a string using the provided info."""48 """Render the template to a string using the provided info.""" 45 49 return self.transform(info, template).render(method=format) 46 50 47 51 def transform(self, info, template): 48 " Render the output to Elements"52 """Render the output to an event stream.""" 49 53 if not isinstance(template, Template): 50 54 template = self.load_template(template) -
/trunk/markup/template.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 """Template engine that is compatible with Kid (http://kid.lesscode.org) to a … … 44 44 import compiler 45 45 import os 46 import posixpath 46 47 import re 47 48 from StringIO import StringIO … … 49 50 from markup.core import Attributes, Namespace, Stream, StreamEventKind 50 51 from markup.eval import Expression 51 from markup.filters import IncludeFilter52 52 from markup.input import HTML, XMLParser, XML 53 53 from markup.path import Path … … 123 123 124 124 def __getitem__(self, key): 125 """Get a variable's value, starting at the current context and going126 upward.125 """Get a variable's value, starting at the current context frame and 126 going upward. 127 127 """ 128 128 return self.get(key) … … 136 136 137 137 def get(self, key): 138 """Get a variable's value, starting at the current context frame and 139 going upward. 140 """ 138 141 for frame in self.stack: 139 142 if key in frame: … … 141 144 142 145 def push(self, **data): 146 """Push a new context frame on the stack.""" 143 147 self.stack.insert(0, data) 144 148 145 149 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 """ 146 154 assert self.stack, 'Pop from empty context stack' 147 155 self.stack.pop(0) … … 168 176 __slots__ = ['expr'] 169 177 170 def __init__(self, template, value, pos):178 def __init__(self, value): 171 179 self.expr = value and Expression(value) or None 172 180 … … 290 298 __slots__ = ['name', 'args', 'defaults', 'stream'] 291 299 292 def __init__(self, template, args, pos):293 Directive.__init__(self, template, None, pos)300 def __init__(self, args): 301 Directive.__init__(self, None) 294 302 ast = compiler.parse(args, 'eval').node 295 303 self.args = [] … … 341 349 __slots__ = ['targets'] 342 350 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) 345 353 self.targets = [str(name.strip()) for name in targets.split(',')] 346 Directive.__init__(self, template, expr_source, pos)354 Directive.__init__(self, value) 347 355 348 356 def __call__(self, stream, ctxt): … … 447 455 __slots__ = ['path', 'stream'] 448 456 449 def __init__(self, template, value, pos):450 Directive.__init__(self, template, None, pos)457 def __init__(self, value): 458 Directive.__init__(self, None) 451 459 self.path = Path(value) 452 460 self.stream = [] … … 575 583 _dir_order = [directive[1] for directive in directives] 576 584 577 def __init__(self, source, filename=None):585 def __init__(self, source, basedir=None, filename=None): 578 586 """Initialize a template from either a string or a file-like object.""" 579 587 if isinstance(source, basestring): … … 581 589 else: 582 590 self.source = source 591 self.basedir = basedir 583 592 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 = [] 586 599 self.parse() 587 600 588 601 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) 591 603 592 604 def parse(self): … … 604 616 depth = 0 605 617 606 for kind, data, pos in XMLParser(self.source ):618 for kind, data, pos in XMLParser(self.source, filename=self.filename): 607 619 608 620 if kind is Stream.START_NS: … … 629 641 cls = self._dir_by_name.get(name.localname) 630 642 if cls is None: 631 raise BadDirectiveError(name, self.filename, pos[ 0])643 raise BadDirectiveError(name, self.filename, pos[1]) 632 644 else: 633 directives.append(cls( self, value, pos))645 directives.append(cls(value)) 634 646 else: 635 647 value = list(self._interpolate(value, *pos)) … … 667 679 _SHORT_EXPR_RE = re.compile(r'(?<!\$)\$([a-zA-Z][a-zA-Z0-9_\.]*)') 668 680 669 def _interpolate(cls, text, lineno=-1, offset=-1):681 def _interpolate(cls, text, filename=None, lineno=-1, offset=-1): 670 682 """Parse the given string and extract expressions. 671 683 … … 689 701 else: 690 702 yield Stream.TEXT, group.replace('$$', '$'), \ 691 ( lineno, offset)703 (filename, lineno, offset) 692 704 return _interpolate(text) 693 705 _interpolate = classmethod(_interpolate) 694 706 695 707 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 """ 697 714 if ctxt is None: 698 715 ctxt = Context() … … 700 717 ctxt._match_templates = [] 701 718 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)) 703 721 704 722 def _eval(self, stream, ctxt=None): 723 """Internal stream filter that evaluates any expressions in `START` and 724 `TEXT` events. 725 """ 705 726 for kind, data, pos in stream: 706 727 … … 723 744 if not value: 724 745 continue 725 new_attrib.append((name, ''.join(value)))746 new_attrib.append((name, u''.join(value))) 726 747 yield kind, (tag, Attributes(new_attrib)), pos 727 748 … … 735 756 # characters 736 757 if isinstance(result, basestring): 737 yield Stream.TEXT, result, pos758 yield Stream.TEXT, unicode(result), pos 738 759 else: 739 760 # Test if the expression evaluated to an iterable, in which … … 749 770 yield kind, data, pos 750 771 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) 755 776 try: 756 777 for kind, data, pos in stream: … … 763 784 for directive in directives: 764 785 substream = directive(iter(substream), ctxt) 786 substream = self._match(self._eval(substream, ctxt), ctxt) 765 787 for event in self._flatten(substream, ctxt): 766 788 yield event … … 769 791 yield kind, data, pos 770 792 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)) 773 795 774 796 def _match(self, stream, ctxt=None): 797 """Internal stream filter that applies any defined match templates 798 to the stream. 799 """ 775 800 for kind, data, pos in stream: 776 801 … … 784 809 if (kind, data, pos) in template[::len(template)]: 785 810 # This is the event this match template produced itself, so 786 # matching it again would result in an infinite loop811 # matching it again would result in an infinite loop 787 812 continue 788 813 … … 805 830 test(*event) 806 831 807 content = list(self._flatten(content, ctxt , apply_filters=False))832 content = list(self._flatten(content, ctxt)) 808 833 809 834 def _apply(stream, ctxt): 810 stream = list(stream)811 835 ctxt.push(select=lambda path: Stream(stream).select(path)) 812 836 for event in template: … … 866 890 self._mtime = {} 867 891 868 def load(self, filename ):892 def load(self, filename, relative_to=None): 869 893 """Load the template with the given name. 870 894 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. 879 911 880 912 @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 881 916 """ 917 if relative_to: 918 filename = posixpath.join(posixpath.dirname(relative_to), filename) 882 919 filename = os.path.normpath(filename) 920 921 # First check the cache to avoid reparsing the same file 883 922 try: 884 923 tmpl = self._cache[filename] 885 924 if not self.auto_reload or \ 886 os.path.getmtime(tmpl.file name) == self._mtime[filename]:925 os.path.getmtime(tmpl.filepath) == self._mtime[filename]: 887 926 return tmpl 888 927 except KeyError: 889 928 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: 891 936 filepath = os.path.join(dirname, filename) 892 937 try: 893 938 fileobj = file(filepath, 'rt') 894 939 try: 895 tmpl = Template(fileobj, filename=filepath) 940 from markup.filters import IncludeFilter 941 tmpl = Template(fileobj, basedir=dirname, filename=filename) 896 942 tmpl.filters.append(IncludeFilter(self)) 897 943 finally: -
/trunk/markup/tests/__init__.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 import doctest -
/trunk/markup/tests/builder.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 import doctest … … 23 23 24 24 def test_link(self): 25 link = tag. A(href='#', title='Foo', accesskey=None)('Bar')25 link = tag.a(href='#', title='Foo', accesskey=None)('Bar') 26 26 bits = iter(link.generate()) 27 27 self.assertEqual((Stream.START, ('a', [('href', "#"), ('title', "Foo")]), -
/trunk/markup/tests/core.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 import doctest 15 from HTMLParser import HTMLParseError16 15 import unittest 17 16 18 17 from markup.core import * 18 from markup.input import ParseError 19 19 20 20 … … 124 124 self.assertEquals('', str(markup.sanitize())) 125 125 markup = Markup('<SCR\0IPT>alert("foo")</SCR\0IPT>') 126 self.assertRaises( HTMLParseError, markup.sanitize().render)126 self.assertRaises(ParseError, markup.sanitize().render) 127 127 markup = Markup('<SCRIPT&XYZ SRC="http://example.com/"></SCRIPT>') 128 self.assertRaises( HTMLParseError, markup.sanitize().render)128 self.assertRaises(ParseError, markup.sanitize().render) 129 129 130 130 def test_sanitize_remove_onclick_attr(self): … … 157 157 # Grave accents (not parsed) 158 158 markup = Markup('<IMG SRC=`javascript:alert("RSnake says, \'foo\'")`>') 159 self.assertRaises( HTMLParseError, markup.sanitize().render)159 self.assertRaises(ParseError, markup.sanitize().render) 160 160 # Protocol encoded using UTF-8 numeric entities 161 161 markup = Markup('<IMG SRC=\'javascri' -
/trunk/markup/tests/eval.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # individuals. For the exact contribution history, see the revision 12 # history and logs, available at h ttp://projects.edgewall.com/trac/.12 # history and logs, available at hhttp://markup.cmlenz.net/log/. 13 13 14 14 import doctest 15 15 import unittest 16 16 17 from markup import eval 18 17 from markup.eval import Expression 19 18 20 19 def suite(): 21 20 suite = unittest.TestSuite() 22 suite.addTest(doctest.DocTestSuite( eval))21 suite.addTest(doctest.DocTestSuite(Expression.__module__)) 23 22 return suite 24 23 -
/trunk/markup/tests/input.py
r20 r30 1 1 # -*- coding: utf-8 -*- 2 2 # 3 # Copyright (C) 2006 Edgewall Software3 # Copyright (C) 2006 Christopher Lenz 4 4 # All rights reserved. 5 5 # 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 import doctest 14 15 import unittest 15 16 … … 18 19 19 20 20 class XMLParserTestCase(unittest.TestCase):21 pass22 23 24 25 21 def suite(): 26 22 suite = unittest.TestSuite() 27 suite.addTest( unittest.makeSuite(XMLParserTestCase, 'test'))23 suite.addTest(doctest.DocTestSuite(XMLParser.__module__)) 28 24 return suite 29 25 -
/trunk/markup/tests/output.py
r20 r30 1 1 # -*- coding: utf-8 -*- 2 2 # 3 # Copyright (C) 2006 Edgewall Software3 # Copyright (C) 2006 Christopher Lenz 4 4 # All rights reserved. 5 5 # 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 import doctest -
/trunk/markup/tests/path.py
r20 r30 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 import doctest 15 15 import unittest 16 16 17 from markup import path 17 from markup.input import XML 18 from markup.path import Path 19 20 21 class 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()) 18 87 19 88 20 89 def suite(): 21 90 suite = unittest.TestSuite() 22 suite.addTest(doctest.DocTestSuite(path)) 91 suite.addTest(doctest.DocTestSuite(Path.__module__)) 92 suite.addTest(unittest.makeSuite(PathTestCase, 'test')) 23 93 return suite 24 94 -
/trunk/markup/tests/template.py
r20 r30 1 1 # -*- coding: utf-8 -*- 2 2 # 3 # Copyright (C) 2006 Edgewall Software3 # Copyright (C) 2006 Christopher Lenz 4 4 # All rights reserved. 5 5 # 6 6 # This software is licensed as described in the file COPYING, which 7 7 # 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. 9 9 # 10 10 # This software consists of voluntary contributions made by many 11 11 # 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 13 14 14 import doctest … … 70 70 xml = '<p xmlns:py="http://purl.org/kid/ns#" py:do="nothing" />' 71 71 try: 72 tmpl = Template(xml, 'test.html')72 tmpl = Template(xml, filename='test.html') 73 73 except BadDirectiveError, e: 74 74 self.assertEqual('test.html', e.filename) … … 78 78 def test_directive_value_syntax_error(self): 79 79 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') 81 81 try: 82 82 list(tmpl.generate(Context())) -
/trunk/setup.py
r20 r30 7 7 # This software is licensed as described in the file COPYING, which 8 8 # 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. 10 10 # 11 11 # This software consists of voluntary contributions made by many 12 12 # 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/. 14 14 15 15 from setuptools import setup, find_packages
Note: See TracChangeset
for help on using the changeset viewer.
