使用Unicode和命名空间在XML中生成大量输出
项目描述
洛迅是一个Python模块,用于使用Unicode和命名空间在XML中生成大量输出。当然,您也可以使用它生成带有纯8位字符串和没有命名空间的较小XML输出。
洛迅的特点包括
小内存占用:文档通过写入输出流即时创建,无需将其全部保存在内存中。
易于使用命名空间:只需添加一个命名空间,然后使用标准的 namespace:tag 语法引用它。
混合Unicode和io.BytesIO:将Unicode或纯8位字符串传递给任何方法。洛迅内部将它们转换为Unicode,因此一旦参数被API接受,您就可以依赖它不会引起任何混乱的 UnicodeError 问题。
自动转义:在编写文本和属性值时,无需手动处理特殊字符,如 < 或 &。
健壮性:在编写文档时,会对您的所有操作进行合理性检查。许多愚蠢的错误会导致立即抛出 XmlError,例如缺少结束标签或引用未声明的命名空间。
开源:遵循 GNU Lesser General Public License 3 或更高版本进行分发。
以下是一个非常基本的示例。首先,您需要创建一个输出流。在许多情况下,这将是文件,但为了简化,我们在这里使用 io.BytesIO。
>>> from __future__ import unicode_literals >>> import io >>> out = io.BytesIO()
然后,您可以创建一个 XmlWriter 来写入此输出。
>>> xml = XmlWriter(out)
现在写入内容。
>>> xml.addNamespace("xhtml", "http://www.w3.org/1999/xhtml") >>> xml.startTag("xhtml:html") >>> xml.startTag("xhtml:body") >>> xml.text("Hello world!") >>> xml.tag("xhtml:img", {"src": "smile.png", "alt": ":-)"}) >>> xml.endTag() >>> xml.endTag() >>> xml.close()
结果是
>>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?> <xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml"> <xhtml:body> Hello world! <xhtml:img alt=":-)" src="smile.png" /> </xhtml:body> </xhtml:html>
编写简单文档
以下示例创建一个非常简单的 XHTML 文档。
为了简化,输出将发送到 BytesIO,但您也可以使用使用 io.open(filename, "wb") 创建的二进制文件。
>>> from __future__ import unicode_literals >>> import io >>> out = io.BytesIO()
首先创建一个 XmlWriter 来将 XML 代码写入指定的输出。
>>> xml = XmlWriter(out)
这将自动添加 XML 前置声明。
>>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?>
接下来添加 <html> 开始标签。
>>> xml.startTag("html")
现在来 <body>。要传递属性,请在字典中指定它们。所以为了添加
<body id="top">
使用
>>> xml.startTag("body", {"id": "top"})
让我们添加一些文本,以便有东西可以查看。
>>> xml.text("Hello world!")
总结:关闭所有元素和文档。
>>> xml.endTag() >>> xml.endTag() >>> xml.close()
我们得到的结果是
>>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?> <html> <body id="top"> Hello world! </body> </html>
指定属性
首先创建一个编写器
>>> import io >>> out = io.BytesIO() >>> xml = XmlWriter(out)
现在写入内容。
>>> xml.tag("img", {"src": "smile.png", "alt": ":-)"})
属性值不必是字符串,其他类型将使用 Python 的 unicode() 函数转换为 Unicode。
>>> xml.tag("img", {"src": "wink.png", "alt": ";-)", "width": 32, "height": 24})
结果是
>>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?> <img alt=":-)" src="smile.png" /> <img alt=";-)" height="24" src="wink.png" width="32" />
使用命名空间
现在进行与上面相同的事情,但使用命名空间。首先创建前置声明和标题,如上所述。
>>> out = io.BytesIO() >>> xml = XmlWriter(out)
接下来添加命名空间。
>>> xml.addNamespace("xhtml", "http://www.w3.org/1999/xhtml")
现在元素可以使用有资格的标签名,使用冒号 (:) 分隔命名空间和标签名。
>>> xml.startTag("xhtml:html") >>> xml.startTag("xhtml:body") >>> xml.text("Hello world!") >>> xml.endTag() >>> xml.endTag() >>> xml.close()
结果,标签名现在以前缀“xhtml:”开头。
>>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?> <xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml"> <xhtml:body> Hello world! </xhtml:body> </xhtml:html>
处理非 ASCII 字符
有时您想使用 ASCII 范围之外的字符,例如德语的变音符号、欧元符号或日文的汉字。最简单且性能最优的方法是使用 Unicode 字符串。例如
>>> import io >>> out = io.BytesIO() >>> xml = XmlWriter(out, prolog=False) >>> xml.text(u"The price is \u20ac 100") # Unicode of Euro symbol >>> out.getvalue().rstrip("\r\n") 'The price is \xe2\x82\xac 100'
注意传递给 XmlWriter.text() 的字符串前面的“u”,它声明该字符串是一个可以包含任何字符的 Unicode 字符串,甚至包括 8 位范围之外的字符。
请注意,在输出中,欧元符号看起来与输入非常不同。这是因为输出编码是 UTF-8(默认值),它具有优点是保持所有 ASCII 字符不变,并将任何具有 128 或更高代码的字符转换为适合二进制文件或 io.BytesIO 输出流的 8 位字节序列。
如果您必须坚持经典的 8 位字符串参数,Loxun 尝试将它们转换为 Unicode。默认情况下,它假定 ASCII 编码,这在使用范围之外的字符时不会奏效。
>>> import io >>> out = io.BytesIO() >>> xml = XmlWriter(out, prolog=False) >>> xml.text("The price is \xa4 100") # ISO-8859-15 code of Euro symbol Traceback (most recent call last): ... UnicodeDecodeError: 'ascii' codec can't decode byte 0xa4 in position 13: ordinal not in range(128)
在这种情况下,您必须通过指定 sourceEncoding 来告诉编写器您使用的编码。
>>> import io >>> out = io.BytesIO() >>> xml = XmlWriter(out, prolog=False, sourceEncoding="iso-8859-15")
现在一切又都正常了。
>>> xml.text("The price is \xa4 100") # ISO-8859-15 code of Euro symbol >>> out.getvalue().rstrip("\r\n") 'The price is \xe2\x82\xac 100'
当然,在实际操作中,您不会用十六进制代码传递文本。相反,您只需使用 PEP 263 中描述的机制指定源编码。
格式化和缩进
默认情况下,Loxun 会为每个 startTag 开启新行,并用两个空格缩进内容。您可以将其更改为任意数量的空格或制表符。
>>> out = io.BytesIO() >>> xml = XmlWriter(out, indent=" ") # <-- Indent with 4 spaces. >>> xml.startTag("html") >>> xml.startTag("body") >>> xml.text("Hello world!") >>> xml.endTag() >>> xml.endTag() >>> xml.close() >>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?> <html> <body> Hello world! </body> </html>
您可以使用 pretty=False 禁用格式化输出,结果将是一个长行输出。
>>> out = io.BytesIO() >>> xml = XmlWriter(out, pretty=False) # <-- Disable pretty printing. >>> xml.startTag("html") >>> xml.startTag("body") >>> xml.text("Hello world!") >>> xml.endTag() >>> xml.endTag() >>> xml.close() >>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?><html><body>Hello world!</body></html>
修改 XML 前置声明
当创建一个写入器时,它会自动将一个 XML 前置处理指令写入输出。这是默认前置声明的样子。
>>> import io >>> out = io.BytesIO() >>> xml = XmlWriter(out) >>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?>
您可以更改版本或编码。
>>> out = io.BytesIO() >>> xml = XmlWriter(out, encoding=u"ascii", version=u"1.1") >>> print out.getvalue().rstrip("\r\n") <?xml version="1.1" encoding="ascii"?>
要完全省略前置声明,请设置参数 prolog=False。
>>> out = io.BytesIO() >>> xml = XmlWriter(out, prolog=False) >>> out.getvalue() ''
添加其他内容
除了文本和标签外,XML 还提供了一些可以添加到文档中的其他内容。以下是一个使用 Loxun 实现此操作的示例。
首先,创建一个写入器。
>>> import io >>> out = io.BytesIO() >>> xml = XmlWriter(out)
让我们添加一个文档类型定义。
>>> xml.raw("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" SYSTEM \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">") >>> xml.newline()
请注意,Loxun 使用通用的 XmlWriter.raw() 来完成此操作,这允许添加任何内容而无需验证或转义。您可以使用 raw() 执行各种可能导致无效 XML 的操作,但这是一种合理的用途。
接下来,让我们添加一条注释。
>>> xml.comment("Show case some rarely used XML constructs")
这是一个处理指令。
>>> xml.processingInstruction("xml-stylesheet", "href=\"default.css\" type=\"text/css\"")
最后是一个 CDATA 部分。
>>> xml.cdata(">> this will not be parsed <<")
结果是
>>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!-- Show case some rarely used XML constructs --> <?xml-stylesheet href="default.css" type="text/css"?> <![CDATA[>> this will not be parsed <<]]>
优化
Loxun 会自动优化成对的空起始/结束标签。例如
>>> out = io.BytesIO() >>> xml = XmlWriter(out) >>> xml.startTag("customers") >>> xml.startTag("person", {"id": "12345", "name": "Doe, John"}) >>> xml.endTag("person") # without optimization, this would add </person>. >>> xml.endTag() >>> xml.close() >>> print out.getvalue().rstrip("\r\n") <?xml version="1.0" encoding="utf-8"?> <customers> <person id="12345" name="Doe, John" /> </customers>
尽管有显式的 startTag("person") 和匹配的 endtag(),输出中仅包含简单的 <person ... /> 标签。
贡献
如果您想帮助改进 Loxun,您可以在 http://github.com/roskakori/loxun 处访问源代码。
未来
目前,Loxun 正在执行其构建的目的。
目前没有计划在近期内改进它,但以下是一些可能在某个时候添加的功能列表
添加对标签和属性名称的验证,以确保所有使用的字符都是允许的。例如,目前 Loxun 对名为“a#b*c$d_”的标签不进行抱怨。
当使用属性而不是 XmlWriter.addNamespace() 添加命名空间时,引发 XmlError。
添加日志支持以简化调用代码的调试。可能 XmlWriter 将获得一个属性 logger,它是一个标准的 logging.Logger。默认情况下,它可以记录 Loxun 转换为 XmlError 的原始异常以及打开和关闭的命名空间。将日志级别更改为 logging.DEBUG 将记录每个标签和 XML 构造,包括有关内部标签堆栈的附加信息。这样,您可以动态地增加或减少日志输出。
重新考虑格式化输出。而不是只能在使用 XmlWriter 初始化时设置的全球属性,它可以是 XmlWriter.startTag() 的可选参数,可以根据需要打开和关闭。属性名称可以是 literal 而不是 pretty(具有相反的逻辑)。
添加一个 DomWriter,用于创建 xml.dom.minidom.Document。
一些其他 XML 库支持的特性,但我从未看到过任何真正的用途
指定标签的属性顺序。
版本历史
版本 2.0,2014-07-28
添加了对 Python 3.2+ 的支持,同时保留了使用 Python 2.6+ 运行的选项(问题 #5;感谢 Stefan Schwarzer 在 EuroPython 2014 的“Python 2 到 3”冲刺期间提供的指导)。
已取消对 Python 2.5 的支持,如果您被困在这个版本中,请继续使用 Loxun 1.3。
版本 1.3,2012-01-01
添加了 endTags() 以关闭一个或多个打开的标签(问题 #3,由 Anton Kolechkin 贡献)。
添加了与XmlWriter类似的ChainXmlWriter,允许链式调用方法,从而编写更简洁的源代码(问题编号 #3,由Anton Kolechkin 贡献)。
版本 1.2,2011-03-12
修复了当设置XmlWriter(..., encoding=...)时出现的AttributeError。
版本 1.1,08-Jan-2011
修复了当将pretty设置为False时出现的AssertionError(问题编号 #1;由David Cramer修复)。
版本 1.0,11-Oct-2010
添加了对Python的with语句的支持,因此您不再需要手动调用XmlWriter.close()。
在<http://github.com/roskakori/loxun>添加了Git仓库。
版本 0.8,11-Jul-2010
添加了将属性传递给XmlWriter.startTag()和XmlWriter.tag()的功能,其值可以是除了str或unicode之外的其他类型。当写入XML时,使用Python的内置unicode()函数进行转换。
添加了从发行版中缺失的一些文件,最重要的是测试套件。
版本 0.7,03-Jul-2010
添加了对匹配不带内容的中间标签的优化。例如,x.startTag("some"); x.endTag()的结果是<some />而不是<some></some>。
- 修复了对未知名称空间的处理。现在它们会引发一个XmlError而不是ValueError。
。
版本 0.6,03-Jun-2010
添加了indent选项,用于指定每行开始的缩进文本。
添加了newline选项,用于指定如何结束写入的行。
修复了XmlWriter.tag()没有删除立即在其之前声明的名称空间的问题。
整理了文档。
版本 0.5,25-May-2010
修复了名称空间属性名称中的拼写错误。
修复了在调用XmlWriter.tag()之前添加名称空间,导致出现XmlError的问题。
版本 0.4,21-May-2010
添加了sourceEncoding选项,简化了经典字符串的处理。手动部分“处理非ASCII字符”解释了如何使用它。
版本 0.3,17-May-2010
添加了作用域名称空间,它们由XmlWriter.endTag()自动删除。
将text()更改为在启用美化打印时规范化换行符和空白。
将XML声明写入构造函数,并删除了XmlWriter.prolog()。要省略声明,在创建XmlWriter时指定prolog=False。如果您以后想自己写入声明,请使用XmlWriter.processingInstruction()。
将*Element()重命名为*Tag,因为它们实际上只写入标签,而不是整个元素。
版本 0.2,16-May-2010
添加了XmlWriter.comment()、XmlWriter.cdata()和XmlWriter.processingInstruction(),用于写入这些特定的XML结构。
添加了缩进和自动换行,如果启用美化打印,则应用于文本。
如果禁用美化打印,则从声明中删除换行。
修复了声明中缺失的“?”。
版本 0.1,15-May-2010
首次发布。