文章大纲
XML 是一种标记性语言类似于 HTML,相对于 HTML 而言它可以自定义标签。我目前对 XML 使用的不多,更多的是 YAML,但学习这个案例还是很有启发意义的,尤其是后续处理类似具有标签性格式的文件。
需要处理的 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 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>Mr. Gumby's Shouting Page</h1>
<p>...</p>
</page>
<page name="sleeping" title="Sleeping">
<h1>Mr. Gumby's Sleeping Page</h1>
<p>...</p>
</page>
<page name="eating" title="Eating">
<h1>Mr. Gumby's Eating Page</h1>
<p>...</p>
</page>
</directory>
</website>
具有多个层次结构,website
page
directory
。
对于 XML 的解析,Python 本身提供了支持,有一个 SAX 的解析器进行解析。
创建 HTML 页面
在不考虑目录的情况下,单独解析 XML 中的页面。需要完成的任务:
- 当处理到
page
开头的,需要创建一个给定名称的文件,并写入合适的 HTML 开头内容,包含页面标题 - 在每个
page
的结尾,将合适的 HTML 尾部写入到文件,并关闭文件 - 在
page
的内部,遍历所有的标签和字符不修改,直接写入到文件,但是需要判断这些内容是否在page
里面,所以需要添加一个变量来判断,同时 HTML 的标签需要尖括号,所以需要重建它们
简单页面创建的脚本:
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>{}</title>'.format(attrs['title']))
self.out.write('</head><body>\n')
elif self.passthrough:
self.out.write('<' + name)
for key, val in attrs.items():
self.out.write(' {}="{}"'.format(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('</{}>'.format(name))
def characters(self, chars):
if self.passthrough: self.out.write(chars)
parse('website.xml', PageMaker())
再次实现
以下是根据 XML 文件内容创建站点的全部实现:
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)
os.makedirs(path, exist_ok=True)
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(' {}="{}"'.format(key, val))
self.out.write('>')
def defaultEnd(self, name):
if self.passthrough:
self.out.write('</{}>'.format(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 <body>\n')
def writeFooter(self):
self.out.write('\n </body>\n</html>\n')
parse('website.xml', WebsiteConstructor('public_html'))
第一眼看过去感觉相当复杂,但其实有点第一个项目的影子。
首先 Dispatcher
类中的 dispatch
方法,它的作用是调用相关方法的,用于分派功能,不同的标签将调用不同的方法,给了一个 prefix
外加标签的名字,如果相关方法没有找到就调用默认的方法(主要是为了处理 page 内部出现的 HTML 标签,避免认为是 XML 的标签而被当作 XML 进行处理)。
同时在 Dispatcher
中重写了 ContentHandler
中的 startElement
和 endElement
方法,来通过 dispatch
方法进行分派。
还有比较有意思的是对目录的处理,首先通过初始化将传递进来的目录放置到内部的一个类型为列表的变量中,作为根目录,一旦遇到 directory
标签,就将标签名字追加到列表中,然后使用 join
方法,将列表转换为路径进行创建。随后通过类似的方式在 directory
中根据 page
的名字创建相应的 HTML 页面,在最后遇到 /directory
时,将路径从列表中进行 pop
。
以上就是整个代码所做的事情,代码中实现分派器和目录的处理真的是很有启发。