Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

from docx.api import Document # noqa

__version__ = '0.8.10.3'
__version__ = '0.8.10.4'


# register custom Part classes with opc package reader

from docx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
from docx.opc.part import PartFactory
from docx.opc.parts.coreprops import CorePropertiesPart
from docx.opc.parts.customprops import CustomPropertiesPart

from docx.parts.document import DocumentPart
from docx.parts.hdrftr import FooterPart, HeaderPart
Expand All @@ -27,6 +28,7 @@ def part_class_selector(content_type, reltype):

PartFactory.part_class_selector = part_class_selector
PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart
PartFactory.part_type_for[CT.OPC_CUSTOM_PROPERTIES] = CustomPropertiesPart
PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart
PartFactory.part_type_for[CT.WML_FOOTER] = FooterPart
PartFactory.part_type_for[CT.WML_HEADER] = HeaderPart
Expand Down
8 changes: 8 additions & 0 deletions docx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ def core_properties(self):
"""
return self._part.core_properties

@property
def custom_properties(self):
"""
A |CustomProperties| object providing read/write access to the custom
properties of this document.
"""
return self._part.custom_properties

@property
def inline_shapes(self):
"""
Expand Down
3 changes: 3 additions & 0 deletions docx/opc/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ class CONTENT_TYPE(object):
OPC_CORE_PROPERTIES = (
'application/vnd.openxmlformats-package.core-properties+xml'
)
OPC_CUSTOM_PROPERTIES = (
'application/vnd.openxmlformats-officedocument.custom-properties+xml'
)
OPC_DIGITAL_SIGNATURE_CERTIFICATE = (
'application/vnd.openxmlformats-package.digital-signature-certificat'
'e'
Expand Down
71 changes: 71 additions & 0 deletions docx/opc/customprops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# encoding: utf-8

"""
The :mod:`pptx.packaging` module coheres around the concerns of reading and
writing presentations to and from a .pptx file.
"""

from __future__ import (
absolute_import, division, print_function, unicode_literals
)

import numbers
from lxml import etree

NS_VT = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"

class CustomProperties(object):
"""
Corresponds to part named ``/docProps/custom.xml``, containing the custom
document properties for this document package.
"""
def __init__(self, element):
self._element = element

def __getitem__( self, item ):
prop = self.lookup(item)
if prop is not None :
# print(etree.tostring(prop, pretty_print=True))
elm = prop[0]
if elm.tag == '{%s}i4' % NS_VT:
try:
return int(elm.text)
except:
return elm.text
elif elm.tag == '{%s}bool' % NS_VT:
return True if elm.text == '1' else False
return elm.text

def __setitem__( self, key, value ):
prop = self.lookup(key)
if prop is None :
elmType = 'lpwstr'
if isinstance(value, bool):
elmType = 'bool'
value = str(1 if value else 0)
elif isinstance(value, numbers.Number):
elmType = 'i4'
value = str(int(value))
prop = etree.SubElement( self._element, "property" )
elm = etree.SubElement(prop, '{%s}%s' %(NS_VT, elmType), nsmap = {'vt':NS_VT} )
elm.text = value
prop.set("name", key)
prop.set("fmtid", "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}")
prop.set("pid", "%s" % str(len(self._element) + 1))
else:
elm = prop[0]
if elm.tag == '{%s}i4' % NS_VT:
elm.text = str(int(value))
elif elm.tag == '{%s}bool' % NS_VT:
elm.text = str(1 if value else 0)
else:
elm.text = '%s' % str(value)

def __len__( self ):
return len(self._element)

def lookup(self, item):
for child in self._element :
if child.get("name") == item :
return child
return None
21 changes: 21 additions & 0 deletions docx/opc/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from docx.opc.packuri import PACKAGE_URI, PackURI
from docx.opc.part import PartFactory
from docx.opc.parts.coreprops import CorePropertiesPart
from docx.opc.parts.customprops import CustomPropertiesPart
from docx.opc.pkgreader import PackageReader
from docx.opc.pkgwriter import PackageWriter
from docx.opc.rel import Relationships
Expand Down Expand Up @@ -41,6 +42,14 @@ def core_properties(self):
"""
return self._core_properties_part.core_properties

@property
def custom_properties(self):
"""
|CustomProperties| object providing read/write access to the Dublin
Core properties for this document.
"""
return self._custom_properties_part.custom_properties

def iter_rels(self):
"""
Generate exactly one reference to each relationship in the package by
Expand Down Expand Up @@ -187,6 +196,18 @@ def _core_properties_part(self):
self.relate_to(core_properties_part, RT.CORE_PROPERTIES)
return core_properties_part

@property
def _custom_properties_part(self):
"""
|CustomPropertiesPart| object related to this package. Creates
a default custom properties part if one is not present (not common).
"""
try:
return self.part_related_by(RT.CUSTOM_PROPERTIES)
except KeyError:
custom_properties_part = CustomPropertiesPart.default(self)
self.relate_to(custom_properties_part, RT.CUSTOM_PROPERTIES)
return custom_properties_part

class Unmarshaller(object):
"""Hosts static methods for unmarshalling a package from a |PackageReader|."""
Expand Down
74 changes: 74 additions & 0 deletions docx/opc/parts/customprops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# encoding: utf-8

"""
Custom properties part, corresponds to ``/docProps/custom.xml`` part in package.
"""

from __future__ import (
absolute_import, division, print_function, unicode_literals
)

from lxml import etree

from datetime import datetime

from ..constants import CONTENT_TYPE as CT
from ..customprops import CustomProperties
from ...oxml.customprops import CT_CustomProperties
from ..packuri import PackURI
from ..part import XmlPart

# configure XML parser
parser_lookup = etree.ElementDefaultClassLookup(element=CT_CustomProperties)
ct_parser = etree.XMLParser(remove_blank_text=True)
ct_parser.set_element_class_lookup(parser_lookup)


def ct_parse_xml(xml):
"""
Return root lxml element obtained by parsing XML character string in
*xml*, which can be either a Python 2.x string or unicode. The custom
parser is used, so custom element classes are produced for elements in
*xml* that have them.
"""
root_element = etree.fromstring(xml, ct_parser)
return root_element



class CustomPropertiesPart(XmlPart):
"""
Corresponds to part named ``/docProps/custom.xml``, containing the custom
document properties for this document package.
"""
@classmethod
def default(cls, package):
"""
Return a new |CustomPropertiesPart| object initialized with default
values for its base properties.
"""
custom_properties_part = cls._new(package)
custom_properties = custom_properties_part.custom_properties
return custom_properties_part

@property
def custom_properties(self):
"""
A |CustomProperties| object providing read/write access to the custom
properties contained in this custom properties part.
"""
return CustomProperties(self.element)

@classmethod
def load(cls, partname, content_type, blob, package):
element = ct_parse_xml(blob)
return cls(partname, content_type, element, package)

@classmethod
def _new(cls, package):
partname = PackURI('/docProps/custom.xml')
content_type = CT.OPC_CUSTOM_PROPERTIES
customProperties = CT_CustomProperties.new()
return CustomPropertiesPart(
partname, content_type, customProperties, package
)
8 changes: 8 additions & 0 deletions docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ def register_element_cls(tag, cls):
namespace = element_class_lookup.get_namespace(nsmap[nspfx])
namespace[tagroot] = cls

def register_element_cls_ns(tag, ns, cls):
"""
Register *cls* to be constructed when the oxml parser encounters an
element with matching *tag*. *tag* is a string of the form
``nspfx:tagroot``, e.g. ``'w:document'``.
"""
namespace = element_class_lookup.get_namespace(ns)
namespace[tag] = cls

def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
"""
Expand Down
Loading