Skip to content
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

DEV-3861: Add Custom properties functionality #22

Merged
merged 15 commits into from
May 23, 2024
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
98 changes: 58 additions & 40 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ Release History

1.0.0 (2024-05-DD)
++++++++++++++++++
- change package name to python-docx-bb to avoid confusion with version

- change package name to ``python-docx-bb`` to avoid confusion with version
numbers and pypi
- DEV-3948: Merge in upstream through python-openxml/python-docx:1.1.2
- DEV-3948: Merge in upstream through ``python-openxml/python-docx:1.1.2``
- DEV-3861: add Custom Properties support from ``michael-koeller:feature/custom_properties``

+---------------------+------------------------------------------------------------------------------------------------+
| python-openxml | Changes |
Expand Down Expand Up @@ -42,48 +44,63 @@ Release History
| | - Add Section.iter_inner_content() |
+---------------------+------------------------------------------------------------------------------------------------+


0.4.7 (2023-07-19)
++++++++++++++++++
- DEV-2649: add shd tag to TcPr for colored table cells
- Fix "text" attr setter for CT_DR and CT_IR
- DEV-3177: get children anywhere for all_runs for CT_IR to support runs
nested inside of deletes
- DEV-2649: add ``w:shd`` tag to ``TcPr`` for colored table cells
- Fix "text" attr setter for ``CT_DR`` and ``CT_IR``
- DEV-3177: get children anywhere for all_runs for ``CT_IR`` to support runs nested
inside of deletes


0.4.6 (2023-05-26)
++++++++++++++++++
- DEV-3195: wrap int() call in float() so we can read float vals of size attrs
in e.g. w:spacing tags
- DEV-3195: wrap ``int()`` call in ``float()`` so we can read float vals of size attrs
in e.g. ``w:spacing`` tags


0.4.5 (2023-02-06)
++++++++++++++++++
- DEV-2853: fix `all_runs` methods of Ins and Del objects

- DEV-2853: fix ``all_runs`` methods of Ins and Del objects


0.4.4 (2022-09-14)
++++++++++++++++++
- DEV-1807: fix `text` property of CT_IR and CT_DR

- DEV-1807: fix ``text`` property of ``CT_IR`` and ``CT_DR``


0.4.3 (2022-05-13)
++++++++++++++++++
- DEV-1907: remove self-added `br` property from CT_R
+ having it there seemed to remove methods added by ZeroOrMore()

- DEV-1907: remove self-added ``br`` property from ``CT_R``
+ having it there seemed to remove methods added by ``ZeroOrMore()``


0.4.2 (2022-04-11)
++++++++++++++++++

- Adds attributes to access more information about formatting


0.4.1 (2022-01-31)
++++++++++++++++++
DEV-1405: Comments in edit transfer
- Adds all_runs property to Ins and Del objects
+ Allows us to get comments that affect runs inside Ins and Del
- changes `comments` property of Paragraph to use all_runs instead of runs
+ allows us to get comments from Ins and Del runs

- DEV-1405: Comments in edit transfer
- Adds all_runs property to ``Ins`` and ``Del`` objects
- Allows us to get comments that affect runs inside Ins and Del
- changes ``comments`` property of Paragraph to use ``all_runs`` instead of ``runs``
- allows us to get comments from Ins and Del runs


0.4.1-rc.1 (2022-01-21)
++++++++++++++++++
+++++++++++++++++++++++

- Adds ability to get all runs from Ins and Del objects
+ Allows us to get comments that affect runs inside Ins and Del


0.4 (2021-12-07)
++++++++++++++++++

Expand All @@ -93,30 +110,31 @@ DEV-1405: Comments in edit transfer

0.3 (2021-09-01)
++++++++++++++++++

- Upgrade BlackBoiler fork of bb-docx to python-openxml v0.8.11

+---------------------+---------------------------------------------------------------------------------------------------------------------------------------+
| python-openxml | Changes |
+=====================+=======================================================================================================================================+
| 0.8.11 (2021-05-15) | - Small build changes and Python 3.8 version changes like collections.abc location |
+---------------------+---------------------------------------------------------------------------------------------------------------------------------------+
| 0.8.10 (2019-01-08) | - Revert use of expanded package directory for default.docx to work around setup.py problem with filenames containing square brackets |
+---------------------+---------------------------------------------------------------------------------------------------------------------------------------+
| 0.8.9 (2019-01-08) | - Fix gap in MANIFEST.in that excluded default document template directory |
+---------------------+---------------------------------------------------------------------------------------------------------------------------------------+
| 0.8.8 (2019-01-07) | - Add support for headers and footers |
+---------------------+---------------------------------------------------------------------------------------------------------------------------------------+
| 0.8.7 (2018-08-18) | - Add _Row.height_rule |
| | - Add _Row.height |
| | - Add _Cell.vertical_alignment |
| | - Fix #455: increment next_id, don't fill gaps |
| | - Add #375: import docx failure on --OO optimization |
| | - Add #254: remove default zoom percentage |
| | - Add #266: miscellaneous documentation fixes |
| | - Add #175: refine MANIFEST.ini |
| | - Add #168: Unicode error on core-props in Python 2" |
+---------------------+---------------------------------------------------------------------------------------------------------------------------------------+
+---------------------+------------------------------------------------------------------------------------------------+
| python-openxml | Changes |
+=====================+================================================================================================+
| 0.8.11 (2021-05-15) | - Small build changes and Python 3.8 version changes like collections.abc location |
+---------------------+------------------------------------------------------------------------------------------------+
| 0.8.10 (2019-01-08) | - Revert use of expanded package directory for default.docx to work around setup.py problem |
| | with filenames containing square brackets |
+---------------------+------------------------------------------------------------------------------------------------+
| 0.8.9 (2019-01-08) | - Fix gap in MANIFEST.in that excluded default document template directory |
+---------------------+------------------------------------------------------------------------------------------------+
| 0.8.8 (2019-01-07) | - Add support for headers and footers |
+---------------------+------------------------------------------------------------------------------------------------+
| 0.8.7 (2018-08-18) | - Add _Row.height_rule |
| | - Add _Row.height |
| | - Add _Cell.vertical_alignment |
| | - Fix #455: increment next_id, don't fill gaps |
| | - Add #375: import docx failure on --OO optimization |
| | - Add #254: remove default zoom percentage |
| | - Add #266: miscellaneous documentation fixes |
| | - Add #175: refine MANIFEST.ini |
| | - Add #168: Unicode error on core-props in Python 2" |
+---------------------+------------------------------------------------------------------------------------------------+


0.2 (2019-04-19)
++++++++++++++++++
Expand Down
40 changes: 40 additions & 0 deletions features/doc-customprops.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Feature: Read and write custom document properties
In order to find documents and make them manageable by digital means
As a developer using python-docx
I need to access and modify the Dublin Core metadata for a document


Scenario: read the custom properties of a document
Given a document having known custom properties
Then I can access the custom properties object
And the expected custom properties are visible
And the custom property values match the known values


Scenario: change the custom properties of a document
Given a document having known custom properties
When I assign new values to the custom properties
Then the custom property values match the new values


Scenario: a default custom properties part is added if doc doesn't have one
Given a document having no custom properties part
When I access the custom properties object
Then a custom properties part with no values is added


Scenario: set custom properties on a document that doesn't have one
Given a document having no custom properties part
When I assign new values to the custom properties
Then the custom property values match the new values


Scenario: iterate the custom properties of a document
Given a document having known custom properties
Then I can iterate the custom properties object


Scenario: delete an existing custom property
Given a document having known custom properties
When I delete an existing custom property
Then the custom property is missing in the remaining list of custom properties
125 changes: 125 additions & 0 deletions features/steps/customprops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# encoding: utf-8

"""
Gherkin step implementations for custom properties-related features.
"""

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

from datetime import datetime, timedelta

from behave import given, then, when

from docx import Document
from docx.opc.customprops import CustomProperties

from helpers import test_docx


# given ===================================================

@given('a document having known custom properties')
def given_a_document_having_known_custom_properties(context):
context.document = Document(test_docx('doc-customprops'))
context.exp_prop_names = [
'AppVersion', 'CustomPropBool', 'CustomPropInt', 'CustomPropString',
'DocSecurity', 'HyperlinksChanged', 'LinksUpToDate', 'ScaleCrop', 'ShareDoc'
]


@given('a document having no custom properties part')
def given_a_document_having_no_custom_properties_part(context):
context.document = Document(test_docx('doc-no-customprops'))
context.exp_prop_names = []


# when ====================================================

@when('I access the custom properties object')
def when_I_access_the_custom_properties_object(context):
context.document.custom_properties


@when("I assign new values to the custom properties")
def when_I_assign_new_values_to_the_custom_properties(context):
context.propvals = (
('CustomPropBool', False),
('CustomPropInt', 1),
('CustomPropString', 'Lorem ipsum'),
)
custom_properties = context.document.custom_properties
for name, value in context.propvals:
custom_properties[name] = value


@when("I delete an existing custom property")
def when_I_delete_an_existing_custom_property(context):
custom_properties = context.document.custom_properties
del custom_properties["CustomPropInt"]
context.prop_name = "CustomPropInt"


# then ====================================================

@then('a custom properties part with no values is added')
def then_a_custom_properties_part_with_no_values_is_added(context):
custom_properties = context.document.custom_properties
assert len(custom_properties) == 0


@then('I can access the custom properties object')
def then_I_can_access_the_custom_properties_object(context):
custom_properties = context.document.custom_properties
assert isinstance(custom_properties, CustomProperties)


@then('the expected custom properties are visible')
def then_the_expected_custom_properties_are_visible(context):
custom_properties = context.document.custom_properties
exp_prop_names = context.exp_prop_names
for name in exp_prop_names:
assert custom_properties.lookup(name) is not None


@then('the custom property values match the known values')
def then_the_custom_property_values_match_the_known_values(context):
known_propvals = (
('CustomPropBool', True),
('CustomPropInt', 13),
('CustomPropString', 'Test String'),
)
custom_properties = context.document.custom_properties
for name, expected_value in known_propvals:
value = custom_properties[name]
assert value == expected_value, (
"got '%s' for custom property '%s'" % (value, name)
)


@then('the custom property values match the new values')
def then_the_custom_property_values_match_the_new_values(context):
custom_properties = context.document.custom_properties
for name, expected_value in context.propvals:
value = custom_properties[name]
assert value == expected_value, (
"got '%s' for custom property '%s'" % (value, name)
)


@then('I can iterate the custom properties object')
def then_I_can_iterate_the_custom_properties_object(context):
custom_properties = context.document.custom_properties
exp_prop_names = context.exp_prop_names
act_prop_names = [name for name in custom_properties]
assert act_prop_names == exp_prop_names


@then('the custom property is missing in the remaining list of custom properties')
def then_the_custom_property_is_missing_in_the_remaining_list_of_custom_properties(context):
custom_properties = context.document.custom_properties
prop_name = context.prop_name
assert prop_name is not None
assert custom_properties.lookup(prop_name) is None
assert prop_name not in [name for name in custom_properties]
Binary file added features/steps/test_files/doc-customprops.docx
Binary file not shown.
Binary file added features/steps/test_files/doc-no-customprops.docx
Binary file not shown.
4 changes: 3 additions & 1 deletion src/docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
if TYPE_CHECKING:
from docx.opc.part import Part

__version__ = "1.0.0-dev"
__version__ = "1.0.0-rc1"


__all__ = ["Document"]
Expand All @@ -25,6 +25,7 @@
from docx.opc.constants import 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
from docx.parts.image import ImagePart
Expand All @@ -44,6 +45,7 @@ def part_class_selector(content_type: str, reltype: str) -> Type[Part] | None:
PartFactory.part_class_selector = part_class_selector
PartFactory.part_type_for[CT.WML_COMMENTS] = CommentsPart
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 src/docx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ def core_properties(self):
"""A |CoreProperties| object providing Dublin Core properties of document."""
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):
"""The |InlineShapes| collection for this document.
Expand Down
1 change: 1 addition & 0 deletions src/docx/opc/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class CONTENT_TYPE:
)
OFC_VML_DRAWING = "application/vnd.openxmlformats-officedocument.vmlDrawing"
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-certificate"
)
Expand Down
Loading