《Python基础教程(第2版)》学习笔记(三):

Proj3万能的XML

这个项目介绍了如何用XML来表示多种数据,以及如何使用适合XML或SAX的简单API来处理XML文件。XML并不是一种具体的语言(例如HTML),它更类似于定义语言的一种规则集合。我们仍然使用HTML中的方式来写标签,但在XML中,还可以自己定义标签名。

工作和目标

本项目解决的问题就是解析(读取和处理)XML文件,具体来讲是通过一个描述各种网页和目录的XML文件生成一个完整的网站。这个文件包括站点结构和每个页面的基本内容。
目标列举如下:
-整个网站应该用一个XML文件描述,其中包括独立网页和目录的信息。
-程序应该能创建所需的目录和网页
-应该可以轻松改变整个网站的设计,并且以新的设计为基础重新生成所有网页。

工具

Python有一些内建的XML支持,查看是否已有一个可以用的SAX语法分析器,执行下面语句:

from xml.sax import parse
parser = make_parser()

执行这条命令不引发任何异常即可以开始项目了。

准备工作

编写处理XML文件的程序前,需要设计XML格式。主要概念包括网站、目录、页面、名称、标题和内容。
-网站。不用存储有关网站本身的任何信息,所以网站就是包括所有文件和目录的顶级元素。
-目录。目录是文件和其他目录的容器。
-页面。一个网页。
-名称。目录和网页都需要名称–当目录和文件出现在文件系统和相应的URL中时,它们可以用作目录名和文件名。
-标题。每个网页都应该有标题(和文件名不同)。
-内容。每个网页都有一些内容。
简单来说,文档由一个包含directory和page元素的website元素组成,每个目录元素可以包括更多的页面和目录。directory和page元素有叫做name的特性,属性值是它们的名字。除此之外,page标签还有title特性。page元素包括XHTML代码。一个实例文件是Proj3万能的XML中的website.xml。如下:

<website>
    <page name="index" title="Home Page">
        <h1>Welcome to My Home Page</h1>

        <p>Hi,there.My name is Alexis,and this is my home page.Here
        are some of my interests:</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>Alexis's Shouting Page</h1>

            <p>...</p>
        </page>
        <page name="sleeping" title="Sleeping">
            <h1>Alexis's Sleeping Page</h1>

            <p>...</p>
        </page>
        <page name="eating" title="Eating">
            <h1>Alexis's Eating Page</h1>

            <p>...</p>
        </page>
    </directory>
</website>

初次实现

创建简单的内容处理程序

在使用SAX进行解析时,有很多种事件类型可用,这里只用到3个:startElement(开始标签的匹配项)、endElement(关闭标签的匹配项)以及characters(字符)。使用xml.sax模块中的parse函数负责读取并且生成这些事件。要生成这些事件需要调用一些处理程序(content handler),xml.sax.handler中的ContentHandler类实现了这些处理程序,但函数具体工作需要用户重写。解析的具体原理可以参看这个链接(http://www.cnblogs.com/hongfei/p/python-xml-sax.html)。假如要解析一个<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_headline = 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

打印出的结果如下图:
解析<code><h1></code>结果
简单的过程就是当找到<h1>标签时,调用startElement,开关打开。在结束标签之前都会调用characters收集字符串。当遇到结束标签</h1>时,执行endElement函数,处理收集到的字符串,添加到存储题目的列表中。

创建HTML页面

像上一节的例子处理<h1>标签那样那样,我们需要编写一个还能处理<page>等其他标签的完整的HTML页面解析程序(暂时不考虑目录的解析)。这个简单版本的代码在工程文件夹下的pagemaker.py文件中。运行结果如下所示:

<html><head>
<title>Home Page</title>
</head><body>

        <h1>Welcome to My Home Page</h1>

        <p>Hi,there.My name is Alexis,and this is my home page.Here
        are some of my interests:</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>

</body></html>

再次实现

调度程序的混入类

与其在标准的泛型事件处理程序(比如startElement)中编写大段的if语句,不如编写自己的具体程序(比如startPage),并且自动调用它们。一个提供有限功能的混入类(本文中是Dispatcher)和其他更具体的类(本文中是ContentHandler类)一起被继承,生成的新类WebsiteConstructor才是最终被parse使用的类。Dispatcher类中的方法dispatch()类似于项目1中的根据名称查找函数,该类还包括基本的startElement和endElement事件处理。

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,
        #args是一个元组
        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)
        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):
        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><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')

解释一下为什么要ensureDirectory。os.makedirs(‘foo/bar/baz’)函数会在当前目录中创建一个字符串中的目录,当该目录(指完整的../foo/bar/baz)本来就存在时,会引发一个异常。为了避免出现这个异常,需要使用os.path.isdir函数,它可以检查给定的路径是否是目录(也就是目录是否存在)。另外一个函数os.path.join可以使用正确的分隔符将数个路径连接起来例如:directory = [“C”, “pic”, “18x.jpg”],执行os.path.join(*directory) 会得到”C\\pic\\18x.jpg”。完整代码见website.py文件。

总结

本项目建立了一个简单的网站,非常简单,只有文本信息。要做更复杂的还需要深入学习,以后有机会再扩展一下这个程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值