万能的XML(1):初次实现

之前提到过XML,现在该更详细的讨论它了。在这个项目中,你将看到XML可用来表示各种类型的数据,以及如何使用Simple API for XML(SAX)来处理XML文件。这个项目的目标是,根据描述各种网页和目录的单个XML文件生成完整的网站。

现在假设你知道XML是什么以及如何编写。如果你对HTML有些了解,就已经熟悉了这些基本知识。不像HTML那样是一种特定的语言,XML是一组定义一类语言的规则。大致而言,你依然可以像使用HTML那样编写标签,但在XML中,还可以自定义标签名。这些标签名及其结构关系可使用文档类型定义(document type definition)或XML架构(XML Schema)来描述,但这里不讨论这些。

有关XML的简洁描述,请参阅万维网联盟(W3C)网站的文章“XML in 10 points”(http://www.w3.org/XML/1999/XML-in-10-points-19990327)。有关XML的详尽教程,请参阅W3Schools网站(http://www.w3schools.com/xml)。有关SAX的详细信息,请参阅SAX官网(http://www.saxproject.org)。

1.问题描述

在这个项目中,要解决的通用问题是解析(读取并处理)XML文件。鉴于XML几乎可用来表示任何信息,而你可对其中的数据做任何处理,因此正如标题指出的,今天介绍的技巧拥有非常广泛的用途。今天要解决的具体问题是,根据一个XML文件生成完整的网站,而这个文件描述了网站的结构以及每个网页的基本内容。

着手处理这个项目前,建议你花点时间了解XML及其用途。这样你可能有更深入的认识,知道在什么情况下使用这种格式很有用,什么情况下使用它犹如大炮打蚊子。(毕竟,有时候用纯文本文件足够了)。

XML可用来表示任何信息

你可能对此持怀疑态度,下面来看几个有关其用途的示例。

  • 标记文本文件以便进行普通的文档处理,如XHTML(http://www.w3.org/TR/xhtml1)或DocBook(http://www.docbook.org)。
  • 表示音乐(http://musicxml.org)。
  • 表示人的心情、情感和性格特征(http://xml.coverpages.org/humanML.html)。
  • 描述任何物体(http://xml.coverpages.org/pml-ons.html)。
  • 通过网络调用Python方法(使用XML-RPC)。

XML Cover Pages(http://xml.coverpages.org/xml.html#applications)提供了一些现有的XML应用示例。


下面来确定这个项目的具体目标。

  • 整个网站由单个XML描述,该文件包含有关各个网页和目录的信息。
  • 程序应根据需要创建目录和网页。
  • 应能够轻松地修改整个网站的设计并根据新的设计重新生成所有网页。

仅考虑到最后一点,就职的创建这样的XML文件了,但还有其他的好处。通过将所有的内容放在一个XML文件中,可轻松地编写其他程序,以使用同样的XML处理技术来提取各种信息,如目录和供自定义搜索引擎使用的索引等。另外,就算不用来创建网站,也可使用这种文件来创建基于HTML的幻灯片或PDF幻灯片(方法是使用之前讨论的ReportLab)。

2.有用的工具

Python本身提供了对XML的支持,但如果你使用的版本过旧,可能需要安装额外的模块。在这个项目中,需要一个管用的SAX解析器。要确定是否已经有这样的SAX解析器,可尝试执行如下代码:

当你这样做时,很可能不会发生异常。如果是这样,就说明万事俱备,可以接着阅读下一节了。


提示 有很多Python的XML工具,除标准框架PyXML外,另一个很有趣的工具是Fredrik Lundh开发的ElementTree(及其C语言实现的cElementTree)。在较新的Python版本中,标准库包含这个工具,它位于xml.etree包中。如果你使用的Python版本较旧,可从http://effbot.org/zone获取ElementTree。这个工具功能强大却易于使用,如果你很重视Python处理XML,就值得花时间去研究它。


如果出现异常,就必须安装PyXML。只要在网上搜索一下,就应该能够找到安装指南(但除非你的Python版本很古老,否则应提供了XML支持)。

3.准备工作

要编写处理XML文件的程序,必须先设计要使用的XML格式。需要哪些标签?这些标签应包含哪些属性?各个标签都用来做什么?为了回答这些问题,首先需要考虑你使用这种XML格式来描述什么。

主要的概念包括网站、目录、页面、名称、标题和内容。

  • 你不会存储有关网站本身的任何信息,因此网站只是一个顶级元素,包含所有的文件和目录。
  • 目录主要用作文件和其他目录的容器。
  • 页面是单个网页。
  • 目录和网页都得有名称。这些名称就是目录名和文件名,将出现在文件系统和相应的URL中。
  • 每个网页都必须有标题(不同于文件名)。
  • 每个网页都包含一些内容。在这里,我们只使用普通的XHTML来表示内容。这样可直接将内容放在最终的网页中,并让浏览器进行解读。

总之,XML文档只包含一个website元素,这些元素包含多个directory和page元素,其中每个directory元素都可能包含page和directory元素。directory和page都包含属性name,而该属性包含目录和页面的名称。另外,page元素还有属性title。page元素包含XHTML代码(这种代码类型是在XHTML body标签中指定的)。下图是一个这样的示例文件。

4.初次实现

到目前为止,还没有介绍XML解析的工作原理。这里使用的方法名为SAX,他要求我们编写一系列事件处理程序(与GUI编程中一样),并让XML解析器在读取XML文档时调用这些处理程序。

使用DOM如何

在Python(和其他编程语言)中,处理XML最常见的方式有两种:SAX和文档对象模式(DOM)。SAX解析器读取XML并指出发现的内容(文本、标签和属性),但每次只存储文档的一小部分。这让SAX简单、快捷且占用内存较少,也就是我在项目中选择使用它的原因所在。DOM采用的是另一种方法:创建一个表示整个文档的数据结构(文档树)。这种方法速度更慢,需要的内存更多,但在需要操作文档的结构时很有用。


4.1.创建简单的内容处理程序

使用SAX进行解析时,可供使用的事件很多,但这里只使用其中的三个:元素开始(遇到起始标签),元素结束(遇到结束标签)和普通文本(字符)。为解析XML文件,我们将使用模块xml.sax中的函数parse。这个函数负责读取文件并生成事件,但生成事件时,它需要调用一些事件处理程序。这些事件处理程序将实现为内容处理程序对象的方法。你将从xml.sax.handler中的ContentHandler类派生出一个子类,因为这个类实现了所有必要的事件处理程序(什么都不做的伪操作),而你只需要重写需要的事件处理程序。

下面首先来创建一个极简的XML解析器(这里假设要解析的XML文件名为website.xml)。

如果执行这个程序,将看起来什么都没有发生,但也不会出现任何错误信息。然而,在幕后对这个XML文件进行了解析,但由于调用的是什么都不做的默认事件处理程序,因此没有任何输出。

下面来尝试进行简单的扩展。为此,在TestHandler类中添加如下方法:

这重写了默认事件处理程序startElement,其中的参数为相关标签的名称和属性(这些属性存储在一个类似于字典的对象中)。如果你再次运行这个程序(对website.xml进行解析),将看到如下输出:

其中的工作原理应该非常清晰。除startElement外,我们还将使用事件处理程序endElement(它只将标签名作为参数)和characters(它将一个字符串作为参数)。

下面的示例使用这三个事件处理程序来创建一个列表,其中包含网站描述文件中的所有标题(h1元素):

请注意,HeadlineHandler跟踪当前解析的文本是否位于一对h1标签内,其实现如下:在startElement发现标签为h1时将self.in_headline设置为True,并在endElement发现标签为h1时将self.in_headline设置为False。方法characters在解析器遇到文本时自动被调用。只要当前位于两个h1标签之间(self.in_headline为True),characters就将传递给它的字符串(可能只是这两个标签之间的文本的一部分)附加到字符串列表self.data的末尾。将这些文本片段合并为单个字符串,将结果附加到self.headlines末尾并将self.data重置为空列表的任务也是由endElement完成的。在SAX编程中,这种做法(使用布尔变量来指出当前是否在特定标签类型内)很常见。

现在,如果运行这个程序(仍然是对文件website.xml进行解析),将得到如下输出:

4.2.创建HTML页面

现在就可以创建原型了。我们暂时不考虑目录,而是专注于创建HTML页面。你需要稍微修改事件处理程序,使其执行如下任务。

  • 在每个page元素的开头,打开一个给定名称的新文件,并在其中写入合适的HTML首部(包括指定的标题)。
  • 在每个page元素的末尾,将合适的HTML尾部写入文件,再将文件关闭。
  • 在page元素内部,遍历所有标签和字符而不修改它们(将其原样写入文件)。
  • 在page元素外部,忽略所有的标签(如website和directory)。

这些任务大都非常容易理解(至少在你对HTML文档的组织结构有所了解时如此)。然而,有两个问题可能不那么显而易见。

  • 你不能将标签原样写入当前创建的HTML文件中,因为只给你提供了标签的名称(可能还有一些属性)。因此,你必须自己重建这些标签(如加上尖括号等)。
  • SAX本身无法告诉你当前是否在page元素内,因此你必须自己跟踪这一点(就像示例HeadlineHandler中那样)。就这个示例而言,你只关心是否要原样写入标签和字符,因此,将使用一个名为pagethrough的布尔变量,并在进入和离开page元素时修改这个变量的值。

这个简单的程序的代码如图所示。

要将文件存储到哪个目录,就应该在哪个目录执行这个脚本。请注意,即便两个页面位于不同的directory元素中,它们最终也存储到同一个目录中。(再次实现将修复这种问题。)

同样,对文件website.xml进行解析。这将得到4个HTML文件,其中的index.html包含如下内容:

下图显示了在浏览器中查看这个页面的结果。

从上述代码可知,它有两个显而易见的主要缺点。

  • 它使用if语句来处理各种事件。如果处理的事件种类很多,if语句将很长,变得难以理解。
  • HTML代码时硬编码的。这应该很容易解决。

这两个缺点在再次实现中都将得到解决。

本文分享自微信公众号 - 小陈学Python(gh_a29b1ed16571)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券