cmlenz@1: # -*- coding: utf-8 -*- cmlenz@1: # cmlenz@854: # Copyright (C) 2006-2009 Edgewall Software cmlenz@1: # All rights reserved. cmlenz@1: # cmlenz@1: # This software is licensed as described in the file COPYING, which cmlenz@1: # you should have received as part of this distribution. The terms cmlenz@230: # are also available at http://genshi.edgewall.org/wiki/License. cmlenz@1: # cmlenz@1: # This software consists of voluntary contributions made by many cmlenz@1: # individuals. For the exact contribution history, see the revision cmlenz@230: # history and logs, available at http://genshi.edgewall.org/log/. cmlenz@1: cmlenz@430: """Support for programmatically generating markup streams from Python code using cmlenz@430: a very simple syntax. The main entry point to this module is the `tag` object cmlenz@430: (which is actually an instance of the ``ElementFactory`` class). You should cmlenz@430: rarely (if ever) need to directly import and use any of the other classes in cmlenz@430: this module. cmlenz@430: cmlenz@430: Elements can be created using the `tag` object using attribute access. For cmlenz@430: example: cmlenz@430: cmlenz@430: >>> doc = tag.p('Some text and ', tag.a('a link', href='http://example.org/'), '.') cmlenz@430: >>> doc cmlenz@430: cmlenz@430: cmlenz@430: This produces an `Element` instance which can be further modified to add child cmlenz@430: nodes and attributes. This is done by "calling" the element: positional cmlenz@430: arguments are added as child nodes (alternatively, the `Element.append` method cmlenz@430: can be used for that purpose), whereas keywords arguments are added as cmlenz@430: attributes: cmlenz@430: cmlenz@430: >>> doc(tag.br) cmlenz@430: cmlenz@853: >>> print(doc) cmlenz@430:

Some text and a link.

cmlenz@430: cmlenz@430: If an attribute name collides with a Python keyword, simply append an underscore cmlenz@430: to the name: cmlenz@430: cmlenz@430: >>> doc(class_='intro') cmlenz@430: cmlenz@853: >>> print(doc) cmlenz@430:

Some text and a link.

cmlenz@430: cmlenz@430: As shown above, an `Element` can easily be directly rendered to XML text by cmlenz@430: printing it or using the Python ``str()`` function. This is basically a cmlenz@430: shortcut for converting the `Element` to a stream and serializing that cmlenz@430: stream: cmlenz@430: cmlenz@430: >>> stream = doc.generate() cmlenz@431: >>> stream #doctest: +ELLIPSIS cmlenz@430: cmlenz@853: >>> print(stream) cmlenz@430:

Some text and a link.

cmlenz@430: cmlenz@430: cmlenz@430: The `tag` object also allows creating "fragments", which are basically lists cmlenz@430: of nodes (elements or text) that don't have a parent element. This can be useful cmlenz@430: for creating snippets of markup that are attached to a parent element later (for cmlenz@430: example in a template). Fragments are created by calling the `tag` object, which cmlenz@430: returns an object of type `Fragment`: cmlenz@430: cmlenz@430: >>> fragment = tag('Hello, ', tag.em('world'), '!') cmlenz@430: >>> fragment cmlenz@430: cmlenz@853: >>> print(fragment) cmlenz@430: Hello, world! cmlenz@430: """ cmlenz@425: cmlenz@737: from genshi.core import Attrs, Markup, Namespace, QName, Stream, \ cmlenz@737: START, END, TEXT cmlenz@1: cmlenz@433: __all__ = ['Fragment', 'Element', 'ElementFactory', 'tag'] cmlenz@425: __docformat__ = 'restructuredtext en' cmlenz@1: cmlenz@1: cmlenz@1: class Fragment(object): cmlenz@28: """Represents a markup fragment, which is basically just a list of element cmlenz@28: or text nodes. cmlenz@28: """ cmlenz@1: __slots__ = ['children'] cmlenz@1: cmlenz@1: def __init__(self): cmlenz@433: """Create a new fragment.""" cmlenz@1: self.children = [] cmlenz@1: cmlenz@65: def __add__(self, other): cmlenz@65: return Fragment()(self, other) cmlenz@65: cmlenz@65: def __call__(self, *args): cmlenz@433: """Append any positional arguments as child nodes. cmlenz@433: cmlenz@433: :see: `append` cmlenz@433: """ cmlenz@854: for arg in args: cmlenz@854: self.append(arg) cmlenz@65: return self cmlenz@65: cmlenz@65: def __iter__(self): cmlenz@98: return self._generate() cmlenz@65: cmlenz@65: def __repr__(self): cmlenz@860: return '<%s>' % type(self).__name__ cmlenz@65: cmlenz@65: def __str__(self): cmlenz@65: return str(self.generate()) cmlenz@65: cmlenz@65: def __unicode__(self): cmlenz@65: return unicode(self.generate()) cmlenz@65: cmlenz@737: def __html__(self): cmlenz@737: return Markup(self.generate()) cmlenz@737: cmlenz@1: def append(self, node): cmlenz@433: """Append an element or string as child node. cmlenz@433: cmlenz@433: :param node: the node to append; can be an `Element`, `Fragment`, or a cmlenz@433: `Stream`, or a Python string or number cmlenz@433: """ cmlenz@379: if isinstance(node, (Stream, Element, basestring, int, float, long)): cmlenz@1: # For objects of a known/primitive type, we avoid the check for cmlenz@1: # whether it is iterable for better performance cmlenz@1: self.children.append(node) cmlenz@1: elif isinstance(node, Fragment): cmlenz@133: self.children.extend(node.children) cmlenz@1: elif node is not None: cmlenz@1: try: cmlenz@854: for child in node: cmlenz@854: self.append(child) cmlenz@1: except TypeError: cmlenz@1: self.children.append(node) cmlenz@94: cmlenz@94: def _generate(self): cmlenz@94: for child in self.children: cmlenz@94: if isinstance(child, Fragment): cmlenz@94: for event in child._generate(): cmlenz@94: yield event cmlenz@379: elif isinstance(child, Stream): cmlenz@379: for event in child: cmlenz@379: yield event cmlenz@94: else: cmlenz@94: if not isinstance(child, basestring): cmlenz@94: child = unicode(child) cmlenz@133: yield TEXT, child, (None, -1, -1) cmlenz@1: cmlenz@1: def generate(self): cmlenz@498: """Return a markup event stream for the fragment. cmlenz@498: cmlenz@498: :rtype: `Stream` cmlenz@498: """ cmlenz@94: return Stream(self._generate()) cmlenz@1: cmlenz@1: cmlenz@345: def _kwargs_to_attrs(kwargs): cmlenz@735: attrs = [] cmlenz@735: names = set() cmlenz@730: for name, value in kwargs.items(): cmlenz@730: name = name.rstrip('_').replace('_', '-') cmlenz@735: if value is not None and name not in names: cmlenz@735: attrs.append((QName(name), unicode(value))) cmlenz@735: names.add(name) cmlenz@735: return Attrs(attrs) cmlenz@345: cmlenz@345: cmlenz@1: class Element(Fragment): cmlenz@1: """Simple XML output generator based on the builder pattern. cmlenz@1: cmlenz@1: Construct XML elements by passing the tag name to the constructor: cmlenz@1: cmlenz@853: >>> print(Element('strong')) cmlenz@1: cmlenz@1: cmlenz@1: Attributes can be specified using keyword arguments. The values of the cmlenz@1: arguments will be converted to strings and any special XML characters cmlenz@1: escaped: cmlenz@1: cmlenz@853: >>> print(Element('textarea', rows=10, cols=60)) cmlenz@1: