python基础教程总结15——3 XML构建网址
要求:
网址用一个XML文件描述,其中包括独立网页和目录的信息;
程序能创建所需的目录和网页;
可以改变网址的设计,并且以新的设计为基础重新生成所有网页
概念:
网站:不用存储有关网站本身的任何信息,即网站就是包含所有文件和目录的顶级元素;
目录:目录是文件和其他目录的容器;
页面:一个网页;
名称:目录和网页都需要名称——当目录和文件出现在文件系统和相应的URL中,可以用作目录名和文件名
标题:每个网页都应该有标题(和文件名不同)
内容:每个网页都应该有内容,这里只用XHTML来表示——就能将它传递到最终的网页上,让浏览器解释
XML程序:
文档由一个包含数个directory和page元素的website元素组成,每个目录元素可以包括更多的页面和目录。directory和page元素有name特性,属性值为它们的名字。此外,page标签还有title特性。page元素包括XHTML代码(XHTML的body标签中的类型)
#用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文件解析:
python解析xml和在java中一样,有两种方式,SAX和DOM,两种处理方式不同点在于速度和范围,前者讲究的是效率,每次只处理文档的一小部分,快速而能有效的利用内存,后者是相反的处理方式,先把所有的文档载入到内存,然后再进行处理,速度比较慢,也比较消耗内存,唯一的好处就是可以操作整个文档。
1. 简单实现
1.1 简单的内容处理程序
在python中使用sax方式处理xml要先引入xml.sax中的parse函数,还有xml.sax.handler中的ContentHandler,后面的这个类是要和parse函数来配合使用的。使用方式如下: parse('xxx.xml',xxxHandler),这里面的xxxHandler要继承上面的ContentHandler。 然后这个parse函数在处理xml文件的时候,会调用xxxHandler中的startElement函数和endElement函数来表示一个xml中的标签的开始和结束,中间的过程使用一个名为characters的函数来处理标签内部的所有字符串。
from xml.sax.handler import ContentHandler from xml.sax import parse class TestHandler(ContentHandler): def startElement(self,name,attrs): print name, attrs.keys() parse('website.xml',TestHandler())
startElement函数参数:标签及其特性(保存在类字典对象中);
endElement函数参数:标签名;
characters函数参数:字符串;
#建立网站大标题(h1元素)列表的例子 from xml.sax.handler import ContentHandler from xml.sax import parse class HeadlineHandler(ContentHandler): in_headline=False def __init__(self,headlines): ContentHandler.__init__(self) self.headlines=headlines self.data=[] def startElement(self,name,attrs): if name=='h1': self.in_lines=True def endElement(self,name): if name=='h1': text=' '.join(self.data) self.data=[] self.headlines.append(text) self.in_headline=False def characters(self,string): if self.in_headline: self.data.append(string) headlines=[] parse('website.xml',HeadlineHandler(headlines)) print 'The following <h1> elements were found:' for h in headlines: print h
分析:
为啥结果是空?????????????????????????
1.2 创建HTML页面
要求:
在每个page元素开始处,使用给定的文件名打开一个新文件,写入合适的HTML首部,包括给定的标题;
在每个page元素的结尾处,写入HTML的页脚,然后关闭文件;
在page元素内部时,跳过所有标签和字符,不进行修改(将它们直接写入文件);
不在page元素内部时,忽略所有标签(比如website或者directory)
#简单的页面创建程序脚本(pagemaker.py) 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>\n‘ % 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())
有啥都没。。。。。。
存在的问题:
使用if语句处理不同的事件类型,若事件很多,if语句很长且不易读;
HTML代码是硬连接的,应该可以轻松进行替换
2. 改进
2.1 调度程序的混入类
#基本事件处理程序 class Dispatcher: #... def startElement(self,name,attrs): self.dispatch('start',name,attrs) def endElement(self,name): self.dispatch('end',name) #dispatch方法 #capitalize()方法返回字符串的一个副本,只有它的第一个字母大写 #Instance = A() print getattr(Instance , 'name, 'not find') #如果Instance 对象中有属性name则打印self.name的值,否则打印'not find' #callable(obj),检查对象obj是否可调用 def dispatch(self,predix,name,attrs=None): mname=predix+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)
说明:
根据一个前缀(‘start’或‘end’)和一个标签名(比如‘page’)构造处理程序的方法名(比如‘startPage');
使用同样的前缀,构造默认处理程序的名字(比如’defaultStart');
试着使用getattr获得处理程序,用Nono作为默认值;
如果结果可以调用,将一个空元组赋值给args;
否则试着利用getattr获取默认处理程序,再次使用None作为默认值,同样的将args设为只包括标签名的元组(默认程序需要);
如果正在使用一个起始处理程序,那么将属性添加到参数元组(args);
如果处理程序可调用(或者是可用的具体处理程序,或者是可用的默认程序)使用正确的参数进行调用。
2.2 实现首部,页脚和默认的处理程序
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") def defaultStart(self,name,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)
2.3 对目录的支持
os.makedirs:在给定的路径内创建所需要的目录;
os.path.isdir:检查指定的路径是否是目录;
os.path.join:可用使用正确的分隔符将数个路径连接起来
2.4 事件处理程序
#目录处理程序 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()
3. 总结
3.1 流程
程序执行逻辑:
1).parse('website.xml',WebsiteConstructor('public_html'))
首先生成一个WebsiteConstructor对象,生成此对象时调用其构造方法创建名为public_html的目录
2).parse调用处理程序WebsiteConstructor,下面将是触发式执行程序
逻辑概要:一共会触发时执行三种事件,起始标签、结束标签、遇到字符
遇到字符:只需直接打印字符即可
起始标签:会判断如果有此标签起始方法则调用,否则调用默认其实方法(default开头的)
结束标签:同上
在page里才能打印,所以在page的起始标签中加入了passthroug变量(如果为True在页面内,False不再页面内)。如果在页面内则能调用默认方法下的if语句,否则不执行。
3.2 程序
1)attrs是一个字典,存储的是该标签的所有属性,以字典方式存储, key=属性名,value=属性值
2)characters遇到字符就会触发的事件,字符块可能是全是空格的字符块
from xml.sax.handler import ContentHandler from xml.sax import parse import os class Dispatcher: def dispatch(self, prefix, name, 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, name, attrs): self.dispatch('start', name, 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) print path print '----' if not os.path.isdir(path): os.makedirs(path) def characters(self, chars): if self.passthrough: self.out.write(chars) def defaultStart(self, name, 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): print 'endDirectory' self.directory.pop() def startPage(self, attrs): print 'startPage' filename = os.path.join(*self.directory + [attrs['name']+'.html']) self.out = open(filename, 'w') self.writeHeader(attrs['title']) self.passthrough = True def endPage(self): print 'endPage' 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'))