diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml
new file mode 100644
index 000000000..521fffae6
--- /dev/null
+++ b/.github/workflows/pythonpublish.yml
@@ -0,0 +1,31 @@
+# This workflows will upload a Python Package using Twine when a release is created
+# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
+
+name: Publish on PYPI
+
+on:
+ release:
+ types: [created]
+
+jobs:
+ deploy:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.x'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install setuptools wheel twine
+ - name: Build and publish
+ env:
+ TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+ run: |
+ python setup.py sdist bdist_wheel
+ twine upload dist/*
diff --git a/.gitignore b/.gitignore
index e24445137..9ce46ccb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,8 @@
_scratch/
Session.vim
/.tox/
+/build/
+/tests/
+/features/
+/docs/
+/ref/
\ No newline at end of file
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 000000000..8e79b0ab8
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,14 @@
+## Bayoo-docx contributors
+
+============================================
+
+* **[Obay Daba](https://github.com/bayoog)**
+
+* **[Bassel Al Madani](https://github.com/pepos9)**
+
+* **[Tareq Ibrahim](https://github.com/idtareq)**
+
+* **[baltazarix](https://github.com/baltazarix)**
+
+* **[Ahmad Alwareh](https://github.com/ahmadalwareh)**
+
diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst
new file mode 100644
index 000000000..f52196ec2
--- /dev/null
+++ b/DESCRIPTION.rst
@@ -0,0 +1,42 @@
+Bayoo-docx
+
+
+Python library forked from `python-docx `_.
+
+The main purpose of the fork was to add implementation for comments and footnotes to the library
+
+Installation
+
+
+Use the package manager `pip `_ to install bayoo-docx.
+
+
+`pip install bayoo-docx`
+
+Usage
+
+
+::
+
+ import docx
+
+ document = docx.Document()
+
+ paragraph1 = document.add_paragraph('text') # create new paragraph
+
+ comment = paragraph.add_comment('comment',author='Obay Daba',initials= 'od') # add a comment on the entire paragraph
+
+ paragraph2 = document.add_paragraph('text') # create another paragraph
+
+ run = paragraph2.add_run('texty') add a run to the paragraph
+
+ run.add_comment('comment') # add a comment only for the run text
+
+ paragraph.add_footnote('footnote text') # add a footnote
+
+
+
+License
+
+
+`MIT `_
diff --git a/HISTORY.rst b/HISTORY.rst
index 5612cbf05..d94b29456 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,264 +1,24 @@
.. :changelog:
-Release History
----------------
-0.8.10 (2019-01-08)
-+++++++++++++++++++
+#Release History BayooG/bayoo-docx forked from (python-openxmm/python-docx)
-- Revert use of expanded package directory for default.docx to work around setup.py
- problem with filenames containing square brackets.
+0.2.8 (2020-05-02)
-0.8.9 (2019-01-08)
-++++++++++++++++++
+- add comments implementation on a run level
+- fix issue with comments date (comments dates are set to current date)
-- Fix gap in MANIFEST.in that excluded default document template directory
+0.2.4 (2019-9-4)
-0.8.8 (2019-01-07)
-++++++++++++++++++
+- loop over all the document chieldern (Paragraphs, Tables, Sections) with the right order `document.elements`
+- addons to Paragraph Object (delete, heading_level, merge_paragraph )
+- Add low-level implementation for comments part
+- Add oxml element for element and sub-elements
+- Add add_comment() method for docx.text.Paragraph
+- Add low-level implementation for footnotes part
+- Add oxml element for element and sub-elements
+- Add add_footnote() method for docx.text.Paragraph
-- 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.8.6 (2016-06-22)
-++++++++++++++++++
-
-- Add #257: add Font.highlight_color
-- Add #261: add ParagraphFormat.tab_stops
-- Add #303: disallow XML entity expansion
-
-
-0.8.5 (2015-02-21)
-++++++++++++++++++
-
-- Fix #149: KeyError on Document.add_table()
-- Fix #78: feature: add_table() sets cell widths
-- Add #106: feature: Table.direction (i.e. right-to-left)
-- Add #102: feature: add CT_Row.trPr
-
-
-0.8.4 (2015-02-20)
-++++++++++++++++++
-
-- Fix #151: tests won't run on PyPI distribution
-- Fix #124: default to inches on no TIFF resolution unit
-
-
-0.8.3 (2015-02-19)
-++++++++++++++++++
-
-- Add #121, #135, #139: feature: Font.color
-
-
-0.8.2 (2015-02-16)
-++++++++++++++++++
-
-- Fix #94: picture prints at wrong size when scaled
-- Extract `docx.document.Document` object from `DocumentPart`
-
- Refactor `docx.Document` from an object into a factory function for new
- `docx.document.Document object`. Extract methods from prior `docx.Document`
- and `docx.parts.document.DocumentPart` to form the new API class and retire
- `docx.Document` class.
-
-- Migrate `Document.numbering_part` to `DocumentPart.numbering_part`. The
- `numbering_part` property is not part of the published API and is an
- interim internal feature to be replaced in a future release, perhaps with
- something like `Document.numbering_definitions`. In the meantime, it can
- now be accessed using ``Document.part.numbering_part``.
-
-
-0.8.1 (2015-02-10)
-++++++++++++++++++
-
-- Fix #140: Warning triggered on Document.add_heading/table()
-
-
-0.8.0 (2015-02-08)
-++++++++++++++++++
-
-- Add styles. Provides general capability to access and manipulate paragraph,
- character, and table styles.
-
-- Add ParagraphFormat object, accessible on Paragraph.paragraph_format, and
- providing the following paragraph formatting properties:
-
- + paragraph alignment (justfification)
- + space before and after paragraph
- + line spacing
- + indentation
- + keep together, keep with next, page break before, and widow control
-
-- Add Font object, accessible on Run.font, providing character-level
- formatting including:
-
- + typeface (e.g. 'Arial')
- + point size
- + underline
- + italic
- + bold
- + superscript and subscript
-
-The following issues were retired:
-
-- Add feature #56: superscript/subscript
-- Add feature #67: lookup style by UI name
-- Add feature #98: Paragraph indentation
-- Add feature #120: Document.styles
-
-**Backward incompatibilities**
-
-Paragraph.style now returns a Style object. Previously it returned the style
-name as a string. The name can now be retrieved using the Style.name
-property, for example, `paragraph.style.name`.
-
-
-0.7.6 (2014-12-14)
-++++++++++++++++++
-
-- Add feature #69: Table.alignment
-- Add feature #29: Document.core_properties
-
-
-0.7.5 (2014-11-29)
-++++++++++++++++++
-
-- Add feature #65: _Cell.merge()
-
-
-0.7.4 (2014-07-18)
-++++++++++++++++++
-
-- Add feature #45: _Cell.add_table()
-- Add feature #76: _Cell.add_paragraph()
-- Add _Cell.tables property (read-only)
-
-
-0.7.3 (2014-07-14)
-++++++++++++++++++
-
-- Add Table.autofit
-- Add feature #46: _Cell.width
-
-
-0.7.2 (2014-07-13)
-++++++++++++++++++
-
-- Fix: Word does not interpret as line feed
-
-
-0.7.1 (2014-07-11)
-++++++++++++++++++
-
-- Add feature #14: Run.add_picture()
-
-
-0.7.0 (2014-06-27)
-++++++++++++++++++
-
-- Add feature #68: Paragraph.insert_paragraph_before()
-- Add feature #51: Paragraph.alignment (read/write)
-- Add feature #61: Paragraph.text setter
-- Add feature #58: Run.add_tab()
-- Add feature #70: Run.clear()
-- Add feature #60: Run.text setter
-- Add feature #39: Run.text and Paragraph.text interpret '\n' and '\t' chars
-
-
-0.6.0 (2014-06-22)
-++++++++++++++++++
-
-- Add feature #15: section page size
-- Add feature #66: add section
-- Add page margins and page orientation properties on Section
-- Major refactoring of oxml layer
-
-
-0.5.3 (2014-05-10)
-++++++++++++++++++
-
-- Add feature #19: Run.underline property
-
-
-0.5.2 (2014-05-06)
-++++++++++++++++++
-
-- Add feature #17: character style
-
-
-0.5.1 (2014-04-02)
-++++++++++++++++++
-
-- Fix issue #23, `Document.add_picture()` raises ValueError when document
- contains VML drawing.
-
-
-0.5.0 (2014-03-02)
-++++++++++++++++++
-
-- Add 20 tri-state properties on Run, including all-caps, double-strike,
- hidden, shadow, small-caps, and 15 others.
-
-
-0.4.0 (2014-03-01)
-++++++++++++++++++
-
-- Advance from alpha to beta status.
-- Add pure-python image header parsing; drop Pillow dependency
-
-
-0.3.0a5 (2014-01-10)
-++++++++++++++++++++++
-
-- Hotfix: issue #4, Document.add_picture() fails on second and subsequent
- images.
-
-
-0.3.0a4 (2014-01-07)
-++++++++++++++++++++++
-
-- Complete Python 3 support, tested on Python 3.3
-
-
-0.3.0a3 (2014-01-06)
-++++++++++++++++++++++
-
-- Fix setup.py error on some Windows installs
-
-
-0.3.0a1 (2014-01-05)
-++++++++++++++++++++++
-
-- Full object-oriented rewrite
-- Feature-parity with prior version
-- text: add paragraph, run, text, bold, italic
-- table: add table, add row, add column
-- styles: specify style for paragraph, table
-- picture: add inline picture, auto-scaling
-- breaks: add page break
-- tests: full pytest and behave-based 2-layer test suite
-
-
-0.3.0dev1 (2013-12-14)
-++++++++++++++++++++++
-
-- Round-trip .docx file, preserving all parts and relationships
-- Load default "template" .docx on open with no filename
-- Open from stream and save to stream (file-like object)
-- Add paragraph at and of document
diff --git a/LICENSE b/LICENSE
index 67ebe716e..dece2863c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2013 Steve Canny, https://github.com/scanny
+Copyright (c) 2019 Obay Daba, https://github.com/bayoog
+forked from https://github.com/python-openxml/python-docx
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..74fb0842e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+Bayoo-docx
+==========
+
+Python library forked from [python-docx](https://github.com/python-openxml/python-docx).
+
+The main purpose of the fork was to add implementation for comments and footnotes to the library
+
+Installation
+------------
+
+Use the package manager [pip](https://pypi.org/project/bayoo-docx/) to install bayoo-docx.
+
+
+`pip install bayoo-docx`
+
+Usage:
+-----
+
+
+
+ import docx
+
+ document = docx.Document()
+
+ paragraph = document.add_paragraph('text') # create new paragraph
+
+ comment = paragraph.add_comment('comment',author='Obay Daba',initials= 'od') # add a comment on the entire paragraph
+
+ paragraph2 = document.add_paragraph('text') # create another paragraph
+
+ run = paragraph2.add_run('text1') #add a run to the paragraph
+
+ run.add_comment('comment') # add a comment only for the run text
+
+ run.add_comment('comment2')
+
+ run_comments = run.comments
+
+ paragraph.add_footnote('footnote text') # add a footnote
+
+
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 82d1f0bd7..000000000
--- a/README.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. image:: https://travis-ci.org/python-openxml/python-docx.svg?branch=master
- :target: https://travis-ci.org/python-openxml/python-docx
-
-*python-docx* is a Python library for creating and updating Microsoft Word
-(.docx) files.
-
-More information is available in the `python-docx documentation`_.
-
-.. _`python-docx documentation`:
- https://python-docx.readthedocs.org/en/latest/
diff --git a/docx/__init__.py b/docx/__init__.py
index 4dae2946b..ee8a52ad1 100644
--- a/docx/__init__.py
+++ b/docx/__init__.py
@@ -2,7 +2,7 @@
from docx.api import Document # noqa
-__version__ = '0.8.10'
+__version__ = '0.2.20'
# register custom Part classes with opc package reader
@@ -17,6 +17,8 @@
from docx.parts.numbering import NumberingPart
from docx.parts.settings import SettingsPart
from docx.parts.styles import StylesPart
+from docx.parts.comments import CommentsPart
+from docx.parts.footnotes import FootnotesPart
def part_class_selector(content_type, reltype):
@@ -26,6 +28,7 @@ def part_class_selector(content_type, reltype):
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.WML_DOCUMENT_MAIN] = DocumentPart
PartFactory.part_type_for[CT.WML_FOOTER] = FooterPart
@@ -33,6 +36,7 @@ def part_class_selector(content_type, reltype):
PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart
PartFactory.part_type_for[CT.WML_SETTINGS] = SettingsPart
PartFactory.part_type_for[CT.WML_STYLES] = StylesPart
+PartFactory.part_type_for[CT.WML_FOOTNOTES] = FootnotesPart
del (
CT,
@@ -40,6 +44,8 @@ def part_class_selector(content_type, reltype):
DocumentPart,
FooterPart,
HeaderPart,
+ FootnotesPart,
+ CommentsPart,
NumberingPart,
PartFactory,
SettingsPart,
diff --git a/docx/api.py b/docx/api.py
index 63e18c406..1cc5fad34 100644
--- a/docx/api.py
+++ b/docx/api.py
@@ -35,3 +35,15 @@ def _default_docx_path():
"""
_thisdir = os.path.split(__file__)[0]
return os.path.join(_thisdir, 'templates', 'default.docx')
+
+
+def element(element, part):
+ if str(type(element)) == "":
+ from .text.paragraph import Paragraph
+ return Paragraph(element, part)
+ elif str(type(element)) == "":
+ from .table import Table
+ return Table(element, part)
+ elif str(type(element)) == "":
+ from .section import Section
+ return Section(element, part)
\ No newline at end of file
diff --git a/docx/blkcntnr.py b/docx/blkcntnr.py
index a80903e52..5c9810060 100644
--- a/docx/blkcntnr.py
+++ b/docx/blkcntnr.py
@@ -9,9 +9,10 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from docx.oxml.table import CT_Tbl
+from docx.oxml.ns import qn
from docx.shared import Parented
from docx.text.paragraph import Paragraph
-
+from docx.api import element
class BlockItemContainer(Parented):
"""Base class for proxy objects that can contain block items.
@@ -66,10 +67,23 @@ def tables(self):
"""
from .table import Table
return [Table(tbl, self) for tbl in self._element.tbl_lst]
+ @property
+ def elements(self):
+ """
+ A list containing the elements in this container (paragraph and tables), in document order.
+ """
+ return [element(item,self.part) for item in self._element.getchildren()]
+
+
+ @property
+ def abstractNumIds(self):
+ return [numId for numId in self.part.numbering_part.element.iterchildren(qn('w:abstractNum'))]
+
def _add_paragraph(self):
"""
Return a paragraph newly added to the end of the content in this
container.
"""
return Paragraph(self._element.add_p(), self)
+
diff --git a/docx/document.py b/docx/document.py
index 6493c458b..a35c85b72 100644
--- a/docx/document.py
+++ b/docx/document.py
@@ -4,6 +4,8 @@
from __future__ import absolute_import, division, print_function, unicode_literals
+from docx.oxml.ns import qn
+
from docx.blkcntnr import BlockItemContainer
from docx.enum.section import WD_SECTION
from docx.enum.text import WD_BREAK
@@ -101,6 +103,23 @@ def core_properties(self):
"""
return self._part.core_properties
+ @property
+ def comments_part(self):
+ """
+ A |Comments| object providing read/write access to the core
+ properties of this document.
+ """
+ return self.part.comments_part
+
+ # @property
+ # def footnotes_part(self):
+ # """
+ # A |Footnotes| object providing read/write access to the core
+ # properties of this document.
+ # """
+ # return self.part._footnotes_part
+
+
@property
def inline_shapes(self):
"""
@@ -165,6 +184,23 @@ def tables(self):
"""
return self._body.tables
+ @property
+ def elements(self):
+ return self._body.elements
+
+ @property
+ def abstractNumIds(self):
+ """
+ Returns list of all the 'w:abstarctNumId' of this document
+ """
+ return self._body.abstractNumIds
+
+ @property
+ def last_abs_num(self):
+ last = self.abstractNumIds[-1]
+ val = last.attrib.get(qn('w:abstractNumId'))
+ return last, val
+
@property
def _block_width(self):
"""
diff --git a/docx/image/__init__.py b/docx/image/__init__.py
index 8ab3ada68..30b8d45d5 100644
--- a/docx/image/__init__.py
+++ b/docx/image/__init__.py
@@ -14,7 +14,7 @@
from docx.image.jpeg import Exif, Jfif
from docx.image.png import Png
from docx.image.tiff import Tiff
-
+from docx.image.emf import Emf
SIGNATURES = (
# class, offset, signature_bytes
@@ -26,4 +26,5 @@
(Tiff, 0, b'MM\x00*'), # big-endian (Motorola) TIFF
(Tiff, 0, b'II*\x00'), # little-endian (Intel) TIFF
(Bmp, 0, b'BM'),
+ (Emf, 40, b' EMF')
)
diff --git a/docx/image/constants.py b/docx/image/constants.py
index 90b469705..97d40e314 100644
--- a/docx/image/constants.py
+++ b/docx/image/constants.py
@@ -102,7 +102,7 @@ class MIME_TYPE(object):
JPEG = 'image/jpeg'
PNG = 'image/png'
TIFF = 'image/tiff'
-
+ EMF = 'image/emf'
class PNG_CHUNK_TYPE(object):
"""
diff --git a/docx/image/emf.py b/docx/image/emf.py
new file mode 100644
index 000000000..e2701c04f
--- /dev/null
+++ b/docx/image/emf.py
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+from __future__ import absolute_import, division, print_function
+
+from .constants import MIME_TYPE
+from .exceptions import InvalidImageStreamError
+from .helpers import BIG_ENDIAN, StreamReader
+from .image import BaseImageHeader
+import struct
+
+class Emf(BaseImageHeader):
+ """
+ Image header parser for PNG images
+ """
+ @property
+ def content_type(self):
+ """
+ MIME content type for this image, unconditionally `image/png` for
+ PNG images.
+ """
+ return MIME_TYPE.EMF
+
+ @property
+ def default_ext(self):
+ """
+ Default filename extension, always 'png' for PNG images.
+ """
+ return 'emf'
+
+ @classmethod
+ def from_stream(cls, stream,filename=None):
+ """
+ Return a |Emf| instance having header properties parsed from image in
+ *stream*.
+ """
+
+ """
+ @0 DWORD iType; // fixed
+ @4 DWORD nSize; // var
+ @8 RECTL rclBounds;
+ @24 RECTL rclFrame; // .01 millimeter units L T R B
+ @40 DWORD dSignature; // ENHMETA_SIGNATURE = 0x464D4520
+ DWORD nVersion;
+ DWORD nBytes;
+ DWORD nRecords;
+ WORD nHandles;
+ WORD sReserved;
+ DWORD nDescription;
+ DWORD offDescription;
+ DWORD nPalEntries;
+ SIZEL szlDevice;
+ SIZEL szlMillimeters;
+ """
+ stream.seek(0)
+ x = stream.read(40)
+ stream.seek(0)
+ iType,nSize = struct.unpack("ii",x[0:8])
+ rclBounds = struct.unpack("iiii",x[8:24])
+ rclFrame = struct.unpack("iiii",x[24:40])
+
+ dpi = 300
+ horz_dpi = dpi
+ vert_dpi = dpi
+ mmwidth = (rclFrame[2]-rclFrame[0])/100.0
+ mmheight = (rclFrame[3]-rclFrame[1])/100.0
+ px_width = int(mmwidth*dpi*0.03937008)
+ px_height = int(mmheight*dpi*0.03937008)
+
+ #1 dot/inch = 0.03937008 pixel/millimeter
+ return cls(px_width,px_height,horz_dpi,vert_dpi)
\ No newline at end of file
diff --git a/docx/image/image.py b/docx/image/image.py
index ba2158e72..3df31672f 100644
--- a/docx/image/image.py
+++ b/docx/image/image.py
@@ -188,7 +188,7 @@ def _ImageHeaderFactory(stream):
def read_32(stream):
stream.seek(0)
- return stream.read(32)
+ return stream.read(64)
header = read_32(stream)
for cls, offset, signature_bytes in SIGNATURES:
diff --git a/docx/opc/package.py b/docx/opc/package.py
index 7ba87bab5..653151642 100644
--- a/docx/opc/package.py
+++ b/docx/opc/package.py
@@ -10,6 +10,8 @@
from docx.opc.parts.coreprops import CorePropertiesPart
from docx.opc.pkgreader import PackageReader
from docx.opc.pkgwriter import PackageWriter
+from docx.parts.comments import CommentsPart
+from docx.parts.footnotes import FootnotesPart
from docx.opc.rel import Relationships
from docx.opc.shared import lazyproperty
@@ -183,6 +185,32 @@ 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 _comments_part(self):
+ """
+ |CommentsPart| object related to this package. Creates
+ a default Comments part if one is not present.
+ """
+ try:
+ return self.part_related_by(RT.COMMENTS)
+ except KeyError:
+ comments_part = CommentsPart.default(self)
+ self.relate_to(comments_part, RT.COMMENTS)
+ return comments_part
+
+ @property
+ def _footnotes_part(self):
+ """
+ |FootnotesPart| object related to this package. Creates
+ a default Comments part if one is not present.
+ """
+ try:
+ return self.part_related_by(RT.FOOTNOTES)
+ except KeyError:
+ footnotes_part = FootnotesPart.default(self)
+ self.relate_to(footnotes_part, RT.FOOTNOTES)
+ return footnotes_part
class Unmarshaller(object):
diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py
index 093c1b45b..21f610edf 100644
--- a/docx/oxml/__init__.py
+++ b/docx/oxml/__init__.py
@@ -76,11 +76,12 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:body', CT_Body)
register_element_cls('w:document', CT_Document)
-from .numbering import CT_Num, CT_Numbering, CT_NumLvl, CT_NumPr # noqa
+from .numbering import CT_Num, CT_AbstractNum, CT_Numbering, CT_NumLvl, CT_NumPr # noqa
register_element_cls('w:abstractNumId', CT_DecimalNumber)
register_element_cls('w:ilvl', CT_DecimalNumber)
register_element_cls('w:lvlOverride', CT_NumLvl)
register_element_cls('w:num', CT_Num)
+register_element_cls('w:abstractNum', CT_AbstractNum)
register_element_cls('w:numId', CT_DecimalNumber)
register_element_cls('w:numPr', CT_NumPr)
register_element_cls('w:numbering', CT_Numbering)
@@ -135,8 +136,11 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('wp:extent', CT_PositiveSize2D)
register_element_cls('wp:inline', CT_Inline)
-from .styles import CT_LatentStyles, CT_LsdException, CT_Style, CT_Styles # noqa
+from .styles import CT_DocDefaults, CT_RPrDefault, CT_PPrDefault, CT_LatentStyles, CT_LsdException, CT_Style, CT_Styles # noqa
register_element_cls('w:basedOn', CT_String)
+register_element_cls('w:docDefaults', CT_DocDefaults)
+register_element_cls('w:rPrDefault', CT_RPrDefault)
+register_element_cls('w:pPrDefault', CT_PPrDefault)
register_element_cls('w:latentStyles', CT_LatentStyles)
register_element_cls('w:locked', CT_OnOff)
register_element_cls('w:lsdException', CT_LsdException)
@@ -162,7 +166,11 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
CT_TcPr,
CT_TrPr,
CT_VMerge,
+ CT_TblMar,
CT_VerticalJc,
+ CT_TblBoarders,
+ CT_Bottom,
+ CT_TcBorders,
)
register_element_cls('w:bidiVisual', CT_OnOff)
register_element_cls('w:gridCol', CT_TblGridCol)
@@ -171,6 +179,8 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:tblGrid', CT_TblGrid)
register_element_cls('w:tblLayout', CT_TblLayoutType)
register_element_cls('w:tblPr', CT_TblPr)
+register_element_cls('w:tblW', CT_TblWidth)
+register_element_cls('w:tblCellMar', CT_TblMar)
register_element_cls('w:tblStyle', CT_String)
register_element_cls('w:tc', CT_Tc)
register_element_cls('w:tcPr', CT_TcPr)
@@ -180,6 +190,9 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:trPr', CT_TrPr)
register_element_cls('w:vAlign', CT_VerticalJc)
register_element_cls('w:vMerge', CT_VMerge)
+register_element_cls('w:tblBorders', CT_TblBoarders)
+register_element_cls('w:tcBorders', CT_TcBorders)
+register_element_cls('w:bottom', CT_Bottom)
from .text.font import ( # noqa
CT_Color,
@@ -246,3 +259,20 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:br', CT_Br)
register_element_cls('w:r', CT_R)
register_element_cls('w:t', CT_Text)
+register_element_cls('w:rPr', CT_RPr)
+
+
+from .comments import CT_Comments,CT_Com, CT_CRE, CT_CRS, CT_CRef
+register_element_cls('w:comments', CT_Comments)
+register_element_cls('w:comment', CT_Com)
+register_element_cls('w:commentRangeStart', CT_CRS)
+register_element_cls('w:commentRangeEnd', CT_CRE)
+register_element_cls('w:commentReference', CT_CRef)
+
+
+from .footnotes import CT_Footnotes, CT_Footnote, CT_FNR, CT_FootnoteRef
+
+register_element_cls('w:footnotes', CT_Footnotes)
+register_element_cls('w:footnote', CT_Footnote)
+register_element_cls('w:footnoteReference', CT_FNR)
+register_element_cls('w:footnoteRef', CT_FootnoteRef)
\ No newline at end of file
diff --git a/docx/oxml/comments.py b/docx/oxml/comments.py
new file mode 100644
index 000000000..214a88f20
--- /dev/null
+++ b/docx/oxml/comments.py
@@ -0,0 +1,127 @@
+"""
+Custom element classes related to the comments part
+"""
+
+from . import OxmlElement
+from .simpletypes import ST_DecimalNumber, ST_String
+from ..opc.constants import NAMESPACE
+from ..text.paragraph import Paragraph
+from ..text.run import Run
+from .xmlchemy import (
+ BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrMore, ZeroOrOne
+)
+
+class CT_Com(BaseOxmlElement):
+ """
+ A ```` element, a container for Comment properties
+ """
+ initials = RequiredAttribute('w:initials', ST_String)
+ _id = RequiredAttribute('w:id', ST_DecimalNumber)
+ date = RequiredAttribute('w:date', ST_String)
+ author = RequiredAttribute('w:author', ST_String)
+
+ p = ZeroOrOne('w:p', successors=('w:comment',))
+
+ @classmethod
+ def new(cls, initials, comm_id, date, author):
+ """
+ Return a new ```` element having _id of *comm_id* and having
+ the passed params as meta data
+ """
+ comment = OxmlElement('w:comment')
+ comment.initials = initials
+ comment.date = date
+ comment._id = comm_id
+ comment.author = author
+ return comment
+
+ def _add_p(self, text):
+ _p = OxmlElement('w:p')
+ _r = _p.add_r()
+ run = Run(_r,self)
+ run.text = text
+ self._insert_p(_p)
+ return _p
+
+ @property
+ def meta(self):
+ return [self.author, self.initials, self.date]
+
+ @property
+ def paragraph(self):
+ return Paragraph(self.p, self)
+
+
+class CT_Comments(BaseOxmlElement):
+ """
+ A ```` element, a container for Comments properties
+ """
+ comment = ZeroOrMore ('w:comment', successors=('w:comments',))
+
+ def add_comment(self,author, initials, date):
+ _next_id = self._next_commentId
+ comment = CT_Com.new(initials, _next_id, date, author)
+ comment = self._insert_comment(comment)
+
+ return comment
+
+ @property
+ def _next_commentId(self):
+ ids = self.xpath('./w:comment/@w:id')
+ len(ids)
+ _ids = [int(_str) for _str in ids]
+ _ids.sort()
+
+ try:
+ return _ids[-1] + 2
+ except:
+ return 0
+
+ def get_comment_by_id(self, _id):
+ namesapce = NAMESPACE().WML_MAIN
+ for c in self.findall('.//w:comment',{'w':namesapce}):
+ if c._id == _id:
+ return c
+ return None
+
+
+class CT_CRS(BaseOxmlElement):
+ """
+ A ```` element
+ """
+ _id = RequiredAttribute('w:id', ST_DecimalNumber)
+
+ @classmethod
+ def new(cls, _id):
+ commentRangeStart = OxmlElement('w:commentRangeStart')
+ commentRangeStart._id =_id
+
+ return commentRangeStart
+
+class CT_CRE(BaseOxmlElement):
+ """
+ A ``w:commentRangeEnd`` element
+ """
+ _id = RequiredAttribute('w:id', ST_DecimalNumber)
+
+
+ @classmethod
+ def new(cls, _id):
+ commentRangeEnd = OxmlElement('w:commentRangeEnd')
+ commentRangeEnd._id =_id
+ return commentRangeEnd
+
+
+class CT_CRef(BaseOxmlElement):
+ """
+ w:commentReference
+ """
+ _id = RequiredAttribute('w:id', ST_DecimalNumber)
+
+ @classmethod
+ def new (cls, _id):
+ commentReference = OxmlElement('w:commentReference')
+ commentReference._id =_id
+ return commentReference
+
+
diff --git a/docx/oxml/footnotes.py b/docx/oxml/footnotes.py
new file mode 100644
index 000000000..90c791bc3
--- /dev/null
+++ b/docx/oxml/footnotes.py
@@ -0,0 +1,89 @@
+"""
+Custom element classes related to the footnotes part
+"""
+
+
+from . import OxmlElement
+from .simpletypes import ST_DecimalNumber, ST_String
+from ..text.paragraph import Paragraph
+from ..text.run import Run
+from ..opc.constants import NAMESPACE
+from .xmlchemy import (
+ BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrMore, ZeroOrOne
+)
+
+
+class CT_Footnotes(BaseOxmlElement):
+ """
+ A ```` element, a container for Footnotes properties
+ """
+
+ footnote = ZeroOrMore ('w:footnote', successors=('w:footnotes',))
+
+ @property
+ def _next_id(self):
+ ids = self.xpath('./w:footnote/@w:id')
+
+ return int(ids[-1]) + 1
+
+ def add_footnote(self):
+ _next_id = self._next_id
+ footnote = CT_Footnote.new(_next_id)
+ footnote = self._insert_footnote(footnote)
+ return footnote
+
+ def get_footnote_by_id(self, _id):
+ namesapce = NAMESPACE().WML_MAIN
+ for fn in self.findall('.//w:footnote', {'w':namesapce}):
+ if fn._id == _id:
+ return fn
+ return None
+
+class CT_Footnote(BaseOxmlElement):
+ """
+ A ```` element, a container for Footnote properties
+ """
+ _id = RequiredAttribute('w:id', ST_DecimalNumber)
+ p = ZeroOrOne('w:p', successors=('w:footnote',))
+
+ @classmethod
+ def new(cls, _id):
+ footnote = OxmlElement('w:footnote')
+ footnote._id = _id
+
+ return footnote
+
+ def _add_p(self, text):
+ _p = OxmlElement('w:p')
+ _p.footnote_style()
+
+ _r = _p.add_r()
+ _r.footnote_style()
+ _r = _p.add_r()
+ _r.add_footnoteRef()
+
+ run = Run(_r, self)
+ run.text = text
+
+ self._insert_p(_p)
+ return _p
+
+ @property
+ def paragraph(self):
+ return Paragraph(self.p, self)
+
+class CT_FNR(BaseOxmlElement):
+ _id = RequiredAttribute('w:id', ST_DecimalNumber)
+
+ @classmethod
+ def new (cls, _id):
+ footnoteReference = OxmlElement('w:footnoteReference')
+ footnoteReference._id = _id
+ return footnoteReference
+
+class CT_FootnoteRef (BaseOxmlElement):
+
+ @classmethod
+ def new (cls):
+ ref = OxmlElement('w:footnoteRef')
+ return ref
\ No newline at end of file
diff --git a/docx/oxml/numbering.py b/docx/oxml/numbering.py
index aeedfa9a0..6f26ee150 100644
--- a/docx/oxml/numbering.py
+++ b/docx/oxml/numbering.py
@@ -45,6 +45,13 @@ def new(cls, num_id, abstractNum_id):
return num
+class CT_AbstractNum(BaseOxmlElement):
+ """
+ ```` element, which represents an abstract numbering definition that defines most of the formatting details.
+ """
+ abstractNumId = RequiredAttribute('w:abstractNumId', ST_DecimalNumber)
+
+
class CT_NumLvl(BaseOxmlElement):
"""
```` element, which identifies a level in a list
@@ -94,6 +101,7 @@ class CT_Numbering(BaseOxmlElement):
```` element, the root element of a numbering part, i.e.
numbering.xml
"""
+ abstractNum = ZeroOrMore('w:abstractNum', successors=('w:num',))
num = ZeroOrMore('w:num', successors=('w:numIdMacAtCleanup',))
def add_num(self, abstractNum_id):
diff --git a/docx/oxml/section.py b/docx/oxml/section.py
index fc953e74d..fd889aa48 100644
--- a/docx/oxml/section.py
+++ b/docx/oxml/section.py
@@ -54,7 +54,6 @@ class CT_PageSz(BaseOxmlElement):
'w:orient', WD_ORIENTATION, default=WD_ORIENTATION.PORTRAIT
)
-
class CT_SectPr(BaseOxmlElement):
"""`w:sectPr` element, the container element for section properties"""
@@ -92,6 +91,7 @@ def add_headerReference(self, type_, rId):
headerReference.rId = rId
return headerReference
+
@property
def bottom_margin(self):
"""
diff --git a/docx/oxml/styles.py b/docx/oxml/styles.py
index 6f27e45eb..4a7483bae 100644
--- a/docx/oxml/styles.py
+++ b/docx/oxml/styles.py
@@ -31,6 +31,17 @@ def styleId_from_name(name):
}.get(name, name.replace(' ', ''))
+class CT_DocDefaults(BaseOxmlElement):
+ _tag_seq = ('w:rPrDefault', 'w:pPrDefault')
+ rPrDefault = ZeroOrOne('w:rPrDefault', successors=(_tag_seq[1:]))
+ pPrDefault = ZeroOrOne('w:pPrDefault', successors=())
+
+class CT_RPrDefault(BaseOxmlElement):
+ rPr = ZeroOrOne('w:rPr', successors=())
+
+class CT_PPrDefault(BaseOxmlElement):
+ pPr = ZeroOrOne('w:pPr', successors=())
+
class CT_LatentStyles(BaseOxmlElement):
"""
`w:latentStyles` element, defining behavior defaults for latent styles
@@ -292,6 +303,7 @@ class CT_Styles(BaseOxmlElement):
styles.xml
"""
_tag_seq = ('w:docDefaults', 'w:latentStyles', 'w:style')
+ docDefaults = ZeroOrOne('w:docDefaults', successors=_tag_seq[1:])
latentStyles = ZeroOrOne('w:latentStyles', successors=_tag_seq[2:])
style = ZeroOrMore('w:style', successors=())
del _tag_seq
diff --git a/docx/oxml/table.py b/docx/oxml/table.py
index e55bf9126..387b05063 100644
--- a/docx/oxml/table.py
+++ b/docx/oxml/table.py
@@ -7,12 +7,13 @@
)
from . import parse_xml
+from . import OxmlElement
from ..enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE
from ..exceptions import InvalidSpanError
-from .ns import nsdecls, qn
+from .ns import nsdecls, qn, nsmap
from ..shared import Emu, Twips
from .simpletypes import (
- ST_Merge, ST_TblLayoutType, ST_TblWidth, ST_TwipsMeasure, XsdInt
+ ST_Merge, ST_TblLayoutType, ST_TblWidth, ST_TwipsMeasure, XsdInt, ST_String
)
from .xmlchemy import (
BaseOxmlElement, OneAndOnlyOne, OneOrMore, OptionalAttribute,
@@ -233,6 +234,20 @@ def _tcs_xml(cls, col_count, col_width):
) % col_width.twips
return xml
+ @property
+ def _section(self):
+ body = self.getparent()
+ sections = body.findall('.//w:sectPr', {'w':nsmap['w']})
+ if len(sections) == 1:
+ return sections[0]
+ else:
+ tbl_index = body.index(self)
+ for i,sect in enumerate(sections):
+ if i == len(sections) - 1 :
+ return sect
+ else:
+ if body.index(sect.getparent().getparent()) > tbl_index:
+ return sect
class CT_TblGrid(BaseOxmlElement):
"""
@@ -265,6 +280,8 @@ class CT_TblLayoutType(BaseOxmlElement):
"""
type = OptionalAttribute('w:type', ST_TblLayoutType)
+class CT_TblBoarders(BaseOxmlElement):
+ pass
class CT_TblPr(BaseOxmlElement):
"""
@@ -280,8 +297,11 @@ class CT_TblPr(BaseOxmlElement):
)
tblStyle = ZeroOrOne('w:tblStyle', successors=_tag_seq[1:])
bidiVisual = ZeroOrOne('w:bidiVisual', successors=_tag_seq[4:])
+ tblW =ZeroOrOne ('w:tblW', successors=('w:tblPr',))
+ tblCellMar = ZeroOrOne('w:tblCellMar', successors=('w:tblPr',))
jc = ZeroOrOne('w:jc', successors=_tag_seq[8:])
tblLayout = ZeroOrOne('w:tblLayout', successors=_tag_seq[13:])
+ tblBorders = ZeroOrOne('w:tblBorders', successors=('w:tblPr',))
del _tag_seq
@property
@@ -747,6 +767,29 @@ def _tr_idx(self):
"""
return self._tbl.tr_lst.index(self._tr)
+class CT_TcBorders(BaseOxmlElement):
+ """
+ element
+ """
+ top = ZeroOrOne('w:top')
+ start = ZeroOrOne('w:start')
+ bottom = ZeroOrOne('w:bottom',successors=('w:tblPr',) )
+ end = ZeroOrOne('w:end')
+
+
+ def new(cls):
+ """
+ Return a new ```` element
+ """
+ return parse_xml(
+ '\n'
+ '' % nsdecls('w')
+ )
+
+ def add_bottom_border(self, val, sz):
+ bottom = CT_Bottom.new ( val, sz)
+ return self._insert_bottom(bottom)
+
class CT_TcPr(BaseOxmlElement):
"""
@@ -760,8 +803,10 @@ class CT_TcPr(BaseOxmlElement):
)
tcW = ZeroOrOne('w:tcW', successors=_tag_seq[2:])
gridSpan = ZeroOrOne('w:gridSpan', successors=_tag_seq[3:])
+ tcBorders = ZeroOrOne('w:tcBorders', successors = ('w:tcPr',))
vMerge = ZeroOrOne('w:vMerge', successors=_tag_seq[5:])
vAlign = ZeroOrOne('w:vAlign', successors=_tag_seq[12:])
+
del _tag_seq
@property
@@ -892,3 +937,31 @@ class CT_VMerge(BaseOxmlElement):
```` element, specifying vertical merging behavior of a cell.
"""
val = OptionalAttribute('w:val', ST_Merge, default=ST_Merge.CONTINUE)
+
+
+class CT_TblMar(BaseOxmlElement):
+ """
+ ```` element
+ """
+ left = ZeroOrOne('w:left', successors=('w:tblCellMar',))
+ right = ZeroOrOne('w:write', successors=('w:tblCellMar',))
+
+
+class CT_Bottom(BaseOxmlElement):
+ """
+ element
+ """
+ val= OptionalAttribute('w:val', ST_String)
+ sz= OptionalAttribute('w:sz', ST_String)
+ space = OptionalAttribute('w:space', ST_String)
+ color = OptionalAttribute('w:color', ST_String)
+
+ @classmethod
+ def new(cls, val, sz):
+ bottom = OxmlElement('w:bottom')
+ bottom.val = val
+ bottom.sz = sz
+ bottom.space = "0"
+ bottom.color = "auto"
+
+ return bottom
diff --git a/docx/oxml/text/font.py b/docx/oxml/text/font.py
index 810ec2b30..889eac098 100644
--- a/docx/oxml/text/font.py
+++ b/docx/oxml/text/font.py
@@ -32,6 +32,8 @@ class CT_Fonts(BaseOxmlElement):
"""
ascii = OptionalAttribute('w:ascii', ST_String)
hAnsi = OptionalAttribute('w:hAnsi', ST_String)
+ asciiTheme = OptionalAttribute('w:asciiTheme', ST_String)
+ hAnsiTheme = OptionalAttribute('w:hAnsiTheme', ST_String)
class CT_Highlight(BaseOxmlElement):
@@ -155,6 +157,44 @@ def rFonts_hAnsi(self, value):
rFonts = self.get_or_add_rFonts()
rFonts.hAnsi = value
+ @property
+ def rFonts_asciiTheme(self):
+ """
+ The value of `w:rFonts/@w:asciiTheme` or |None| if not present. Represents
+ the assigned typeface Theme. The rFonts element also specifies other
+ special-case typeface Theme; this method handles the case where just
+ the common Theme is required.
+ """
+ rFonts = self.rFonts
+ if rFonts is None:
+ return None
+ return rFonts.asciiTheme
+
+ @rFonts_asciiTheme.setter
+ def rFonts_asciiTheme(self, value):
+ if value is None:
+ self._remove_rFonts()
+ return
+ rFonts = self.get_or_add_rFonts()
+ rFonts.asciiTheme = value
+
+ @property
+ def rFonts_hAnsiTheme(self):
+ """
+ The value of `w:rFonts/@w:hAnsiTheme` or |None| if not present.
+ """
+ rFonts = self.rFonts
+ if rFonts is None:
+ return None
+ return rFonts.hAnsiTheme
+
+ @rFonts_hAnsiTheme.setter
+ def rFonts_hAnsiTheme(self, value):
+ if value is None and self.rFonts is None:
+ return
+ rFonts = self.get_or_add_rFonts()
+ rFonts.hAnsiTheme = value
+
@property
def style(self):
"""
diff --git a/docx/oxml/text/paragraph.py b/docx/oxml/text/paragraph.py
index 5e4213776..122b65c5f 100644
--- a/docx/oxml/text/paragraph.py
+++ b/docx/oxml/text/paragraph.py
@@ -26,6 +26,46 @@ def add_p_before(self):
new_p = OxmlElement('w:p')
self.addprevious(new_p)
return new_p
+
+ def link_comment(self, _id, rangeStart=0, rangeEnd=0):
+ rStart = OxmlElement('w:commentRangeStart')
+ rStart._id = _id
+ rEnd = OxmlElement('w:commentRangeEnd')
+ rEnd._id = _id
+ if rangeStart == 0 and rangeEnd == 0:
+ self.insert(0,rStart)
+ self.append(rEnd)
+ else:
+ self.insert(rangeStart,rStart)
+ if rangeEnd == len(self.getchildren() ) - 1 :
+ self.append(rEnd)
+ else:
+ self.insert(rangeEnd+1, rEnd)
+
+ def add_comm(self, author, comment_part, initials, dtime, comment_text, rangeStart, rangeEnd):
+
+ comment = comment_part.add_comment(author, initials, dtime)
+ comment._add_p(comment_text)
+ _r = self.add_r()
+ _r.add_comment_reference(comment._id)
+ self.link_comment(comment._id, rangeStart= rangeStart, rangeEnd=rangeEnd)
+
+ return comment
+
+ def add_fn(self, text, footnotes_part):
+ footnote = footnotes_part.add_footnote()
+ footnote._add_p(' '+text)
+ _r = self.add_r()
+ _r.add_footnote_reference(footnote._id)
+
+ return footnote
+
+ def footnote_style(self):
+ pPr = self.get_or_add_pPr()
+ rstyle = pPr.get_or_add_pStyle()
+ rstyle.val = 'FootnoteText'
+
+ return self
@property
def alignment(self):
@@ -71,7 +111,24 @@ def style(self):
if pPr is None:
return None
return pPr.style
+
+ @property
+ def comment_id(self):
+ _id = self.xpath('./w:commentRangeStart/@w:id')
+ if len(_id) > 1 or len(_id) == 0:
+ return None
+ else:
+ return int(_id[0])
+
+ @property
+ def footnote_ids(self):
+ _id = self.xpath('./w:r/w:footnoteReference/@w:id')
+ if len(_id) == 0 :
+ return None
+ else:
+ return _id
+
@style.setter
def style(self, style):
pPr = self.get_or_add_pPr()
diff --git a/docx/oxml/text/parfmt.py b/docx/oxml/text/parfmt.py
index 466b11b1b..69900b0dc 100644
--- a/docx/oxml/text/parfmt.py
+++ b/docx/oxml/text/parfmt.py
@@ -57,6 +57,7 @@ class CT_PPr(BaseOxmlElement):
spacing = ZeroOrOne('w:spacing', successors=_tag_seq[22:])
ind = ZeroOrOne('w:ind', successors=_tag_seq[23:])
jc = ZeroOrOne('w:jc', successors=_tag_seq[27:])
+ rPr = ZeroOrOne('w:rPr', successors=_tag_seq[34:])
sectPr = ZeroOrOne('w:sectPr', successors=_tag_seq[35:])
del _tag_seq
diff --git a/docx/oxml/text/run.py b/docx/oxml/text/run.py
index 8f0a62e82..255c45a35 100644
--- a/docx/oxml/text/run.py
+++ b/docx/oxml/text/run.py
@@ -5,11 +5,15 @@
"""
from ..ns import qn
-from ..simpletypes import ST_BrClear, ST_BrType
+from ..simpletypes import ST_BrClear, ST_BrType, ST_DecimalNumber, ST_String
+
+from .. import OxmlElement
from ..xmlchemy import (
- BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne
+ BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne, RequiredAttribute
)
+from .. import OxmlElement
+
class CT_Br(BaseOxmlElement):
"""
@@ -24,6 +28,8 @@ class CT_R(BaseOxmlElement):
```` element, containing the properties and text for a run.
"""
rPr = ZeroOrOne('w:rPr')
+ # wrong
+ ref = ZeroOrOne('w:commentRangeStart', successors=('w:r',))
t = ZeroOrMore('w:t')
br = ZeroOrMore('w:br')
cr = ZeroOrMore('w:cr')
@@ -52,6 +58,61 @@ def add_drawing(self, inline_or_anchor):
drawing.append(inline_or_anchor)
return drawing
+ def add_comm(self, author, comment_part, initials, dtime, comment_text):
+
+ comment = comment_part.add_comment(author, initials, dtime)
+ comment._add_p(comment_text)
+ # _r = self.add_r()
+ self.add_comment_reference(comment._id)
+ self.link_comment(comment._id)
+
+ return comment
+
+ def link_comment(self, _id):
+ rStart = OxmlElement('w:commentRangeStart')
+ rStart._id = _id
+ rEnd = OxmlElement('w:commentRangeEnd')
+ rEnd._id = _id
+ self.addprevious(rStart)
+ self.addnext(rEnd)
+
+ def add_comment_reference(self, _id):
+ reference = OxmlElement('w:commentReference')
+ reference._id = _id
+ self.append(reference)
+ return reference
+
+ def add_footnote_reference(self, _id):
+ rPr = self.get_or_add_rPr()
+ rstyle = rPr.get_or_add_rStyle()
+ rstyle.val = 'FootnoteReference'
+ reference = OxmlElement('w:footnoteReference')
+ reference._id = _id
+ self.append(reference)
+ return reference
+
+ def add_footnoteRef(self):
+ ref = OxmlElement('w:footnoteRef')
+ self.append(ref)
+
+ return ref
+
+ def footnote_style(self):
+ rPr = self.get_or_add_rPr()
+ rstyle = rPr.get_or_add_rStyle()
+ rstyle.val = 'FootnoteReference'
+
+ self.add_footnoteRef()
+ return self
+
+ @property
+ def footnote_id(self):
+ _id = self.xpath('./w:footnoteReference/@w:id')
+ if len(_id) > 1 or len(_id) == 0:
+ return None
+ else:
+ return int(_id[0])
+
def clear_content(self):
"""
Remove all child elements except the ```` element if present.
@@ -60,6 +121,12 @@ def clear_content(self):
for child in content_child_elms:
self.remove(child)
+ def add_comment_reference(self, _id):
+ reference = OxmlElement('w:commentReference')
+ reference._id = _id
+ self.append(reference)
+ return reference
+
@property
def style(self):
"""
@@ -96,6 +163,8 @@ def text(self):
text += '\t'
elif child.tag in (qn('w:br'), qn('w:cr')):
text += '\n'
+ elif child.tag == qn('w:noBreakHyphen'):
+ text += '-'
return text
@text.setter
@@ -103,6 +172,39 @@ def text(self, text):
self.clear_content()
_RunContentAppender.append_to_run_from_text(self, text)
+ def add_fldChar(self, fldCharType, fldLock=False, dirty=False):
+ if fldCharType not in ("begin", "end", "separate"):
+ return None
+
+ fld_char = OxmlElement("w:fldChar")
+ fld_char.set(qn("w:fldCharType"), fldCharType)
+ if fldLock:
+ fld_char.set(qn("w:fldLock"), "true")
+ elif dirty:
+ fld_char.set(qn("w:fldLock"), "true")
+ self.append(fld_char)
+ return fld_char
+
+ @property
+ def instr_text(self):
+ for child in list(self):
+ if child.tag.endswith("instrText"):
+ return child
+ return None
+
+ @instr_text.setter
+ def instr_text(self, instr_text_val):
+ if self.instr_text is not None:
+ self._remove_instr_text()
+
+ instr_text = OxmlElement("w:instrText")
+ instr_text.text = instr_text_val
+ self.append(instr_text)
+
+ def _remove_instr_text(self):
+ for child in self.iterchildren("{*}instrText"):
+ self.remove(child)
+
class CT_Text(BaseOxmlElement):
"""
@@ -110,6 +212,14 @@ class CT_Text(BaseOxmlElement):
"""
+class CT_RPr(BaseOxmlElement):
+ rStyle = ZeroOrOne('w:rStyle')
+
+
+class CT_RStyle(BaseOxmlElement):
+ val = RequiredAttribute('w:val', ST_String)
+
+
class _RunContentAppender(object):
"""
Service object that knows how to translate a Python string into run
@@ -119,6 +229,7 @@ class _RunContentAppender(object):
appended. Likewise a newline or carriage return character ('\n', '\r')
causes a ```` element to be appended.
"""
+
def __init__(self, r):
self._r = r
self._bfr = []
diff --git a/docx/parts/comments.py b/docx/parts/comments.py
new file mode 100644
index 000000000..03a045aea
--- /dev/null
+++ b/docx/parts/comments.py
@@ -0,0 +1,26 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import os
+
+from docx.opc.constants import CONTENT_TYPE as CT
+from ..opc.packuri import PackURI
+
+from docx.oxml import parse_xml
+from ..opc.part import XmlPart
+
+class CommentsPart(XmlPart):
+ """Definition of Comments Part"""
+
+ @classmethod
+ def default(cls, package):
+ partname = PackURI("/word/comments.xml")
+ content_type = CT.WML_COMMENTS
+ element = parse_xml(cls._default_comments_xml())
+ return cls(partname, content_type, element, package)
+
+ @classmethod
+ def _default_comments_xml(cls):
+ path = os.path.join(os.path.split(__file__)[0], '..', 'templates', 'default-comments.xml')
+ with open(path, 'rb') as f:
+ xml_bytes = f.read()
+ return xml_bytes
diff --git a/docx/parts/document.py b/docx/parts/document.py
index 59d0b7a71..fe55eb3a6 100644
--- a/docx/parts/document.py
+++ b/docx/parts/document.py
@@ -11,6 +11,8 @@
from docx.parts.settings import SettingsPart
from docx.parts.story import BaseStoryPart
from docx.parts.styles import StylesPart
+from docx.parts.comments import CommentsPart
+from docx.parts.footnotes import FootnotesPart
from docx.shape import InlineShapes
from docx.shared import lazyproperty
@@ -102,6 +104,8 @@ def numbering_part(self):
numbering_part = NumberingPart.new()
self.relate_to(numbering_part, RT.NUMBERING)
return numbering_part
+
+
def save(self, path_or_stream):
"""
@@ -152,3 +156,33 @@ def _styles_part(self):
styles_part = StylesPart.default(self.package)
self.relate_to(styles_part, RT.STYLES)
return styles_part
+
+ @lazyproperty
+ def comments_part(self):
+ """
+ A |Comments| object providing read/write access to the core
+ properties of this document.
+ """
+ # return self.package._comments_part
+
+ @property
+ def _comments_part(self):
+ try:
+ return self.part_related_by(RT.COMMENTS)
+ except KeyError:
+ comments_part = CommentsPart.default(self)
+ self.relate_to(comments_part, RT.COMMENTS)
+ return comments_part
+
+ @property
+ def _footnotes_part(self):
+ """
+ |FootnotesPart| object related to this package. Creates
+ a default Comments part if one is not present.
+ """
+ try:
+ return self.part_related_by(RT.FOOTNOTES)
+ except KeyError:
+ footnotes_part = FootnotesPart.default(self)
+ self.relate_to(footnotes_part, RT.FOOTNOTES)
+ return footnotes_part
\ No newline at end of file
diff --git a/docx/parts/footnotes.py b/docx/parts/footnotes.py
new file mode 100644
index 000000000..67f29fb71
--- /dev/null
+++ b/docx/parts/footnotes.py
@@ -0,0 +1,26 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+from ..opc.constants import CONTENT_TYPE as CT
+from ..opc.packuri import PackURI
+from ..opc.part import XmlPart
+from ..oxml import parse_xml
+
+import os
+
+class FootnotesPart(XmlPart):
+ """
+ Definition of Footnotes Part
+ """
+ @classmethod
+ def default(cls, package):
+ partname = PackURI("/word/footnotes.xml")
+ content_type = CT.WML_FOOTNOTES
+ element = parse_xml(cls._default_footnotes_xml())
+ return cls(partname, content_type, element, package)
+
+ @classmethod
+ def _default_footnotes_xml(cls):
+ path = os.path.join(os.path.split(__file__)[0], '..', 'templates', 'default-footnotes.xml')
+ with open(path, 'rb') as f:
+ xml_bytes = f.read()
+ return xml_bytes
\ No newline at end of file
diff --git a/docx/table.py b/docx/table.py
index b3bc090fb..9801f8346 100644
--- a/docx/table.py
+++ b/docx/table.py
@@ -10,7 +10,7 @@
from .enum.style import WD_STYLE_TYPE
from .oxml.simpletypes import ST_Merge
from .shared import Inches, lazyproperty, Parented
-
+from .section import Section
class Table(Parented):
"""
@@ -45,6 +45,9 @@ def add_row(self):
return _Row(tr, self)
@property
+ def section(self):
+ return Section(self._element._section, self.part)
+ @property
def alignment(self):
"""
Read/write. A member of :ref:`WdRowAlignment` or None, specifying the
diff --git a/docx/templates/default-comments.xml b/docx/templates/default-comments.xml
new file mode 100644
index 000000000..4ceb12ea4
--- /dev/null
+++ b/docx/templates/default-comments.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/docx/templates/default-footnotes.xml b/docx/templates/default-footnotes.xml
new file mode 100644
index 000000000..5dc12e66f
--- /dev/null
+++ b/docx/templates/default-footnotes.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docx/text/comment.py b/docx/text/comment.py
new file mode 100644
index 000000000..adc0110a1
--- /dev/null
+++ b/docx/text/comment.py
@@ -0,0 +1,23 @@
+from ..shared import Parented
+
+class Comment(Parented):
+ """[summary]
+
+ :param Parented: [description]
+ :type Parented: [type]
+ """
+ def __init__(self, com, parent):
+ super(Comment, self).__init__(parent)
+ self._com = self._element = self.element = com
+
+ @property
+ def paragraph(self):
+ return self.element.paragraph
+
+ @property
+ def text(self):
+ return self.element.paragraph.text
+
+ @text.setter
+ def text(self, text):
+ self.element.paragraph.text = text
\ No newline at end of file
diff --git a/docx/text/font.py b/docx/text/font.py
index 162832101..bdef5b808 100644
--- a/docx/text/font.py
+++ b/docx/text/font.py
@@ -197,6 +197,25 @@ def name(self, value):
rPr.rFonts_ascii = value
rPr.rFonts_hAnsi = value
+ @property
+ def theme(self):
+ """
+ Get or set the typeface theme for this |Font| instance, causing the
+ text it controls to appear in the themed font, if a matching font is
+ found. |None| indicates the typeface is inherited from the style
+ hierarchy.
+ """
+ rPr = self._element.rPr
+ if rPr is None:
+ return None
+ return rPr.rFonts_asciiTheme
+
+ @theme.setter
+ def theme(self, value):
+ rPr = self._element.get_or_add_rPr()
+ rPr.rFonts_asciiTheme = value
+ rPr.rFonts_hAnsiTheme = value
+
@property
def no_proof(self):
"""
diff --git a/docx/text/paragraph.py b/docx/text/paragraph.py
index 4fb583b94..9bf676b92 100644
--- a/docx/text/paragraph.py
+++ b/docx/text/paragraph.py
@@ -13,6 +13,8 @@
from .run import Run
from ..shared import Parented
+from datetime import datetime
+import re
class Paragraph(Parented):
"""
@@ -38,7 +40,39 @@ def add_run(self, text=None, style=None):
if style:
run.style = style
return run
-
+
+ def delete(self):
+ """
+ delete the content of the paragraph
+ """
+ self._p.getparent().remove(self._p)
+ self._p = self._element = None
+
+ def add_comment(self, text, author='python-docx', initials='pd', dtime=None ,rangeStart=0, rangeEnd=0, comment_part=None):
+ if comment_part is None:
+ comment_part = self.part._comments_part.element
+ if dtime is None:
+ dtime = str( datetime.now() ).replace(' ', 'T')
+ comment = self._p.add_comm(author, comment_part, initials, dtime, text, rangeStart, rangeEnd)
+
+ return comment
+
+ def add_footnote(self, text):
+ footnotes_part = self.part._footnotes_part.element
+ footnote = self._p.add_fn(text, footnotes_part)
+
+ return footnote
+
+ def merge_paragraph(self, otherParagraph):
+ r_lst = otherParagraph.runs
+ self.append_runs(r_lst)
+
+ def append_runs(self, runs):
+ self.add_run(' ')
+ for run in runs:
+ self._p.append(run._r)
+
+
@property
def alignment(self):
"""
@@ -93,6 +127,9 @@ def runs(self):
return [Run(r, self) for r in self._p.r_lst]
@property
+ def all_runs(self):
+ return [Run(r, self) for r in self._p.xpath('.//w:r[not(ancestor::w:r)]')]
+ @property
def style(self):
"""
Read/Write. |_ParagraphStyle| object representing the style assigned
@@ -131,6 +168,70 @@ def text(self):
text += run.text
return text
+ @property
+ def header_level(self):
+ '''
+ input Paragraph Object
+ output Paragraph level in case of header or returns None
+ '''
+ headerPattern = re.compile(".*Heading (\d+)$")
+ level = 0
+ if headerPattern.match(self.style.name):
+ level = int(self.style.name.lower().split('heading')[-1].strip())
+ return level
+
+ @property
+ def NumId(self):
+ '''
+ returns NumId val in case of paragraph has numbering
+ else: return None
+ '''
+ try:
+ return self._p.pPr.numPr.numId.val
+ except:
+ return None
+
+ @property
+ def list_lvl(self):
+ '''
+ returns ilvl val in case of paragraph has a numbering level
+ else: return None
+ '''
+ try:
+ return self._p.pPr.numPr.ilvl.val
+ except :
+ return None
+
+ @property
+ def list_info(self):
+ '''
+ returns tuple (has numbering info, numId value, ilvl value)
+ '''
+ if self.NumId and self.list_lvl:
+ return True, self.NumId, self.list_lvl
+ else:
+ return False, 0, 0
+
+ @property
+ def is_heading(self):
+ return True if self.header_level else False
+
+ @property
+ def full_text(self):
+ return u"".join([r.text for r in self.all_runs])
+
+ @property
+ def footnotes(self):
+ if self._p.footnote_ids is not None :
+ return True
+ else :
+ return False
+
+ @property
+ def comments(self):
+ runs_comments = [run.comments for run in self.runs]
+ return [comment for comments in runs_comments for comment in comments]
+
@text.setter
def text(self, text):
self.clear()
diff --git a/docx/text/run.py b/docx/text/run.py
index 97d6da7db..0ed3e972a 100644
--- a/docx/text/run.py
+++ b/docx/text/run.py
@@ -5,6 +5,10 @@
"""
from __future__ import absolute_import, print_function, unicode_literals
+from datetime import datetime
+
+from docx.oxml.ns import qn
+from docx.opc.part import *
from ..enum.style import WD_STYLE_TYPE
from ..enum.text import WD_BREAK
@@ -12,6 +16,8 @@
from ..shape import InlineShape
from ..shared import Parented
+from .comment import Comment
+
class Run(Parented):
"""
@@ -21,6 +27,7 @@ class Run(Parented):
not specified directly on the run and its effective value is taken from
the style hierarchy.
"""
+
def __init__(self, r, parent):
super(Run, self).__init__(parent)
self._r = self._element = self.element = r
@@ -80,6 +87,14 @@ def add_text(self, text):
t = self._r.add_t(text)
return _Text(t)
+ def add_comment(self, text, author='python-docx', initials='pd', dtime=None):
+ comment_part = self.part._comments_part.element
+ if dtime is None:
+ dtime = str(datetime.now()).replace(' ', 'T')
+ comment = self._r.add_comm(author, comment_part, initials, dtime, text)
+
+ return comment
+
@property
def bold(self):
"""
@@ -181,11 +196,96 @@ def underline(self):
def underline(self, value):
self.font.underline = value
+ @property
+ def footnote(self):
+ _id = self._r.footnote_id
+
+ if _id is not None:
+ footnotes_part = self._parent._parent.part._footnotes_part.element
+ footnote = footnotes_part.get_footnote_by_id(_id)
+ return footnote.paragraph.text
+ else:
+ return None
+
+ @property
+ def is_hyperlink(self):
+ '''
+ checks if the run is nested inside a hyperlink element
+ '''
+ return self.element.getparent().tag.split('}')[1] == 'hyperlink'
+
+ def get_hyperLink(self):
+ """
+ returns the text of the hyperlink of the run in case of the run has a hyperlink
+ """
+ document = self._parent._parent.document
+ parent = self.element.getparent()
+ linkText = ''
+ if self.is_hyperlink:
+ if parent.attrib.__contains__(qn('r:id')):
+ rId = parent.get(qn('r:id'))
+ linkText = document._part._rels[rId].target_ref
+ return linkText, True
+ elif parent.attrib.__contains__(qn('w:anchor')):
+ linkText = parent.get(qn('w:anchor'))
+ return linkText, False
+ else:
+ print('No Link in Hyperlink!')
+ print(self.text)
+ return '', False
+ else:
+ return 'None'
+
+ @property
+ def comments(self):
+ comment_part = self._parent._parent.part._comments_part.element
+ comment_refs = self._element.findall(qn('w:commentReference'))
+ ids = [int(ref.get(qn('w:id'))) for ref in comment_refs]
+ coms = [com for com in comment_part if com._id in ids]
+ return [Comment(com, comment_part) for com in coms]
+
+ def add_ole_object_to_run(self, ole_object_path):
+ """
+ Add saved OLE Object in the disk to an run and retun the newly created relationship ID
+ Note: OLE Objects must be stored in the disc as `.bin` file
+ """
+ reltype: str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"
+ pack_path: str = "/word/embeddings/" + ole_object_path.split("\\")[-1]
+ partname = PackURI(pack_path)
+ content_type: str = "application/vnd.openxmlformats-officedocument.oleObject"
+
+ with open(ole_object_path, "rb") as f:
+ blob = f.read()
+ target_part = Part(partname=partname, content_type=content_type, blob=blob)
+ rel_id: str = self.part.rels._next_rId
+ self.part.rels.add_relationship(reltype=reltype, target=target_part, rId=rel_id)
+ return rel_id
+
+ def add_fldChar(self, fldCharType, fldLock: bool = False, dirty: bool = False):
+
+ fldChar = self._r.add_fldChar(fldCharType, fldLock, dirty)
+ return fldChar
+
+ @property
+ def instr_text(self):
+ return self._r.instr_text
+
+ @instr_text.setter
+ def instr_text(self, instr_text_val):
+ self._r.instr_text = instr_text_val
+
+ def remove_instr_text(self):
+ if self.instr_text is None:
+ return None
+ else:
+ self._r._remove_instr_text()
+
class _Text(object):
"""
Proxy object wrapping ```` element.
"""
+
def __init__(self, t_elm):
super(_Text, self).__init__()
self._t = t_elm
diff --git a/setup.py b/setup.py
index f0b3ef54d..ff8652647 100644
--- a/setup.py
+++ b/setup.py
@@ -26,13 +26,13 @@ def text_of(relpath):
).group(1)
-NAME = 'python-docx'
+NAME = 'bayoo-docx'
VERSION = version
DESCRIPTION = 'Create and update Microsoft Word .docx files.'
KEYWORDS = 'docx office openxml word'
-AUTHOR = 'Steve Canny'
-AUTHOR_EMAIL = 'python-docx@googlegroups.com'
-URL = 'https://github.com/python-openxml/python-docx'
+AUTHOR = 'Obay Daba'
+AUTHOR_EMAIL = 'ObayDaba96@googlegroups.com'
+URL = 'https://github.com/BayooG/bayooo-docx'
LICENSE = text_of('LICENSE')
PACKAGES = find_packages(exclude=['tests', 'tests.*'])
PACKAGE_DATA = {'docx': ['templates/*.xml', 'templates/*.docx']}
@@ -58,7 +58,7 @@ def text_of(relpath):
'Topic :: Software Development :: Libraries'
]
-LONG_DESCRIPTION = text_of('README.rst') + '\n\n' + text_of('HISTORY.rst')
+LONG_DESCRIPTION = text_of('DESCRIPTION.rst') + '\n\n' + text_of('HISTORY.rst')
params = {