Skip to content

Implements extended-properties #1206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
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
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
.coverage
.vscode
.idea
.pytest_cache/
/dist/
/docs/.build/
/*.egg-info
*.pyc
.pytest_cache/
_scratch/
Session.vim
/.tox/
/build/
build-install.bat
app.xml
core.xml
editor-appproperties.py
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions .idea/aws.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions .idea/python-docx.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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.extendedprops import ExtendedPropertiesPart

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_EXTENDED_PROPERTIES] = ExtendedPropertiesPart
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 All @@ -37,6 +39,7 @@ def part_class_selector(content_type, reltype):
del (
CT,
CorePropertiesPart,
ExtendedPropertiesPart,
DocumentPart,
FooterPart,
HeaderPart,
Expand Down
8 changes: 8 additions & 0 deletions docx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ def core_properties(self):
properties of this document.
"""
return self._part.core_properties

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

@property
def inline_shapes(self):
Expand Down
7 changes: 5 additions & 2 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_EXTENDED_PROPERTIES = (
'application/vnd.openxmlformats-officedocument.extended-properties+xml'
)
OPC_DIGITAL_SIGNATURE_CERTIFICATE = (
'application/vnd.openxmlformats-package.digital-signature-certificat'
'e'
Expand Down Expand Up @@ -409,8 +412,8 @@ class RELATIONSHIP_TYPE(object):
'/control'
)
CORE_PROPERTIES = (
'http://schemas.openxmlformats.org/package/2006/relationships/metada'
'ta/core-properties'
'http://schemas.openxmlformats.org/package/2006/relationships/metadata'
'/core-properties'
)
CUSTOM_PROPERTIES = (
'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
Expand Down
97 changes: 97 additions & 0 deletions docx/opc/extendedprops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# encoding: utf-8

"""
The :mod:`pptx.opc.extendedprops` module defines the ExtendedProperties class, which
coheres around the concerns of reading and writing application document
properties to and from the app.xml part of a .docx file.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import re


def _get_property_name_from_tag(tag):
name = tag.split('}')[-1]
snake_name = ''.join(['_' + i.lower() if i.isupper() else i for i in name]).lstrip('_')
return snake_name


class ExtendedProperties(object):
"""
Corresponds to part named ``/docProps/app.xml``, containing the extended
document properties for this document package.
"""

def __init__(self, element):
self._element = element
self.template = None
self.manager = None
self.company = None
self.pages = None
self.words = None
self.characters = None
self.presentation_format = None
self.lines = None
self.paragraphs = None
self.slides = None
self.notes = None
self.total_time = None
self.hidden_slides = None
self.mm_clips = None
self.scale_crop = None
self.heading_pairs = None
self.titles_of_parts = None
self.links_up_to_date = None
self.characters_with_space = None
self.shared_doc = None
self.hyperlink_base = None
self.h_links = None
self.hyperlinks_changed = None
self.dig_sig = None
self.application = None
self.app_version = None
self.doc_security = None
self._property_elements = {}

for child in self._element:
property_name = _get_property_name_from_tag(child.tag)
if hasattr(self, property_name):
setattr(self, property_name, child.text)
self._property_elements[property_name] = child

def set_property(self, property_name, value):
if hasattr(self, property_name):
xml_element = self._property_elements.get(property_name)
if xml_element is not None:
xml_element.text = value
setattr(self, property_name, value)
else:
raise AttributeError(f"XML element not found for property '{property_name}'.")
else:
raise AttributeError(f"Property '{property_name}' not found in ExtendedProperties.")

# @property
# def total_time(self):
# return self._element[1].text
#
# @total_time.setter
# def total_time(self, value):
# self._element[1].text = value
#
# @property
# def template(self):
# return self._element[0].text
#
# @template.setter
# def template(self, value):
# self._element[0].text = value
#
# @property
# def pages(self):
# return self._element[2].text
#
# @pages.setter
# def pages(self, value):
# self._element[2].text = value

22 changes: 22 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.extendedprops import ExtendedPropertiesPart
from docx.opc.pkgreader import PackageReader
from docx.opc.pkgwriter import PackageWriter
from docx.opc.rel import Relationships
Expand Down Expand Up @@ -40,6 +41,14 @@ def core_properties(self):
Core properties for this document.
"""
return self._core_properties_part.core_properties

@property
def extended_properties(self):
"""
|AppProperties| object providing read/write access to the Dublin
App properties for this document.
"""
return self._extended_properties_part.extended_properties

def iter_rels(self):
"""
Expand Down Expand Up @@ -183,6 +192,19 @@ def _core_properties_part(self):
core_properties_part = CorePropertiesPart.default(self)
self.relate_to(core_properties_part, RT.CORE_PROPERTIES)
return core_properties_part

@property
def _extended_properties_part(self):
"""
|ExtendedPropertiesPart| object related to this package. Creates
a default app properties part if one is not present (not common).
"""
try:
return self.part_related_by(RT.EXTENDED_PROPERTIES)
except KeyError:
extended_properties_part = ExtendedPropertiesPart.default(self)
self.relate_to(extended_properties_part, RT.EXTENDED_PROPERTIES)
return extended_properties_part


class Unmarshaller(object):
Expand Down
1 change: 1 addition & 0 deletions docx/opc/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .shared import lazyproperty



class Part(object):
"""
Base class for package parts. Provides common properties and methods, but
Expand Down
60 changes: 60 additions & 0 deletions docx/opc/parts/extendedprops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# encoding: utf-8
# docx\opc\parts\extendedprops.py
"""
App properties part, corresponds to ``/docProps/app.xml`` part in package.
"""

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


from ..constants import CONTENT_TYPE as CT
from ..extendedprops import ExtendedProperties
from ..part import XmlPart
from ...oxml.extendedprops import CT_ExtendedProperties
from ..packuri import PackURI


class ExtendedPropertiesPart(XmlPart):
"""
Corresponds to part named ``/docProps/app.xml``, containing the app
document properties for this document package.
"""
@classmethod
def default(cls, package):
"""
Return a new |ExtendedPropertiesPart| object initialized with default
values for its base properties.
"""
extended_properties_part = cls._new(package)
extended_properties = extended_properties_part.extended_properties
extended_properties.total_time = '1'
# extended_properties.pages = '1'
# extended_properties.company = 'Company'
# extended_properties.manager = 'Manager'
# extended_properties.category = 'Category'
# extended_properties.presentation_format = 'Presentation Format'
# extended_properties.links_up_to_date = 'false'
# extended_properties.characters = '1'
# extended_properties.lines = '1'
# extended_properties.paragraphs = '1'

return extended_properties_part

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

@classmethod
def _new(cls, package):
partname = PackURI('/docProps/app.xml')
content_type = CT.OFC_EXTENDED_PROPERTIES
extended_properties = CT_ExtendedProperties.new()
return ExtendedPropertiesPart(
partname, content_type, extended_properties, package
)
Loading