python-docx-源码浅析-创建Document
- 2021-05-20
Docx介绍
在介绍Docx之前,先了解一下Office Open XML, 参考:wikipadia
Office Open XML是Microsoft Office的默认格式,其文档就是docx文件。
我们如果用zip打开docx,可以看到一系列文件夹,如下图
$ tree word
word
├── [Content_Types].xml
├── _rels
├── customXml
│ ├── _rels
│ │ └── item1.xml.rels
│ ├── item1.xml
│ └── itemProps1.xml
├── docProps
│ ├── app.xml
│ └── core.xml
└── word
├── _rels
│ └── document.xml.rels
├── document.xml
├── endnotes.xml
├── fontTable.xml
├── footnotes.xml
├── media
│ ├── image1.png
│ ├── image2.png
├── numbering.xml
├── settings.xml
├── styles.xml
├── theme
│ └── theme1.xml
└── webSettings.xml
我们有非常多的手段去解析xml文件,但分析OOXML仍然不是一件简单事情,而python-docx是一个可以用于解析docx的库。
Document构建
Working with Documents : 参考
我们可以构建一个新的document
from docx import Document
document = Document()
可以通过文件名打开一个docx文件
from docx import Document
document = Document("filename.docx")
也可以通过文件流打开一个docx文件
from docx import Document
with open("filename.docx", "rb") as f:
stream = StringIO(f.read())
doucment = Document(stream)
stream.close()
Document构建流程
构建一个新的document
Document
源代码:GitHub
def Document(docx=None):
docx = _default_docx_path() if docx is None else docx
document_part = Package.open(docx).main_document_part
if document_part.content_type != CT.WML_DOCUMENT_MAIN:
tmpl = "file '%s' is not a Word file, content type is '%s'"
raise ValueError(tmpl % (docx, document_part.content_type))
return document_part.document
def _default_docx_path():
_thisdir = os.path.split(__file__)[0]
return os.path.join(_thisdir, 'templates', 'default.docx')
可以看出如果没有提供docx的话,会加载模板,也就是docx/templates/default.docx文件
然后会调用Package.open将docx加载到内存
接着会判断文件类型是不是CT.WML_DOCUMENT_MAIN
然后就把document返回给调用者。
Package.open
源代码:GitHub
@classmethod
def open(cls, pkg_file):
pkg_reader = PackageReader.from_file(pkg_file)
package = cls()
Unmarshaller.unmarshal(pkg_reader, package, PartFactory)
return package
这是一个类方法,通过PackageReader.from_file可以获得一个解析器,然后利用这个解析器去对pkg_file进行解析
pkgreader
源代码:GitHub
@staticmethod
def from_file(pkg_file):
"""
Return a |PackageReader| instance loaded with contents of *pkg_file*.
"""
phys_reader = PhysPkgReader(pkg_file)
content_types = _ContentTypeMap.from_xml(phys_reader.content_types_xml)
pkg_srels = PackageReader._srels_for(phys_reader, PACKAGE_URI)
sparts = PackageReader._load_serialized_parts(
phys_reader, pkg_srels, content_types
)
phys_reader.close()
return PackageReader(content_types, pkg_srels, sparts)
PhysPkgReader
源代码:GitHub
class PhysPkgReader(object):
def __new__(cls, pkg_file):
if is_string(pkg_file):
if os.path.isdir(pkg_file):
reader_cls = _DirPkgReader
elif is_zipfile(pkg_file):
reader_cls = _ZipPkgReader
else:
raise PackageNotFoundError(
"Package not found at '%s'" % pkg_file
)
else:
reader_cls = _ZipPkgReader
return super(PhysPkgReader, cls).__new__(reader_cls)
PhysPkgReader将返回一个解析器,根据情况而言:
如果pkg_file是字符串,那么 判断pkg_file,是文件路径的话使用_DirPkgReader,是zip文件使用_ZipPkgReader, 其他抛出异常
如果不是字符串,一律使用_ZipPkgReader
注意到
super(PhysPkgReader, cls).__new__(reader_cls)
这句话其实等价于
object.__new__(reader_cls)
等价于利用reader_cls对象作为本类的实例,这是一个非常有趣的点。
不过这里无论是DirPkgReader、还是ZipPkgReader都是PhysPkgReader的子类。