Source code for pyx12.xmlwriter
# A simple XML-generator# Originally Lars Marius Garshol, September 1998
# http://mail.python.org/pipermail/xml-sig/1998-September/000347.html
# Changes by Uche Ogbuji April 2003
# * unicode support: accept encoding argument and use Python codecs
# for correct character output
# * switch from deprecated string module to string methods
# * use PEP 8 style
from __future__ import annotations
import sys
from typing import TextIO
[docs]
class XMLWriter:
"""
Doctest:
>>>from xmlwriter import XMLWriter
>>>writer = XMLWriter()
>>>writer.doctype(
... u"xsa", u"-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML",
... u"https://www.garshol.priv.no/download/xsa/xsa.dtd")
>>>#Notice: there is no error checking to ensure that the root element
>>>#specified in the doctype matches the top-level element generated
>>>writer.push(u"xsa")
>>>#Another element with child elements
>>>writer.push(u"vendor")
>>>#Element with simple text (#PCDATA) content
>>>writer.elem(u"name", u"Centigrade systems")
>>>writer.elem(u"email", u"info@centigrade.bogus")
>>>writer.elem(u"vendor", u"Centigrade systems")
>>>#Close currently open element ("vendor)
>>>writer.pop()
>>>#Element with an attribute
>>>writer.push(u"product", {u"id": u"100\\u00B0"})
>>>writer.elem(u"name", u"100\\u00B0 Server")
>>>writer.elem(u"version", u"1.0")
>>>writer.elem(u"last-release", u"20030401")
>>>#Empty element
>>>writer.empty(u"changes")
>>>writer.pop()
>>>writer.pop()
startElement
endElement
emptyElement
text, data
endDocument
attribute
indentation
close
flush
"""
def __init__(
self, out: TextIO = sys.stdout, encoding: str = "utf-8", indent: str = " "
) -> None:
"""
out - a stream for the output
encoding - an encoding used to wrap the output for unicode
indent - white space used for indentation
"""
# wrapper = codecs.lookup(encoding).streamwriter
# self.out = wrapper(out)
self.encoding = encoding
self.out = out
self.stack = []
self.indent = indent
self._write(f'<?xml version="1.0" encoding="{encoding}"?>\n')
[docs]
def doctype(self, root: str, pubid: str | None, sysid: str) -> None:
"""
Create a document type declaration (no internal subset)
"""
if pubid is None:
self._write(f"<!DOCTYPE {root} SYSTEM '{sysid}'>\n")
else:
self._write(f"<!DOCTYPE {root} PUBLIC '{pubid}' '{sysid}'>\n")
[docs]
def push(self, elem: str, attrs: dict[str, str] | None = None) -> None:
"""
Create an element which will have child elements
"""
if attrs is None:
attrs = {}
self._indent()
self._write("<" + elem)
for a, v in attrs.items():
self._write(f" {a}='{self._escape_attr(v)}'")
self._write(">\n")
self.stack.append(elem)
[docs]
def elem(self, elem: str, content: str | None, attrs: dict[str, str] | None = None) -> None:
"""
Create an element with text content only
"""
if attrs is None:
attrs = {}
self._indent()
self._write("<" + elem)
for a, v in attrs.items():
self._write(f" {a}='{self._escape_attr(v)}'")
self._write(f">{self._escape_cont(content)}</{elem}>\n")
[docs]
def empty(self, elem: str, attrs: dict[str, str] | None = None) -> None:
"""
Create an empty element
"""
if attrs is None:
attrs = {}
self._indent()
self._write("<" + elem)
for a, v in attrs.items():
self._write(f" {a}='{self._escape_attr(v)}'")
self._write("/>\n")
[docs]
def pop(self) -> None:
"""
Close an element started with the push() method
"""
if len(self.stack) > 0:
elem = self.stack[-1]
del self.stack[-1]
self._indent()
self._write(f"</{elem}>\n")
def __len__(self) -> int:
return len(self.stack)
def _indent(self) -> None:
self._write(self.indent * (len(self.stack) * 2))
def _escape_cont(self, text: str | None) -> str | None:
if text is None:
return None
return text.replace("&", "&").replace("<", "<").replace(">", ">")
def _escape_attr(self, text: str | None) -> str | None:
if text is None:
return None
return (
text.replace("&", "&")
.replace("'", "'")
.replace("<", "<")
.replace(">", ">")
)
def _write(self, strval: str) -> None:
# self.out.write(strval.encode(self.encoding))
self.out.write(strval)