aboutsummaryrefslogtreecommitdiff
path: root/mammoth/docx
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2013-12-01 11:47:48 +0000
committerMichael Williamson <mike@zwobble.org>2013-12-01 11:47:48 +0000
commitf8d72dbba832db282a3ec04a5108fbd090f49a4f (patch)
treee86971aec308963c55afde6263091f6591bb08d8 /mammoth/docx
parent833c867189d757efb5b13e1e783869a4039860de (diff)
Move docx reading into separate package
Diffstat (limited to 'mammoth/docx')
-rw-r--r--mammoth/docx/__init__.py88
-rw-r--r--mammoth/docx/xmlparser.py99
2 files changed, 187 insertions, 0 deletions
diff --git a/mammoth/docx/__init__.py b/mammoth/docx/__init__.py
new file mode 100644
index 0000000..de2b8a8
--- /dev/null
+++ b/mammoth/docx/__init__.py
@@ -0,0 +1,88 @@
+import zipfile
+
+from .. import documents
+from ..results import Result
+from .xmlparser import parse_xml, node_types
+
+
+_namespaces = [
+ ("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"),
+]
+
+
+def read(fileobj):
+ zip_file = zipfile.ZipFile(fileobj)
+ document_fileobj = zip_file.open("word/document.xml")
+ document_xml = parse_xml(document_fileobj, _namespaces)
+ return Result(read_xml_element(document_xml), [])
+
+
+_handlers = {}
+
+
+def handler(name):
+ def add(func):
+ _handlers[name] = func
+
+ return add
+
+
+def read_xml_element(element):
+ handler = _handlers.get(element.name)
+ if handler is None:
+ return None
+ else:
+ return handler(element)
+
+
+@handler("w:t")
+def text(element):
+ return documents.Text(_inner_text(element))
+
+
+@handler("w:r")
+def run(element):
+ return documents.Run(_read_xml_elements(element.children))
+
+
+@handler("w:p")
+def paragraph(element):
+ properties = element.find_child_or_null("w:pPr")
+ style_name = properties \
+ .find_child_or_null("w:pStyle") \
+ .attributes.get("w:val")
+
+ children = _read_xml_elements(element.children)
+ return documents.paragraph(children, style_name)
+
+
+@handler("w:body")
+def body(element):
+ return _read_xml_elements(element.children)
+
+
+@handler("w:document")
+def document(element):
+ body_element = _find_child(element, "w:body")
+ return documents.Document(_read_xml_elements(body_element.children))
+
+
+def _find(predicate, iterable):
+ for item in iterable:
+ if predicate(item):
+ return item
+
+
+def _find_child(element, name):
+ return _find(lambda child: child.name == name, element.children)
+
+
+def _read_xml_elements(elements):
+ return filter(None, map(read_xml_element, elements))
+
+
+def _inner_text(node):
+ if node.node_type == node_types.text:
+ return node.value
+ else:
+ return "".join(_inner_text(child) for child in node.children)
diff --git a/mammoth/docx/xmlparser.py b/mammoth/docx/xmlparser.py
new file mode 100644
index 0000000..d58ea75
--- /dev/null
+++ b/mammoth/docx/xmlparser.py
@@ -0,0 +1,99 @@
+import collections
+import xml.sax
+
+
+XmlElementBase = collections.namedtuple("XmlElement", ["name", "attributes", "children"])
+class XmlElement(XmlElementBase):
+ def find_child_or_null(self, name):
+ for child in self.children:
+ if child.name == name:
+ return child
+ return _null_xml_element
+
+
+class NullXmlElement(object):
+ attributes = {}
+
+ def find_child_or_null(self, name):
+ return self
+
+
+_null_xml_element = NullXmlElement()
+
+
+XmlText = collections.namedtuple("XmlText", ["value"])
+
+
+def element(name, attributes=None, children=None):
+ return XmlElement(name, attributes or {}, children or [])
+
+text = XmlText
+
+
+class node_types(object):
+ element = 1
+ text = 3
+
+
+XmlElement.node_type = node_types.element
+XmlText.node_type = node_types.text
+
+
+
+def parse_xml(fileobj, namespace_mapping=None):
+ if namespace_mapping is None:
+ namespace_prefixes = {}
+ else:
+ namespace_prefixes = dict((uri, prefix) for prefix, uri in namespace_mapping)
+
+ handler = Handler(namespace_prefixes)
+ parser = xml.sax.make_parser()
+ parser.setFeature(xml.sax.handler.feature_namespaces, True)
+ parser.setContentHandler(handler)
+ parser.parse(fileobj)
+ return handler.root()
+
+
+class Handler(xml.sax.handler.ContentHandler):
+ def __init__(self, namespace_prefixes):
+ self._namespace_prefixes = namespace_prefixes
+ self._element_stack = [RootElement()]
+ self._character_buffer = []
+
+ def root(self):
+ return self._element_stack[0].children[0]
+
+ def startElementNS(self, name, qname, attrs):
+ self._flush_character_buffer()
+ attributes = dict((self._read_name(key), value) for key, value in attrs.items())
+ element = XmlElement(self._read_name(name), attributes, [])
+ self._element_stack[-1].children.append(element)
+ self._element_stack.append(element)
+
+ def endElementNS(self, name, qname):
+ self._flush_character_buffer()
+ self._element_stack.pop()
+
+ def characters(self, content):
+ self._character_buffer.append(content)
+
+ def _flush_character_buffer(self):
+ if self._character_buffer:
+ text = "".join(self._character_buffer)
+ self._element_stack[-1].children.append(XmlText(text))
+ self._character_buffer = []
+
+ def _read_name(self, name):
+ uri, local_name = name
+ if uri is None:
+ return local_name
+ else:
+ prefix = self._namespace_prefixes.get(uri)
+ if prefix is None:
+ return "{%s}%s" % (uri, local_name)
+ else:
+ return "%s:%s" % (prefix, local_name)
+
+class RootElement(object):
+ def __init__(self):
+ self.children = []