 have been recent releases of two of the Python-XML projects in which I'm involved; 4Suite and Amara XML Toolkit. One common theme in both releases was marked improvements to the XML document creation APIs. These improvements are significant enough to discuss and compare to the other systems for XML output I have presented in this column. The code uses 4Suite version 1.0b1 and Amara 1.0b2, running under Python 2.3.5. Installation is basically the same as in my earlier articles covering these packages: "Three More for XML Output" and "Introducing the Amara XML Toolkit".
New in 4Suite 1.0b1 is the class Ft.Xml.MarkupWriter, which is
specialized for creating XML documents from scratch. It offers at least one
feature I haven't seen in any other output libraries. Listing 1 uses this class
to generate a simple XML Software Autoupdate (XSA) file. XSA is the XML data
format I've been using as a standard example article for XML output. It is
a format for listing and describing software packages.
Ft.Xml.MarkupWriter
from Ft.Xml import MarkupWriter
#Set the output doc type details (required by XSA)
SYSID = u""
PUBID = u"-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML"
writer = MarkupWriter(indent=u"yes", doctypeSystem=SYSID,
doctypePublic=PUBID)
writer.startDocument()
writer.startElement(u'xsa')
writer.startElement(u'vendor')
#Element with simple text (#PCDATA) content
writer.simpleElement(u'name', content=u'Centigrade systems')
writer.simpleElement(u'email', content=u"info@centigrade.bogus")
writer.endElement(u'vendor')
#Element with an attribute
writer.startElement(u'product', attributes={u'id': u"100\u00B0"})
writer.simpleElement(u'name', content=u"100\u00B0 Server")
writer.simpleElement(u'version', content=u"1.0")
writer.simpleElement(u'last-release', content=u"20030401")
#Empty element
writer.simpleElement(u'changes')
writer.endElement(u'product')
writer.endElement(u'xsa')
writer.endDocument()
This illustrates the basics of the API, but there are some advanced features
available. As an example, attributes can be added individually rather than
in dictionary form. As such the line:
writer.startElement(u'product', attributes={u'id': u"100\u00B0"})
Could instead be written
writer.startElement(u'product')
writer.attribute(u'id', u"100\u00B0")
There is also similar flexibility when omitting text. The line:
writer.simpleElement(u'name', content=u'Centigrade systems')
writer.startElement(u'product')
writer.text(u'Centigrade systems')
writer.endElement(u'product')
simpleElement is basically a shortcut for the startElement/endElement combination.
More interestingly, MarkupWriter allows you to insert well-formed XML entities
as complete chunks in the output. This is a very convenient way to omit boilerplate
XML without breaking it down into all the separate element/attribute/content
bits. As such the lines:
simpleElement
startElement
endElement
writer.simpleElement(u'name', content=u"100\u00B0 Server")
writer.simpleElement(u'version', content=u"1.0")
writer.simpleElement(u'last-release', content=u"20030401")
Could instead be written:
writer.xmlFragment(""" <name>100° Server</name>
<version>1.0</version>
<last-release>20030401</last-release>""")
In Listing 1, you can see how parameters that control the output are passed
into the MarkupWriter initializer, including document type info
and whether to indent (pretty print). You can pass any of the usual controls
for XSLT output into the initializer in this way. So for instance omitXmlDeclaration=u"yes" could
be used to suppress output of the XML declaration. By default MarkupWriter
sends its output to sys.stdout, but you can substitute any file-like
object by passing in an initializer parameter. For example:
MarkupWriter
omitXmlDeclaration=u"yes"
sys.stdout
writer = MarkupWriter(output_file, indent=u"yes")
You can also set other parameters, based on those in the XSLT spec:
encoding
omitXmlDeclaration
standalone
mediaType
cdataSectionElements
The XSLT spec also defines a method parameter to choose between
XML, HTML or plain text output rules, but for MarkupWriter at the moment you
should stick to XML. The result of changing the method is undefined. We'll
probably relax this restriction in later releases.
method
The output from Listing 1 is shown in Listing 2. I added new lines to the
document type declaration for formatting reasons. One important thing to keep
in mind is that MarkupWriter produces output incrementally. There is no waiting
for the entire document to be built before sending output.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsa PUBLIC
"-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML"
"">
<xsa>
<vendor>
<name>Centigrade systems</name>
</vendor>
<product id="100°">
<name>100° Server</name>
<version>1.0</version>
<last-release>20030401</last-release>
<changes/>
</product>
</xsa>
Amara's Bindery component now also allows you to create XML documents from
scratch. The interface is not quite as rich as MarkupWriter, but it has some
similarities. Amara's API is probably more suitable if you're writing programs
that have a variety of document reading and update tasks, besides just creating
output. If you really just want to write XML as directly as possible, MarkupWriter
is probably a better bet. Listing 3 uses Amara to generate the same XSA text
as Listing 1.
from amara import binderytools
#Set the output doc type details (required by XSA)
SYSID = u""
PUBID = u"-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML"
#Create a document object with basic details, to be updated with
#Additional elements/attributes/content in subsequent lines
doc = binderytools.create_document(u'xsa', sysid=SYSID, pubid=PUBID)
doc.xsa.xml_append(doc.xml_element(u'vendor'))
#Element with simple text (#PCDATA) content
doc.xsa.vendor.xml_append(
doc.xml_element(u'name', content=u'Centigrade systems'))
doc.xsa.vendor.xml_append(
doc.xml_element(u'email', content=u'info@centigrade.bogus'))
#Element with an attribute
doc.xsa.xml_append(
doc.xml_element(u'product', attributes={u'id': u"100\u00B0"}))
doc.xsa.product.xml_append(
doc.xml_element(u'name', content=u'100\u00B0 Server'))
doc.xsa.product.xml_append(
doc.xml_element(u'version', content=u'1.0'))
doc.xsa.product.xml_append(
doc.xml_element(u'last-release', content=u'20030401'))
doc.xsa.product.xml_append(doc.xml_element(u'changes'))
print doc.xml(indent=u"yes") #Print it
The basic differences in style have to do with what's happening under the covers.
Whereas). In the
Amara example, you also wait until the entire document is built before triggering
the actual output (in this case using print). The xml method
used to generate XML output accepts the same XSLT-based output control parameters
as described above for MarkupWriter.
print
xml
We really turned our jets on this week in the community. I myself kept very
busy with releases of two of the main Python packages I maintain or help to
maintain for XML processing.
First of all comes 4Suite 1.0b1,
the next big step toward the long overdue 1.0 release. Performance is the
main theme of this release, and there have been gains in all the core libraries.
The biggest gains have come where they count the most: in the Domlette library
that forms the basis of much of 4Suite. The Python implementation of Domlette
has been removed, and 4Suite is no longer dependent on pyexpat, which helps
with several platform-specific issues. The MarkupWriter class that I introduced
in this article is also new in this release. There are now BerkeleyDB and MySQL
drivers for RDF and the repository. As usual there are many bug fixes and minor
enhancements. See the announcement.
I also announced Amara
XML Utilities 1.0b2. The main changes are improvements to the mutation
API. The user can now perform common replacement and deletion actions using
familiar Python idioms. See the announcement.
David Mertz announced Gnosis
Utils 1.2.0, which seems to be the culmination of a good deal of work
on the package I've featured a couple of times already in this column. The
announcement says, "This release of the Gnosis Utilities contains several
new modules, as well as fixes, enhancements, and speedups in existing subpackages." One
of the new modules is XML-related: gnosis.xml.xmlmap, "Unicode->XML legality
testing & Unicode helper functions." There are also many updates to gnosis.xml.objectify,
and the fixes I suggested in "Full
XML Indexes with Gnosis " have been incorporated in gnosis.xml.indexer.
See the announcement.
Martijn Faassen announced lxml
0.5.1, "a Pythonic binding for the libxml2 and libxslt libraries." It
largely follows the ElementTree API, extending this to "expose libxml2 and
libxslt specific functionality, such as XPath, Relax NG, XSLT, and c14n".
See the announcement.
Ivan Voras wrote a simple "XML parser" of his own, xmldict. I
thought very hard about even mentioning this code, because it's a pretty dangerous
idea. Ivan says "It's really quick and dirty. It doesn't even use standard
parsers such as dom or sax, but improvises its own. It's also very likely to
fail in mysterious ways when it encounters invalid XML, but for quick and dirty
jobs, it's very nice and easy. See the bottom of the file for some quick examples." It's
OK to use such a package as long as you are crystal clear to yourself
and others that you are not using anything like an XML parser. Think of it
like this: you might use grep to search a directory full of XML files,
but you'd never fool yourself into saying you were doing any XML parsing.
I almost missed an interesting blog
entry by Danny Ayers:
"I just got around to reading Uche Ogbuji’s interesting Wrestling
HTML article at xml.com on parsing HTML with Python. Uche mentions
HTML to XML, something the rough SAX2-styled HTML soup/ill-formed XML parser
code I did a while ago can do pretty easily (not well, but easily). So
I’ve added a quick handler to demo that (there’s already an
RSS/Atom feed reading demo in there) and here it is: psoup_2004-09-13.zip. "
"It’s not been tidied up like JSoup (Java
version, still hacky!), but it still might make a useful starting point for
someone. "
And speaking of blogs, I have established a blog of my own, Copia,
(with my brother and fellow XML.com author Chimezie
Ogbuji). Starting this week, I'll be posting brief links to selected Python-XML
software announcements there. Of course I'll continue to provide the monthly
summary in this space.
© , O’Reilly Media, Inc.
(707) 827-7019
(800) 889-8969
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.