文档模型:用以描述词汇和文档结构,定义文档中将要出现的数据元素,元素之间的关系,以及元素的数量等

实现文档模型的方法:模式 和 DTD (document type definition 文档类型定义)

1 文档模型的用途

文档模型用于在处理文档之前,验证它的内容是否符合标准。

DTD是文档类型定义,表示文档模型的最原始的方法

在文件顶部声明后插入一行: library.dtd是系统上的DTD路径

DTD中元素频率和元素分组操作符

?指定0个或者1个前面出现的元素。

+ 指定一个或者多个前面出现的元素

,指定一列元素必须 以此特定的顺序出现 (title,author+)意味着书必须有一个标题,随后是一个或者多个作者,必须以该顺序出现

(list) 将元素组织在一起。应用于圆括号后的运算符适用于组中的所有元素。(author,editor)+的含义是一篇文档可能有多个作者与多个编辑。

!或 运算符。该运算符允许在多个选项之间选择 (author|editor)允许一本书有一个作者或者一个编辑,但是不能同时具有两者

* 指定前面的元素或组出现0 次或多次。(book,CD)*允许图书馆中有任意数目的书或者CD 或者什么都没有,是空的。

3 XPath 是在xml文档中描述位置和节点集合的语言。xpath表达式包含对某个节点必须匹配的模式的描述。模式要么相对于一个上下文节点,要么由文档的根节点绝对定义。绝对路径以斜杠开始。路径的每一步之间由斜杠分隔开。

路径中的每一步包含3个部分:描述移动方向的轴,沿着该轴选择节点的测试,还有可选的谓词,它是节点必须满足的boolean 型测试。

如:ancestor-or-self::book[1]其中ancestor-or-self是轴,book是节点测试,[1]是谓词,指定选择满足所有其他条件的第一个节点。

节点测试既可以是一个函数,也可以是一个节点名称 book/node()将返回选择的书节点下的所有子节点,不管它们是文本还是元素。

@ 指定属性轴,这是attribute::的缩写

* 指定当前节点的所有子节点

//指定当前节点的所有后代节点 :descendant-or-self::*//的缩写。如果在xpath开头使用,它将匹配文档中任意地方的元素。

4 html是xml 的子集

为了解析一个html文档,必须创建一个从HTMLParser继承而来的类,并实现必要的方法

使用feed方法向解析器提供数据。可以每次提供一行数据,或者一次提供所有数据。

from html.parser import HTMLParser
class HeadingParser(HTMLParser):
inHeading=False
def handle_starttag(self,tag,attrs):#开始标签
if tag=="h1":
self.inHeading=True
print("found a heading 1")
def handle_data(self,data):#处理标签内容
if self.inHeading:
print(data)
def handle_endtag(self,tag): #结束标签
if tag=="h1":
self.inHeading=False
hParser=HeadingParser()
file=open("headings.html","r")
html=file.read()
file.close()
hParser.feed(html)

5 在解析xml时,可以 选择两种不同类型的解析器:SAX和DOM

SAX代表XML的简单API它是基于流的事件驱动的解析器。这些事件称作 文档事件,在元素开始之处、元素结尾处、遇到文本节点或者遇到一个注释时都有可能发生。

当用SAX解析文档时,文档以期出现的顺序被读入和解析。解析器以数据流的方式打开该文件或者其他数据源如url,之后无论何时遇到任何元素都将引发事件。操作文档不够高效。对于文档转换,应该选择SAX,因为事件驱动的模型速度很快。

DOM的核心在于文档对象,它是XML文档基于树的表示形式。树中的元素称作节点对象,节点拥有属性、子节点、文本 等,它们以对象的形式存储在树中。能在内存中存储整个文档,并且以树的形式操作和搜索其中的元素。文档大时预先处理时间长,处理后快。

python中解析器:xml.sax 和xml.dom.minidom

xml.dom.minidom 中parse方法解析文档树 返回一个Document对象。文本存储于节点的data属性

parse()函数可以引用一个文件名称或一个打开的文件对象

from xml.dom.minidom import parse, parseString
dom1 = parse('c:\\temp\\mydata.xml') # parse an XML file by name
datasource = open('c:\\temp\\mydata.xml')
dom2 = parse(datasource) # parse an open file
dom3 = parseString('Some data some more data')

appendChild方法可以创建节点的结构

节点的方法 insertBefor(newChild,refChild)可以在节点的子节点列表中的任意位置插入新的子节点

方法replaceChild(newChild,oldChild)可以将一个子节点替换为别一个子节点

删除节点 首先需要得到要删除的节点的引用,随后再调用 removeChild(childNode)方法。删除后,调用unlink()方法强制对被删除的节点及它可能连接的子节点进行垃圾回收,xml.dom中不可用。

xml.dom.minidom方法:toprettyxml,它接收两个可选参数:一个是缩进字符串,一个是换行符。如果没有指定参数值,这两个参数分别默认为tabulator 和\n。该方法将DOM 打印为包含良好缩进的XML

xmlns:lib="http://server.domain.tld/NameSpaces/Library">
Sandman volumn 
 
Neil Gaiman
Good omens 
 
Neil Gamain
Terry Pratchett
"Repent,harlequin!" said the man 
 
Harlan Ellison
以上的xml 保存在文件里去掉命名空间可以使用以下代码操作
import os
from xml.dom.minidom import parse
import xml.dom.minidom
def printLibrary(library):
books=myLibrary.getElementsByTagName("book")
for book in books:
print("*******book******")
print("Title:%s"%book.getElementsByTagName("title")[0].childNodes[0].data)
for author in book.getElementsByTagName("author"):
print("author:%s"%author.childNodes[0].data)
# open an xml file and parse it into a dom
myDoc=parse(r'E:\pythonscript\ch15\library.xml')
myLibrary=myDoc.getElementsByTagName("library")[0]
#get all the book elements in the library
books=myLibrary.getElementsByTagName("book")
#Insert a new book in the library
newBook=myDoc.createElement("book")
newBookTitle=myDoc.createElement("title")
titleText=myDoc.createTextNode("Beginning Python")
newBookTitle.appendChild(titleText)
newBook.appendChild(newBookTitle)
newBookAuthor=myDoc.createElement("author")
authorName=myDoc.createTextNode("Peter Norton,et al")
newBookAuthor.appendChild(authorName)
newBook.appendChild(newBookAuthor)
myLibrary.appendChild(newBook)
print("--------added a new book!")
#printLibrary(myLibrary)
#remove a book from the library
#find ellison book
for book in myLibrary.getElementsByTagName("book"):
for author in book.getElementsByTagName("author"):
if author.childNodes[0].data.find("Ellison")>=0:
print(author.childNodes[0].data)
removedBook=myLibrary.removeChild(book)
removedBook.unlink()
print("------------removed a book.")
#printLibrary(myLibrary)
print(myDoc.toprettyxml())
#write back to the library file
lib=open(r"E:\pythonscript\ch15\library.xml","w")
lib.write(myDoc.toprettyxml(" "))

lib.close()# 这里是个方法,如果没有 这个方法 则不能写入数据,文件一直被占用

使用sax 解析:

#!/usr/bin/python
from xml.sax import make_parser
from xml.sax.handler import ContentHandler
#begin bookHandler
class bookHandler(ContentHandler):
inAuthor=False
inTitle=False
def startElement(self,name,attributes):
if name=="book":
print("********book*********")
if name=="title":
self.inTitle=True
print("Title:",)
if name=="author":
self.inAuthor=True
print("Author:",)
def endElement(self,name):
if name=="title":
self.inTitle=False
if name=="author":
self.inAuthor=False
def characters(self,content):
if self.inTitle or self.inAuthor:
print(content)
#end bookHandler
parser=make_parser()
parser.setContentHandler(bookHandler())
parser.parse("library.xml")

解析器xml.sax使用Handler对象解析文档过程中发生的事件。Handler可能是ContentHandler/DTDHandler /EntityResolver/ErrorHandler一个sax应用程序必须实现符合这些接口的处理程序类,并为解析器设置处理程序

接口ContentHandler包含了被文件事件触发的方法,例如元素和字符数据的开始和结束等。在解析字符数据时,解析器可以选择将结果作为一整块数据返回,或者作为若干小的以空白分隔的数据块返回,所以在处理一块文本的过程中需要反复调用characters方法。

make_parser方法创建一个新的解析器对象并将它返回。

6 lxml 使用cmd.exe pip install lxml 安装lxml

lxml是python利用libxml2 和libxslt库的快速、丰富特性的唯一绑定,并且它通过一个简单的api允许处理HTML /xml

包lxml使用了略作修改的ElementTreeAPI

导入lxml:import lxml

from lxml import etree

元素类:元素是ElementTreeAPI的主要容器对象,提供了xml树功能的核心,它们拥有属性并且包含文本.

元素类遵守标准的xml树层次,因此既能支持父元素也能支持子元素。

>>> import lxml
>>> from lxml import etree
>>> author=etree.Element("Horror") #创建新的元素类author,并赋予一个标签名称:Horror
>>> print(author.tag)
Horror
>>> writer1=etree.SubElement(author,"NeilGaiman")# 一个元素的子元素 创建一个新的子元素 ,它的标签是NeilGaiman,父元素是author
>>> writer2=etree.SubElement(author,"StephenKing")
>>> writer3=etree.SubElement(author,"CliveBarker")
>>> print(etree.tostring(author))
b''
>>> writer=author[0] #元素类也是列表,可以使用列表函数
>>> print(writer.tag)
NeilGaiman
>>> for writer in author:
print(writer.tag)
NeilGaiman
StephenKing
CliveBarker

元素可以包含属性,描述元素。

>>> author=etree.Element("author",audience="Adult")
>>> print(author.get("audience"))
Adult
get()方法可以从元素中提取数据,set()方法设置属性或都添加属性
>>> author.set("testpro","protect")
>>> etree.tostring(author)
b''

还可以向元素中添加文本

>>> html=etree.Element("html")
>>> body=etree.SubElement(html,"body")
>>> h1=etree.SubElement(body,"h1")
>>> h1.text="Introduction"
>>> paragraph=etree.SubElement(body,"p")
>>> paragraph.text="here is some text representing our paragraph"
>>> etree.tostring(html)
b'
Introduction
here is some text representing our paragraph
'


打印元素的文本:

>>> etree.tostring(paragraph,method="text")
b'here is some text representing our paragraph'

lxml解析函数:

fromstring()
>>> sentence="here is a sentence"
>>> info=etree.fromstring(sentence)
>>> print(info.tag)
info
>>> print(info.text)
here is a sentence
XML()
>>> info=etree.XML("here is a sentence")
>>> print(info.tag)
info
>>> print(info.text)
here is a sentence
>>> etree.tostring(info)
b'here is a sentence'
>>> import io
>>> newsentence=io.StringIO("This is another sentence")
>>> somesentence=etree.parse(newsentence)
>>> etree.tostring(somesentence)
b'This is another sentence'
>>> printit=somesentence.getroot()
>>> print(printit.tag)
info
>>> print(printit.text)
This is another sentence
dom解析xml
import xml.dom.minidom
from xml.dom.minidom import parse
dom1=parse(r'E:\pythonscript\ch15\config.xml')
myconfig=dom1.getElementsByTagName("config")[0]
dire=myconfig.getElementsByTagName("utilitydirectory")[0]
print(dire.childNodes[0].data)
uti=myconfig.getElementsByTagName("utility")[0]
print(uti.childNodes[0].data)
mode1=myconfig.getElementsByTagName("mode")[0]
print(mode1.childNodes[0].data)
#!/usr/bin/python
from xml.sax import make_parser
from xml.sax.handler import ContentHandler
class configHandler(ContentHandler):
isUtilDir=False
isUtil=False
isMode=False
def startElement(self,name,attributes):
if name=="utilitydirectory":
self.isUtilDir=True
print("------------utility directory-----",)
if name=="utility":
self.isUtil=True
print("--------------utility----------",)
if name=="mode":
self.isMode=True
print("---------------mode--------------",)
def endElement(self,name):
if name=="utilitydirectory":
isUtilDir=False
if name=="utility":
isUtil=False
if name=="mode":
isMode=False
def characters(self,content):
if self.isUtilDir or self.isUtil or self.isMode:
print(content)
parser=make_parser()
parser.setContentHandler(configHandler())
parser.parse(r"E:\pythonscript\ch15\config.xml")