问题描述:
问题来源于《Python基础教程》第三个实例,万能的xml。本项目要解决的常见问题是解析(读取和处理)XML文件。因为使用XML几乎能表示任何数据。更具体点来说,通过单个的XML文件生成一个完整的网站,这个文件包括站点结果和每个页面的基本内容。因为对于XML的不了解,所以,先在http://www.w3school.com.cn/x.asp补充了一点XML的背景知识。
首先考虑XML文件需要描述什么:
*网站:不用存储有关网站本身的任何信息,所以,网站就是包括所有文件和目录的顶级元素。
*目录:目录是文件和其他目录的容器。
*页面:一个网页。
*名称:目录和网页都需要名称--当目录和文件出现在文件系统和相应的URL中时,它们可以用作目录名和文件名。
*内容:每个网页都有一些内容。这里只用XHTL来表示内容--这样就能将它传递到最终的网页上,让浏览器对它进行解释。
简单来说,文档由一个包含数个directory和page元素的website元素组成,每个目录元素可以包括更多的页面和目录。Directory和page元素有叫做name的特性,属性值是他们的名字。除此之外,page标签还有title的特性。
用于测试的XML文件表示的简单网站(website.xml)
<website> <page name="index" title="Home page"> <h1>Welcome to my Home page</h1> <p>Hi,there. My name is Mr.gumby,and this is my home page,here are some of my int:</p> <ul> <li><a href="interests/shouting.html">Shouting</a></li> <li><a href="interests/sleeping.html">Sleeping</a></li> <li><a href="interests/eating.html">Eating</a></li> </ul> </page> <directory name="interests"> <page name="shouting" title="Shouting"> <h1>shouting page</h1> <p>....</p> </page> <page name="sleeping" title="Sleeping"> <h1>sleeping page</h1> <p>...</p> </page> <page name="eating" title="Eating"> <h1>Eating page</h1> <p>....</p> </page> </directory> </website>
XML解析:
XML解析是如何工作的,这里所使用的方法叫做SAX,包括编写一组时间处理程序(就如同GUI程序设计),当解析器读XML文档的时候,就可以让它调用这些处理完成解析工作。
使用SAX进行解析时,有很多的事件类型可用,但是本项目只用了三个:元素的起始(开始标签的匹配项)、元素的结束(关闭标签的匹配项)以及纯文本(字符)。要解析XML文件,可以使用xml.sax模块的parse函数。这个函数负责处理程序会作为内容处理程序(contenthandler)对象的方法来实现。需要集成xml.asx.handler中的ContentHandler类,因为它实现了所有需求的事件处理程序(只不过是没有任何效果的未操作),可以再需要的时候覆盖这些函数。
创建HTML页面:
现在已经准备好要创建好原形了,首先忽略目录,专注于创建HTML页面。创建的压面要符合厦门的要求:
1、在每个page元素的开始处,使用给定的文件名打开一个新文件,写入合适的HTML首部,包括给定的标题。
2、在每个page元素的结尾处,写入HTML的页脚,然后关闭文件。
3、在page元素内部时,跳过所有标签和字符,不进行修改(将他们直接写入文件)。
4、不再page元素内部时,忽略所有的标签(比如说website或者directory)
大多数的功能实现起来都很简单,但是有两个问题比较不完全清楚:
1、不能简单的“穿过”标签(在建立HTML文件时直接写入文件),因为只有名字(可能还有一些特性)。需要自己重建这些标签(使用尖括号等)。
2、SAX本身无法告诉你当前是否正位于一个page元素内部。所以,需要自己注意这类事情。在这个项目中,程序支队是否穿过标签和文本感兴趣,所以使用一个叫做passthrough的布尔变量在进入和离开页面时进行更新。
初次实现代码如下:
from xml.sax.handler import ContentHandler from xml.sax import parse class PageMaker(ContentHandler): passthrough = False def startElement(self,name,attrs): if name == 'page': self.passthrough = True self.out = open(attrs['name']+ '.html','w') self.out.write('<html><head>\n') self.out.write('<title>%s</title>' % attrs['title']) self.out.write('</head><body>\n') elif self.passthrough: self.out.write('<' + name) for key,val in attrs.items(): self.out.write(' %s="%s"' % (key,val)) self.out.write('>') def endElement(self,name): if name =='page': self.passthrough = False self.out.write('\n</body></html>\n') self.out.close() elif self.passthrough: self.out.write('</%s>' % name) def characters(self,chars): if self.passthrough: self.out.write(chars) parse('website.xml',PageMaker())
Eating.html
Index.html
Shouting.html
Sleeping.html
其中index.html的截图如下:
再次实现:
因为SAX的机制比较底层且基本,同城会编写一个Mix-in类来处理手机字符数据,管理布尔状态变量(比如passthrough)或是纸牌时间到自己定义的时间中处理程序等这类管理细节。这里主要介绍程序的调度。
主要改变有三点:
1、调度程序的Mix-in类
这个类实现了以下几个功能:
A、当使用‘foo’这样的名字调用startElement时,它会试图寻找叫做startFoo的时间处理程序,然后利用给定的特性进行调用。
B、同样的,如果使用‘foo’调用endElement,那么它会试着调用ednFoo。
C、如果在这些地方找不到给定的时间处理程序,那么会分别调用defaultStart或者defaultEnd方法。如果连默认的处理程序都没有的话,那就什么都不做。
可能对于抽象编程这类东西了解不够深,对于这个感觉还是有点难以理解。按照书里面说的,所做的方法如下:
A、根据一个前缀(‘start’或‘end’)和一个标签名(比如‘page’)构造处理程序的方法名(比如‘startPage’)
B、使用同一的前缀,构造默认处理程序的名字(比如‘defaultStart’)
C、试着使用getattr获得处理程序,用None作为默认值
D、如果结果可以调用,那么讲一个空元组赋值给args
E、否则试着利用getattr获取默认处理程序,在使用None作为默认值。同样的,将args设定为只包括标签名的元组(以为内默认的处理程序需要)。
F、如果正使用一个起始处理程序,那么将属性添加到参数元组(args)中
G、如果处理程序可调用(或者是可用的具体处理程序,或者是可用的默认处理程序),那么,使用正确的参数进行调用。
2、实现首部、页脚和默认的处理程序
这个比较好理解,程序创建单独的方法用于编写首部和页脚。
3、对目录的支持
为了创建所需要的目录,需要os和os.path模块中的一些有用的函数,其中之一就是os.makedirs,它可以再给定的路径中创建所有需要的目录。比如os.makedirs(‘foo/bar/baz’)会在当前的目录中创建foo目录,然后在foo中创建bar目录,最后在bar目录中创建baz,如果foo目录已经存在,那么只会创建bar和baz,类似的,如果bar也存在的话那么只有baz会被创建。不过如果baz同样存在的话,就会引发一个异常。
为了避免出现这个异常,需要使用os.path.isdir函数,它可以检查给定的路径是否是目录(即目录是否存在)。另外的一个有用的函数是os.path.join,它可以使用正确的分隔符将数个路径连接起来。
4、事件处理程序
最后需要实现时间处理程序。需要四个对象--两个处理目录,两个处理页面。目录处理程序只有使用directory列表和ensureDirectory方法。
页面处理程序使用writeHeader和writeFooter方法,初次之外,他们还要设定passthrough变量(穿过XHTML)以及要打开和关闭的页面关联的文件。
网站构建函数如下(website,py):
from xml.sax.handler import ContentHandler from xml.sax import parse import os class Dispatcher: def dispatch(self,prefix,attrs = None): mname = prefix +name.capitalize() dname = 'default' +prefix.capitalize() method = getattr(self,mname,None) if callable(method): args = () else: method = getattr(self,dname,None) args = name,if prefix == 'start':args +=attrs,if callable(method): method(*args) def startElement(self,attrs): self.dispatch('start',attrs) def endElement(self,name): self.dispatch('end',name) class WebsiteConstructor(Dispatcher,ContentHandler): passthrough = False def __init__(self,directory): self.directory = [directory] self.ensureDirectory() def ensureDirectory(self): path = os.path.join(*self.directory) if not os.path.isdir(path): os.makedirs(path) def characters(self,chars): if self.passthrough: self.out.write(chars) def defaultStart(self,attrs): if self.passthrough: self.out.write('<' + name) for key,val in attrs.items(): self.out.write(' %s = "%s"' %(key,val)) self.out.write('>') def defaultEnd(self,name): if self.passthrough: self.out.write('</%s>' % name) def startDirectory(self,attrs): self.directory.append(attrs['name']) self.ensureDirectory() def endDirectory(self): self.directory.pop() def startPage(self,attrs): filename = os.path.join(*self.directory+[attrs['name']+'.html']) self.out = open(filename,'w') self.writeHeader(attrs['title']) self.passthrough = True def endPage(self): self.passthrough = False self.writeFooter() self.out.close() def writeHeader(self,title): self.out.write('<html>\n <head>\n <title>') self.out.write(title) self.out.write('</title>\n </head>\n <body>\n') def writeFooter(self): self.out.write('\n </body>\n</html>\n') parse('website.xml',WebsiteConstructor('public_html'))
实现结果是生成public_html/的文件夹,这个文件夹里面含有一个index.html的文件和interests的文件夹,interests里面放的是三个HTML文件,HTML的文件实现结果和之前一样。
原文链接:https://www.f2er.com/xml/299061.html