Skip to content

Commit 53c7ec8

Browse files
authored
Merge pull request python-openxml#1 from BayooG/feature/comments_lower_level
Feature/comments lower level
2 parents 94623ff + b66eaf7 commit 53c7ec8

File tree

11 files changed

+266
-2
lines changed

11 files changed

+266
-2
lines changed

docx/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from docx.parts.numbering import NumberingPart
1717
from docx.parts.settings import SettingsPart
1818
from docx.parts.styles import StylesPart
19+
from docx.parts.comments import CommentsPart
1920

2021

2122
def part_class_selector(content_type, reltype):
@@ -25,13 +26,14 @@ def part_class_selector(content_type, reltype):
2526

2627

2728
PartFactory.part_class_selector = part_class_selector
29+
PartFactory.part_type_for[CT.WML_COMMENTS] = CommentsPart
2830
PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart
2931
PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart
3032
PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart
3133
PartFactory.part_type_for[CT.WML_SETTINGS] = SettingsPart
3234
PartFactory.part_type_for[CT.WML_STYLES] = StylesPart
3335

3436
del (
35-
CT, CorePropertiesPart, DocumentPart, NumberingPart, PartFactory,
36-
StylesPart, part_class_selector
37+
CT, CorePropertiesPart, CommentsPart, DocumentPart, NumberingPart, PartFactory,
38+
StylesPart, part_class_selector,
3739
)

docx/document.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ def core_properties(self):
108108
"""
109109
return self._part.core_properties
110110

111+
@property
112+
def comments_part(self):
113+
"""
114+
A |Comments| object providing read/write access to the core
115+
properties of this document.
116+
"""
117+
return self._part._package._comments_part
118+
119+
111120
@property
112121
def inline_shapes(self):
113122
"""

docx/opc/package.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .packuri import PACKAGE_URI
1212
from .part import PartFactory
1313
from .parts.coreprops import CorePropertiesPart
14+
from docx.parts.comments import CommentsPart
1415
from .pkgreader import PackageReader
1516
from .pkgwriter import PackageWriter
1617
from .rel import Relationships
@@ -171,6 +172,19 @@ def _core_properties_part(self):
171172
core_properties_part = CorePropertiesPart.default(self)
172173
self.relate_to(core_properties_part, RT.CORE_PROPERTIES)
173174
return core_properties_part
175+
176+
@property
177+
def _comments_part(self):
178+
"""
179+
|CommentsPart| object related to this package. Creates
180+
a default Comments part if one is not present.
181+
"""
182+
try:
183+
return self.part_related_by(RT.COMMENTS)
184+
except KeyError:
185+
comments_part = CommentsPart.default(self)
186+
self.relate_to(comments_part, RT.COMMENTS)
187+
return comments_part
174188

175189

176190
class Unmarshaller(object):

docx/oxml/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,11 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
203203
register_element_cls('w:br', CT_Br)
204204
register_element_cls('w:r', CT_R)
205205
register_element_cls('w:t', CT_Text)
206+
207+
208+
from .comments import CT_Comments,CT_Com, CT_CRE, CT_CRS, CT_CRef
209+
register_element_cls('w:comments', CT_Comments)
210+
register_element_cls('w:comment', CT_Com)
211+
register_element_cls('w:commentRangeStart', CT_CRS)
212+
register_element_cls('w:commentRangeEnd', CT_CRE)
213+
register_element_cls('w:commentReference', CT_CRef)

docx/oxml/comments.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Custom element classes related to the comments part
3+
"""
4+
5+
from . import OxmlElement
6+
from .simpletypes import ST_DecimalNumber, ST_String
7+
from docx.text.run import Run
8+
from .xmlchemy import (
9+
BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrMore, ZeroOrOne
10+
)
11+
12+
class CT_Com(BaseOxmlElement):
13+
"""
14+
A ``<w:comment>`` element, a container for Comment properties
15+
"""
16+
initials = RequiredAttribute('w:initials', ST_String)
17+
_id = RequiredAttribute('w:id', ST_DecimalNumber)
18+
date = RequiredAttribute('w:date', ST_String)
19+
author = RequiredAttribute('w:author', ST_String)
20+
21+
paragraph = ZeroOrOne('w:p', successors=('w:comment',))
22+
23+
@classmethod
24+
def new(cls, initials, comm_id, date, author):
25+
"""
26+
Return a new ``<w:comment>`` element having _id of *comm_id* and having
27+
the passed params as meta data
28+
"""
29+
comment = OxmlElement('w:comment')
30+
comment.initials = initials
31+
comment.date = date
32+
comment._id = comm_id
33+
comment.author = author
34+
return comment
35+
36+
def _add_p(self, text):
37+
_p = OxmlElement('w:p')
38+
_r = _p.add_r()
39+
run = Run(_r,self)
40+
run.text = text
41+
self._insert_paragraph(_p)
42+
return _p
43+
44+
class CT_Comments(BaseOxmlElement):
45+
"""
46+
A ``<w:comments>`` element, a container for Comments properties
47+
"""
48+
comment = ZeroOrMore ('w:comment', successors=('w:comments',))
49+
50+
def add_comment(self,author, initials, date):
51+
_next_id = self._next_commentId
52+
comment = CT_Com.new(initials, _next_id, date, author)
53+
comment = self._insert_comment(comment)
54+
55+
return comment
56+
57+
@property
58+
def _next_commentId(self):
59+
ids = self.xpath('./w:comment/@w:id')
60+
len(ids)
61+
_ids = [int(_str) for _str in ids]
62+
_ids.sort()
63+
64+
print(_ids)
65+
try:
66+
return _ids[-1] + 2
67+
except:
68+
return 0
69+
70+
71+
class CT_CRS(BaseOxmlElement):
72+
"""
73+
A ``<w:commentRangeStart>`` element
74+
"""
75+
_id = RequiredAttribute('w:id', ST_DecimalNumber)
76+
77+
@classmethod
78+
def new(cls, _id):
79+
commentRangeStart = OxmlElement('w:commentRangeStart')
80+
commentRangeStart._id =_id
81+
82+
return commentRangeStart
83+
84+
85+
86+
class CT_CRE(BaseOxmlElement):
87+
"""
88+
A ``w:commentRangeEnd`` element
89+
"""
90+
_id = RequiredAttribute('w:id', ST_DecimalNumber)
91+
92+
93+
@classmethod
94+
def new(cls, _id):
95+
commentRangeEnd = OxmlElement('w:commentRangeEnd')
96+
commentRangeEnd._id =_id
97+
return commentRangeEnd
98+
99+
100+
class CT_CRef(BaseOxmlElement):
101+
"""
102+
w:commentReference
103+
"""
104+
_id = RequiredAttribute('w:id', ST_DecimalNumber)
105+
106+
@classmethod
107+
def new (cls, _id):
108+
commentReference = OxmlElement('w:commentReference')
109+
commentReference._id =_id
110+
return commentReference

docx/oxml/text/paragraph.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,34 @@ def add_p_before(self):
2626
new_p = OxmlElement('w:p')
2727
self.addprevious(new_p)
2828
return new_p
29+
30+
def link_comment(self, _id, rangeStart=0, rangeEnd=0):
31+
rStart = OxmlElement('w:commentRangeStart')
32+
rStart._id = _id
33+
rEnd = OxmlElement('w:commentRangeEnd')
34+
rEnd._id = _id
35+
if rangeStart == 0 and rangeEnd == 0:
36+
self.insert(0,rStart)
37+
self.append(rEnd)
38+
else:
39+
self.insert(rangeStart,rStart)
40+
if rangeEnd == len(self.getchildren() ) - 1 :
41+
self.append(rEnd)
42+
else:
43+
self.insert(rangeEnd+1, rEnd)
44+
45+
def add_comm(self, author, comment_part, initials, dtime, comment_text, rangeStart, rangeEnd):
46+
47+
comment = comment_part.add_comment(author, initials, dtime)
48+
comment._add_p(comment_text)
49+
_r = self.add_r()
50+
_r.add_comment_reference(comment._id)
51+
self.link_comment(comment._id, rangeStart= rangeStart, rangeEnd=rangeEnd)
52+
53+
return comment
54+
55+
56+
2957

3058
@property
3159
def alignment(self):
@@ -71,6 +99,15 @@ def style(self):
7199
if pPr is None:
72100
return None
73101
return pPr.style
102+
103+
@property
104+
def comment_id(self):
105+
_id = self.xpath('./w:commentRangeStart/@w:id')
106+
if(len(_id)>1):
107+
return None
108+
else:
109+
return int(_id[0])
110+
74111

75112
@style.setter
76113
def style(self, style):

docx/oxml/text/run.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66

77
from ..ns import qn
88
from ..simpletypes import ST_BrClear, ST_BrType
9+
from .. import OxmlElement
910
from ..xmlchemy import (
1011
BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne
1112
)
1213

14+
from .. import OxmlElement
15+
1316

1417
class CT_Br(BaseOxmlElement):
1518
"""
@@ -24,6 +27,8 @@ class CT_R(BaseOxmlElement):
2427
``<w:r>`` element, containing the properties and text for a run.
2528
"""
2629
rPr = ZeroOrOne('w:rPr')
30+
###wrong
31+
ref = ZeroOrOne('w:commentRangeStart', successors=('w:r',))
2732
t = ZeroOrMore('w:t')
2833
br = ZeroOrMore('w:br')
2934
cr = ZeroOrMore('w:cr')
@@ -52,6 +57,12 @@ def add_drawing(self, inline_or_anchor):
5257
drawing.append(inline_or_anchor)
5358
return drawing
5459

60+
def add_comment_reference(self, _id):
61+
reference = OxmlElement('w:commentReference')
62+
reference._id = _id
63+
self.append(reference)
64+
return reference
65+
5566
def clear_content(self):
5667
"""
5768
Remove all child elements except the ``<w:rPr>`` element if present.
@@ -60,6 +71,12 @@ def clear_content(self):
6071
for child in content_child_elms:
6172
self.remove(child)
6273

74+
def add_comment_reference(self, _id):
75+
reference = OxmlElement('w:commentReference')
76+
reference._id = _id
77+
self.append(reference)
78+
return reference
79+
6380
@property
6481
def style(self):
6582
"""

docx/parts/comments.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
import os
4+
5+
from docx.opc.constants import CONTENT_TYPE as CT
6+
from ..opc.packuri import PackURI
7+
8+
from docx.oxml import parse_xml
9+
from ..opc.part import XmlPart
10+
11+
class CommentsPart(XmlPart):
12+
"""Definition of Comments Part"""
13+
14+
@classmethod
15+
def default(cls, package):
16+
partname = PackURI("/word/comments.xml")
17+
content_type = CT.WML_COMMENTS
18+
element = parse_xml(cls._default_comments_xml())
19+
return cls(partname, content_type, element, package)
20+
21+
@classmethod
22+
def _default_comments_xml(cls):
23+
path = os.path.join(os.path.split(__file__)[0], '..', 'templates', 'default-comments.xml')
24+
with open(path, 'rb') as f:
25+
xml_bytes = f.read()
26+
return xml_bytes

docx/parts/document.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ..shared import lazyproperty
1818
from .settings import SettingsPart
1919
from .styles import StylesPart
20+
from .comments import CommentsPart
2021

2122

2223
class DocumentPart(XmlPart):
@@ -121,6 +122,8 @@ def numbering_part(self):
121122
numbering_part = NumberingPart.new()
122123
self.relate_to(numbering_part, RT.NUMBERING)
123124
return numbering_part
125+
126+
124127

125128
def save(self, path_or_stream):
126129
"""
@@ -171,3 +174,19 @@ def _styles_part(self):
171174
styles_part = StylesPart.default(self.package)
172175
self.relate_to(styles_part, RT.STYLES)
173176
return styles_part
177+
@lazyproperty
178+
def comments_part(self):
179+
"""
180+
A |Comments| object providing read/write access to the core
181+
properties of this document.
182+
"""
183+
# return self.package._comments_part
184+
185+
@property
186+
def _comments_part(self):
187+
try:
188+
return self.part_related_by(RT.COMMENTS)
189+
except KeyError:
190+
comments_part = CommentsPart.default(self)
191+
self.relate_to(comments_part, RT.COMMENTS)
192+
return comments_part

docx/templates/default-comments.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se wp14"></w:comments>

docx/text/paragraph.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .run import Run
1414
from ..shared import Parented
1515

16+
from datetime import datetime
1617

1718
class Paragraph(Parented):
1819
"""
@@ -39,6 +40,25 @@ def add_run(self, text=None, style=None):
3940
run.style = style
4041
return run
4142

43+
# def add_comment(self, author, initials, dt, comment_text, rangeStart=0, rangeEnd=0):
44+
# comment_part = self.part.comments_part.element
45+
# comment = comment_part.add_comment(author, initials, dt)
46+
# comment._add_p(comment_text)
47+
48+
# _r = self._p.add_r()
49+
# _r.add_comment_reference(comment._id)
50+
# self._p.link_comment(comment._id, rangeStart= rangeStart, rangeEnd=rangeEnd)
51+
52+
# return comment
53+
54+
def add_comment(self, text, author='python-docx', initials='pd', dtime=None ,rangeStart=0, rangeEnd=0):
55+
comment_part = self.part._comments_part.element
56+
if dtime is None:
57+
dtime = str( datetime.now )
58+
comment = self._p.add_comm(author, comment_part, initials, dtime, text, rangeStart, rangeEnd)
59+
60+
return comment
61+
4262
@property
4363
def alignment(self):
4464
"""

0 commit comments

Comments
 (0)