System.Xml.XmlWriter
inside a Python's 'with' statement using IronPython.Writing Xml
While reading some code examples from the XIST HTML/XML generation library I noticed the nice use of Python's
'with'
statement to represent the target HTML or XML. The System.Xml.XmlWriter class provided with .NET already gives you a way to write well formed Xml documents. In this post I'm going to show how to use an XmlWriter instance in conjunction with Python's 'with' statement.
We want to write the following code:
from __future__ import with_statement
...
w = XmlWriter.Create(System.Console.Out,XmlWriterSettings(Indent=True))
x = XWriter(w)
with x.element('tableofcontents'):
with x.element('section',{'page' : '10'}):
x.text('Introduction')
with x.element('section',{'page' : '12'}):
x.text('Main topic')
with x.element('section',{'page' : '14'}):
x.text('Extra topic')
To generate the following Xml file:
<tableofcontents>
<section page="10">Introduction</section>
<section page="12">Main topic</section>
<section page="14">Extra topic</section>
</tableofcontents>
The 'with' statement
The 'with' statement was introduced in Python 2.5 . This statement is used wrap the execution of a series of statements with some special code. For example it is used to implement the
try...except...finally
pattern. As described in the documentation the following statement:
with context expression:
statements...
Will be executed as follows:
- Evaluate the context expression to obtain the context manager
- Invoke the context manager's
__enter__()
method - Execute the statements
- When the execution of the statements finishes(even with an exception), the context manager's
__exit()__
method is called.
Given these steps we're going to implement a context manager that assist in the creation of Xml documents using the
System.Xml.XmlWriter
.NET class.The following code shows a class that wraps the XmlWriter instance and helps with the creation of context managers:
class XWriter(object):
def __init__(self,writer):
self.writer = writer
def element(self,name,atts = {}):
return ElementCtxt(name,atts,self)
def nselement(self,prefix,name,namespace,atts = {}):
return NamespaceElementCtxt(prefix,name,namespace,atts,self)
def text(self,text):
self.writer.WriteString(text)
def cdata(self,text):
self.writer.WriteCData(text)
Notice that the
element
method creates an instance of the ElementCtxt
class using the element name and an optional dictionary with the attributes. As the following listing shows this class performs the calls to WriteStartElement and WriteEndElement in the __enter__
and __exit__
methods.
class ElementCtxt(object):
def __init__(self,elementName,atts,writer):
self.elementName = elementName
self.atts = atts
self.writer = writer
def processAttributes(self):
for att in self.atts:
self.writer.writer.WriteAttributeString(att,self.atts[att].__str__())
def processStartTag(self):
self.writer.writer.WriteStartElement(self.elementName)
self.processAttributes()
def __enter__(self):
self.processStartTag()
return self
def __exit__(self,t,v,tr):
self.writer.writer.WriteEndElement()
return t == None
The
XWriter.nselement
method is used to write elements with namespace and prefix. This call generates an instance of the following context manager:
class NamespaceElementCtxt(ElementCtxt):
def __init__(self,prefix,elementName,namespace,atts,writer):
ElementCtxt.__init__(self,elementName,atts,writer)
self.namespace = namespace
self.prefix = prefix
def processStartTag(self):
self.writer.writer.WriteStartElement(self.prefix,self.elementName,self.namespace)
self.processAttributes()
Final example
The following code shows how to create a little SVG file:
from __future__ import with_statement
from xmlwriterw import XWriter
import clr
clr.AddReference('System.Xml')
from System.Xml import *
import System
w = XmlWriter.Create(System.Console.Out,\
XmlWriterSettings(Indent=True))
x = XWriter(w)
svgNs = 'http://www.w3.org/2000/svg'
with x.nselement('s','svg',svgNs,{'version': '1.1',
'viewBox': '0 0 100 100',
'style':'width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;'}):
with x.nselement('s','linearGradient',svgNs, { 'id' : 'gradient' }):
with x.nselement('s','stop',svgNs, {'class' : 'begin',
'offset' : '0%',
'stop-color':'red'}):
pass
with x.nselement('s','stop',svgNs, {'class' : 'end',
'offset' : '100%'}):
pass
with x.nselement('s','rect',svgNs, { 'x':0,
'y':0,
'width':100,
'height':100,
'style':'fill:url(#gradient)'} ):
pass
for i in range(1,5):
with x.nselement('s','circle',svgNs, { 'cx': 50,
'cy': 50,
'r': 30 - i*3,
'style':'fill:url(#gradient)'} ):
pass
w.Close()
Running this program shows:
<s:svg viewBox="0 0 100 100" style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;" version="1.1" xmlns:s="http://www.w3.org/2000/svg">
<s:linearGradient id="gradient">
<s:stop offset="0%" class="begin" stop-color="red" />
<s:stop offset="100%" class="end" />
</s:linearGradient>
<s:rect x="0" height="100" width="100" style="fill:url(#gradient)" y="0" />
<s:circle cx="50" cy="50" style="fill:url(#gradient)" r="27" />
<s:circle cx="50" cy="50" style="fill:url(#gradient)" r="24" />
<s:circle cx="50" cy="50" style="fill:url(#gradient)" r="21" />
<s:circle cx="50" cy="50" style="fill:url(#gradient)" r="18" />
</s:svg>