文档模型:用以描述词汇和文档结构,定义文档中将要出现的数据元素,元素之间的关系,以及元素的数量等
实现文档模型的方法:模式 和 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")