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..ac9440518
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,12 @@
+## 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)**
+
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/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.rst b/README.rst
index 82d1f0bd7..f706025cd 100644
--- a/README.rst
+++ b/README.rst
@@ -1,10 +1,52 @@
-.. image:: https://travis-ci.org/python-openxml/python-docx.svg?branch=master
- :target: https://travis-ci.org/python-openxml/python-docx
+Bayoo-docx
+==========
-*python-docx* is a Python library for creating and updating Microsoft Word
-(.docx) files.
+Python library forked from `python-docx `_.
-More information is available in the `python-docx documentation`_.
+The main purpose of the fork was to add implementation for comments and footnotes to the library
-.. _`python-docx documentation`:
- https://python-docx.readthedocs.org/en/latest/
+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
+
+ run.add_comment('comment2')
+
+ run_comments = run.comments
+
+ paragraph.add_footnote('footnote text') # add a footnote
+
+
+Donation
+------------
+::
+
+ bitcoin: bc1q9dftn4ndufwzyzkm8ryu0kky0v8y7w00zdah9r
+
+
+License
+-------
+
+`MIT `_
diff --git a/docx/__init__.py b/docx/__init__.py
index 59756c021..052b94ea4 100644
--- a/docx/__init__.py
+++ b/docx/__init__.py
@@ -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..ad3f4a418 100644
--- a/docx/image/__init__.py
+++ b/docx/image/__init__.py
@@ -14,7 +14,8 @@
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
+from docx.image.svg import Svg
SIGNATURES = (
# class, offset, signature_bytes
@@ -26,4 +27,6 @@
(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'),
+ (Svg, 0, b'`` 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/ns.py b/docx/oxml/ns.py
index 6b0861284..a35164baf 100644
--- a/docx/oxml/ns.py
+++ b/docx/oxml/ns.py
@@ -24,6 +24,7 @@
"wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xml": "http://www.w3.org/XML/1998/namespace",
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
+ "asvg": "http://schemas.microsoft.com/office/drawing/2016/SVG/main",
}
pfxmap = dict((value, key) for key, value in nsmap.items())
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/shape.py b/docx/oxml/shape.py
index 77ca7db8a..5ce60e6b3 100644
--- a/docx/oxml/shape.py
+++ b/docx/oxml/shape.py
@@ -23,6 +23,7 @@ class CT_Blip(BaseOxmlElement):
"""
embed = OptionalAttribute('r:embed', ST_RelationshipId)
link = OptionalAttribute('r:link', ST_RelationshipId)
+ extLst = ZeroOrOne('a:extLst')
class CT_BlipFillProperties(BaseOxmlElement):
@@ -98,7 +99,7 @@ def _inline_xml(cls):
' \n'
' \n'
' \n'
- '' % nsdecls('wp', 'a', 'pic', 'r')
+ '' % nsdecls('wp', 'a', 'pic', 'r', 'asvg')
)
@@ -133,14 +134,49 @@ def new(cls, pic_id, filename, rId, cx, cy):
contents required to define a viable picture element, based on the
values passed as parameters.
"""
- pic = parse_xml(cls._pic_xml())
+ if filename.endswith(".svg"):
+ pic = parse_xml(cls._pic_xml_svg())
+ pic.blipFill.blip.extLst.ext.svgBlip.embed = rId
+ else:
+ pic = parse_xml(cls._pic_xml())
+ pic.blipFill.blip.embed = rId
+
pic.nvPicPr.cNvPr.id = pic_id
pic.nvPicPr.cNvPr.name = filename
- pic.blipFill.blip.embed = rId
pic.spPr.cx = cx
pic.spPr.cy = cy
return pic
+ @classmethod
+ def _pic_xml_svg(cls):
+ return (
+ '\n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ '' % nsdecls('pic', 'a', 'r', 'asvg')
+ )
+
@classmethod
def _pic_xml(cls):
return (
@@ -162,7 +198,7 @@ def _pic_xml(cls):
' \n'
' \n'
' \n'
- '' % nsdecls('pic', 'a', 'r')
+ '' % nsdecls('pic', 'a', 'r', 'asvg')
)
@@ -189,6 +225,7 @@ class CT_PositiveSize2D(BaseOxmlElement):
"""
cx = RequiredAttribute('cx', ST_PositiveCoordinate)
cy = RequiredAttribute('cy', ST_PositiveCoordinate)
+ svgBlip = ZeroOrOne('asvg:svgBlip')
class CT_PresetGeometry2D(BaseOxmlElement):
@@ -258,6 +295,7 @@ class CT_Transform2D(BaseOxmlElement):
"""
off = ZeroOrOne('a:off', successors=('a:ext',))
ext = ZeroOrOne('a:ext', successors=())
+ embed = OptionalAttribute('r:embed', ST_RelationshipId)
@property
def cx(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..8f3a9c70f 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
@@ -110,6 +179,13 @@ 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
@@ -164,3 +240,5 @@ def flush(self):
if text:
self._r.add_t(text)
del 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..77f25e45b 100644
--- a/docx/text/run.py
+++ b/docx/text/run.py
@@ -5,6 +5,9 @@
"""
from __future__ import absolute_import, print_function, unicode_literals
+from datetime import datetime
+
+from docx.oxml.ns import qn
from ..enum.style import WD_STYLE_TYPE
from ..enum.text import WD_BREAK
@@ -12,6 +15,7 @@
from ..shape import InlineShape
from ..shared import Parented
+from .comment import Comment
class Run(Parented):
"""
@@ -80,6 +84,13 @@ 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):
"""
@@ -180,7 +191,55 @@ def underline(self):
@underline.setter
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]
+
class _Text(object):
"""