From d3424b8a302316901911542f31e8497b0e887bbf Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 19 May 2014 16:53:50 +0200 Subject: [PATCH 01/11] first endnotes implementation --- MODS.txt | 39 +++++++++++++++++++++++++++++++++ docx/__init__.py | 2 ++ docx/oxml/__init__.py | 4 ++++ docx/oxml/parts/endnotes.py | 25 +++++++++++++++++++++ docx/parts/endnotes.py | 43 +++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 MODS.txt create mode 100644 docx/oxml/parts/endnotes.py create mode 100644 docx/parts/endnotes.py diff --git a/MODS.txt b/MODS.txt new file mode 100644 index 000000000..fb2b06d8a --- /dev/null +++ b/MODS.txt @@ -0,0 +1,39 @@ +content_type +------------ + +'application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml' +WML_ENDNOTES +WML_FOOTNOTES + +register part +------------- + +__init__.py +26:PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart + +api.py +183: if document_part.content_type != CT.WML_DOCUMENT_MAIN: + +opc/constants.py +275: WML_DOCUMENT_MAIN = ( + + +endnotes +-------- + +endnotes = [p for p in doc._package.parts if p._partname == '/word/endnotes.xml'][0] + + +import docx +from docx.oxml.shared import qn + +doc = docx.api.Document('../docx_converter/testdata/endnotes.docx') +doc._package.parts +[p._partname for p in doc._package.parts] +endnotes = [p for p in doc._package.parts if p._partname == '/word/endnotes.xml'][0] +endnotes._element.notes_lst +endnotes.notes +n = endnotes.notes[0] +n.id +n.type +endnotes.get_note(2) diff --git a/docx/__init__.py b/docx/__init__.py index b6147da04..2138470f3 100644 --- a/docx/__init__.py +++ b/docx/__init__.py @@ -14,6 +14,7 @@ from docx.parts.image import ImagePart from docx.parts.numbering import NumberingPart from docx.parts.styles import StylesPart +from docx.parts.endnotes import EndnotesPart def part_class_selector(content_type, reltype): @@ -26,5 +27,6 @@ def part_class_selector(content_type, reltype): PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart PartFactory.part_type_for[CT.WML_STYLES] = StylesPart +PartFactory.part_type_for[CT.WML_ENDNOTES] = EndnotesPart del CT, DocumentPart, PartFactory, part_class_selector diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index 820eeec1d..e6130dcbc 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -45,6 +45,10 @@ register_custom_element_class('w:style', CT_Style) register_custom_element_class('w:styles', CT_Styles) +from docx.oxml.parts.endnotes import CT_Endnotes, CT_Note +register_custom_element_class('w:endnotes', CT_Endnotes) +register_custom_element_class('w:endnote', CT_Note) + from docx.oxml.table import CT_Row, CT_Tbl, CT_TblGrid, CT_TblPr, CT_Tc register_custom_element_class('w:tbl', CT_Tbl) register_custom_element_class('w:tblGrid', CT_TblGrid) diff --git a/docx/oxml/parts/endnotes.py b/docx/oxml/parts/endnotes.py new file mode 100644 index 000000000..37cae82c1 --- /dev/null +++ b/docx/oxml/parts/endnotes.py @@ -0,0 +1,25 @@ +from docx.oxml.shared import OxmlBaseElement, qn +#from docx.oxml.text import CT_P + + +class CT_Endnotes(OxmlBaseElement): + + @property + def notes_lst(self): + return self.findall(qn('w:endnote')) + + +class CT_Note(OxmlBaseElement): + + @property + def type(self): + return self.attrib.get(qn('w:type')) + + @property + def id(self): + return int(self.attrib.get(qn('w:id'))) + + @property + def p_lst(self): + return self.findall(qn('w:p')) + diff --git a/docx/parts/endnotes.py b/docx/parts/endnotes.py new file mode 100644 index 000000000..94bb6494f --- /dev/null +++ b/docx/parts/endnotes.py @@ -0,0 +1,43 @@ +from ..opc.package import Part +from ..oxml.shared import oxml_fromstring +from ..shared import lazyproperty +from ..text import Paragraph + + +class EndnotesPart(Part): + + def __init__(self, partname, content_type, endnotes_elm, package): + super(EndnotesPart, self).__init__( + partname, content_type, package=package + ) + self._element = endnotes_elm + + @classmethod + def load(cls, partname, content_type, blob, package): + """ + Provides PartFactory interface for loading a numbering part from + a WML package. + """ + endnotes_elm = oxml_fromstring(blob) + return cls(partname, content_type, endnotes_elm, package) + + def get_note(self, note_id): + if not hasattr(self, '_notes_map'): + self._notes_map = dict((n.id, n) for n in self.notes) + return self._notes_map[note_id] + + @property + def notes(self): + return [Note(n) for n in self._element.notes_lst] + + +class Note(object): + + def __init__(self, el): + self._element = el + self.id = el.id + self.type = el.type + + @property + def paragraphs(self): + return [Paragraph(p) for p in self._element.p_lst] From 797b49cd6106426967f8a5465875b76d26df052e Mon Sep 17 00:00:00 2001 From: ludo Date: Mon, 19 May 2014 20:36:16 +0200 Subject: [PATCH 02/11] notes-related methods in api.Document --- docx/api.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docx/api.py b/docx/api.py index 88e97c115..5b82c625d 100644 --- a/docx/api.py +++ b/docx/api.py @@ -34,6 +34,12 @@ def __init__(self, docx=None): document_part, package = self._open(docx) self._document_part = document_part self._package = package + self._endnotes_part = self._footnotes_part = None + for part in self._package.parts: + if part._partname == '/word/endnotes.xml': + self._endnotes_part = part + elif part._partname == '/word/footnotes.xml': + self._footnotes_part = part def add_heading(self, text='', level=1): """ @@ -139,7 +145,25 @@ def paragraphs(self): marks such as ```` or ```` do not appear in this list. """ return self._document_part.paragraphs - + + @property + def endnotes(self): + if self._endnotes_part is not None: + return self._endnotes_part.notes + + def get_endnote(self, note_id): + if self._endnotes_part is not None: + return self._endnotes_part.get_note(note_id) + + @property + def footnotes(self): + if self._footnotes_part is not None: + return self._footnotes_part.notes + + def get_footnote(self, note_id): + if self._footnotes_part is not None: + return self._footnotes_part.get_note(note_id) + def save(self, path_or_stream): """ Save this document to *path_or_stream*, which can be either a path to From 381430fb885460f7c37dad4a7157a74fc3f85f0b Mon Sep 17 00:00:00 2001 From: ludo Date: Mon, 19 May 2014 20:38:50 +0200 Subject: [PATCH 03/11] rename endnotes.py to notes.py --- docx/__init__.py | 2 +- docx/oxml/__init__.py | 2 +- docx/oxml/parts/{endnotes.py => notes.py} | 0 docx/parts/{endnotes.py => notes.py} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename docx/oxml/parts/{endnotes.py => notes.py} (100%) rename docx/parts/{endnotes.py => notes.py} (100%) diff --git a/docx/__init__.py b/docx/__init__.py index 2138470f3..a32e312fe 100644 --- a/docx/__init__.py +++ b/docx/__init__.py @@ -14,7 +14,7 @@ from docx.parts.image import ImagePart from docx.parts.numbering import NumberingPart from docx.parts.styles import StylesPart -from docx.parts.endnotes import EndnotesPart +from docx.parts.notes import EndnotesPart def part_class_selector(content_type, reltype): diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index e6130dcbc..b2e967bce 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -45,7 +45,7 @@ register_custom_element_class('w:style', CT_Style) register_custom_element_class('w:styles', CT_Styles) -from docx.oxml.parts.endnotes import CT_Endnotes, CT_Note +from docx.oxml.parts.notes import CT_Endnotes, CT_Note register_custom_element_class('w:endnotes', CT_Endnotes) register_custom_element_class('w:endnote', CT_Note) diff --git a/docx/oxml/parts/endnotes.py b/docx/oxml/parts/notes.py similarity index 100% rename from docx/oxml/parts/endnotes.py rename to docx/oxml/parts/notes.py diff --git a/docx/parts/endnotes.py b/docx/parts/notes.py similarity index 100% rename from docx/parts/endnotes.py rename to docx/parts/notes.py From cc402b937faaacf81f38ff9c90eb9af9f8bc17dc Mon Sep 17 00:00:00 2001 From: ludo Date: Mon, 19 May 2014 20:50:46 +0200 Subject: [PATCH 04/11] fixes --- docx/__init__.py | 5 +++-- docx/oxml/__init__.py | 4 +++- docx/oxml/parts/notes.py | 14 ++++++++++++-- docx/parts/notes.py | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/docx/__init__.py b/docx/__init__.py index a32e312fe..595755e5f 100644 --- a/docx/__init__.py +++ b/docx/__init__.py @@ -14,7 +14,7 @@ from docx.parts.image import ImagePart from docx.parts.numbering import NumberingPart from docx.parts.styles import StylesPart -from docx.parts.notes import EndnotesPart +from docx.parts.notes import NotesPart def part_class_selector(content_type, reltype): @@ -27,6 +27,7 @@ def part_class_selector(content_type, reltype): PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart PartFactory.part_type_for[CT.WML_STYLES] = StylesPart -PartFactory.part_type_for[CT.WML_ENDNOTES] = EndnotesPart +PartFactory.part_type_for[CT.WML_ENDNOTES] = NotesPart +PartFactory.part_type_for[CT.WML_FOOTNOTES] = NotesPart del CT, DocumentPart, PartFactory, part_class_selector diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index b2e967bce..9037a5b05 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -45,9 +45,11 @@ register_custom_element_class('w:style', CT_Style) register_custom_element_class('w:styles', CT_Styles) -from docx.oxml.parts.notes import CT_Endnotes, CT_Note +from docx.oxml.parts.notes import CT_Endnotes, CT_Footnotes, CT_Note register_custom_element_class('w:endnotes', CT_Endnotes) register_custom_element_class('w:endnote', CT_Note) +register_custom_element_class('w:footnotes', CT_Footnotes) +register_custom_element_class('w:footnote', CT_Note) from docx.oxml.table import CT_Row, CT_Tbl, CT_TblGrid, CT_TblPr, CT_Tc register_custom_element_class('w:tbl', CT_Tbl) diff --git a/docx/oxml/parts/notes.py b/docx/oxml/parts/notes.py index 37cae82c1..386c7258c 100644 --- a/docx/oxml/parts/notes.py +++ b/docx/oxml/parts/notes.py @@ -2,11 +2,21 @@ #from docx.oxml.text import CT_P -class CT_Endnotes(OxmlBaseElement): +class CT_Notes(OxmlBaseElement): + + _notes_tag = None @property def notes_lst(self): - return self.findall(qn('w:endnote')) + return self.findall(self._notes_tag) + + +class CT_Endnotes(CT_Notes): + _notes_tag = qn('w:endnote') + + +class CT_Footnotes(CT_Notes): + _notes_tag = qn('w:footnote') class CT_Note(OxmlBaseElement): diff --git a/docx/parts/notes.py b/docx/parts/notes.py index 94bb6494f..df648ff7f 100644 --- a/docx/parts/notes.py +++ b/docx/parts/notes.py @@ -4,10 +4,10 @@ from ..text import Paragraph -class EndnotesPart(Part): +class NotesPart(Part): def __init__(self, partname, content_type, endnotes_elm, package): - super(EndnotesPart, self).__init__( + super(NotesPart, self).__init__( partname, content_type, package=package ) self._element = endnotes_elm From c9bec1d7326c06c60c513ab93d6a663d4c78af16 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 20 May 2014 10:31:35 +0200 Subject: [PATCH 05/11] style attributes, styles iterator --- docx/oxml/parts/styles.py | 14 ++++++++++++++ docx/parts/styles.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/docx/oxml/parts/styles.py b/docx/oxml/parts/styles.py index 58fb6ecfe..9af88dec6 100644 --- a/docx/oxml/parts/styles.py +++ b/docx/oxml/parts/styles.py @@ -14,6 +14,20 @@ class CT_Style(OxmlBaseElement): @property def pPr(self): return self.find(qn('w:pPr')) + + @property + def id(self): + return self.attrib.get(qn('w:styleId')) + + @property + def type(self): + return self.attrib.get(qn('w:type')) + + @property + def name(self): + el = self.find(qn('w:name')) + if el is not None: + return el.attrib.get(qn('w:val')) class CT_Styles(OxmlBaseElement): diff --git a/docx/parts/styles.py b/docx/parts/styles.py index b63317b9e..e68269cc1 100644 --- a/docx/parts/styles.py +++ b/docx/parts/styles.py @@ -48,6 +48,9 @@ def styles(self): proxies) for this styles part. """ return _Styles(self._element) + + def get_style(self, style_id): + return self._element.style_having_styleId(style_id) class _Styles(object): @@ -61,3 +64,6 @@ def __init__(self, styles_elm): def __len__(self): return len(self._styles_elm.style_lst) + + def __iter__(self): + return iter(self._styles_elm.style_lst) From 76a5edc7f7008abed447e56828cd3e0ac02c72a5 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 20 May 2014 11:51:41 +0200 Subject: [PATCH 06/11] proper document parts for notes, nose tests for notes --- docx/api.py | 36 +++++-------- docx/oxml/__init__.py | 4 +- docx/oxml/parts/notes.py | 7 +++ docx/parts/notes.py | 8 ++- notes_tests/__init__.py | 0 notes_tests/data/notes.docx | Bin 0 -> 5948 bytes notes_tests/test_docx.py | 100 ++++++++++++++++++++++++++++++++++++ 7 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 notes_tests/__init__.py create mode 100644 notes_tests/data/notes.docx create mode 100644 notes_tests/test_docx.py diff --git a/docx/api.py b/docx/api.py index 5b82c625d..5cc6803a3 100644 --- a/docx/api.py +++ b/docx/api.py @@ -34,12 +34,6 @@ def __init__(self, docx=None): document_part, package = self._open(docx) self._document_part = document_part self._package = package - self._endnotes_part = self._footnotes_part = None - for part in self._package.parts: - if part._partname == '/word/endnotes.xml': - self._endnotes_part = part - elif part._partname == '/word/footnotes.xml': - self._footnotes_part = part def add_heading(self, text='', level=1): """ @@ -146,23 +140,21 @@ def paragraphs(self): """ return self._document_part.paragraphs - @property - def endnotes(self): - if self._endnotes_part is not None: - return self._endnotes_part.notes - - def get_endnote(self, note_id): - if self._endnotes_part is not None: - return self._endnotes_part.get_note(note_id) - - @property - def footnotes(self): - if self._footnotes_part is not None: - return self._footnotes_part.notes + @lazyproperty + def endnotes_part(self): + return self._notes_part(RT.ENDNOTES) - def get_footnote(self, note_id): - if self._footnotes_part is not None: - return self._footnotes_part.get_note(note_id) + @lazyproperty + def footnotes_part(self): + return self._notes_part(RT.FOOTNOTES) + + def _notes_part(self, rel_type): + try: + return self._document_part.part_related_by(rel_type) + except KeyError: + notes_part = NotesPart.new() + self._document_part.relate_to(notes_part, rel_type) + return notes_part def save(self, path_or_stream): """ diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index 9037a5b05..4b765d19f 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -45,11 +45,13 @@ register_custom_element_class('w:style', CT_Style) register_custom_element_class('w:styles', CT_Styles) -from docx.oxml.parts.notes import CT_Endnotes, CT_Footnotes, CT_Note +from docx.oxml.parts.notes import CT_Endnotes, CT_Footnotes, CT_Note, CT_NoteReference register_custom_element_class('w:endnotes', CT_Endnotes) register_custom_element_class('w:endnote', CT_Note) register_custom_element_class('w:footnotes', CT_Footnotes) register_custom_element_class('w:footnote', CT_Note) +register_custom_element_class('w:endnoteReference', CT_NoteReference) +register_custom_element_class('w:footnoteReference', CT_NoteReference) from docx.oxml.table import CT_Row, CT_Tbl, CT_TblGrid, CT_TblPr, CT_Tc register_custom_element_class('w:tbl', CT_Tbl) diff --git a/docx/oxml/parts/notes.py b/docx/oxml/parts/notes.py index 386c7258c..f61dac18e 100644 --- a/docx/oxml/parts/notes.py +++ b/docx/oxml/parts/notes.py @@ -33,3 +33,10 @@ def id(self): def p_lst(self): return self.findall(qn('w:p')) + +class CT_NoteReference(OxmlBaseElement): + + @property + def id(self): + return int(self.attrib.get(qn('w:id'))) + diff --git a/docx/parts/notes.py b/docx/parts/notes.py index df648ff7f..70318de3f 100644 --- a/docx/parts/notes.py +++ b/docx/parts/notes.py @@ -18,8 +18,12 @@ def load(cls, partname, content_type, blob, package): Provides PartFactory interface for loading a numbering part from a WML package. """ - endnotes_elm = oxml_fromstring(blob) - return cls(partname, content_type, endnotes_elm, package) + notes_elm = oxml_fromstring(blob) + return cls(partname, content_type, notes_elm, package) + + @classmethod + def new(cls): + raise NotImplementedError def get_note(self, note_id): if not hasattr(self, '_notes_map'): diff --git a/notes_tests/__init__.py b/notes_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/notes_tests/data/notes.docx b/notes_tests/data/notes.docx new file mode 100644 index 0000000000000000000000000000000000000000..c750bdbbe8a1b8b6223c8ee5427d00050dbb3d4b GIT binary patch literal 5948 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 Date: Tue, 20 May 2014 12:06:43 +0200 Subject: [PATCH 07/11] run endnote_reference and footnote_reference properties --- docx/oxml/text.py | 8 ++++++++ docx/parts/notes.py | 12 ++++++++++++ docx/text.py | 17 ++++++++++++++++- notes_tests/data/notes.docx | Bin 5948 -> 5986 bytes notes_tests/test_docx.py | 15 ++++++++++----- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/docx/oxml/text.py b/docx/oxml/text.py index 37b1edefe..29eeb39dc 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -294,6 +294,14 @@ def t_lst(self): Sequence of elements in this paragraph. """ return self.findall(qn('w:t')) + + @property + def endnote_ref(self): + return self.find(qn('w:endnoteReference')) + + @property + def footnote_ref(self): + return self.find(qn('w:footnoteReference')) @property def underline(self): diff --git a/docx/parts/notes.py b/docx/parts/notes.py index 70318de3f..1e56a5ffc 100644 --- a/docx/parts/notes.py +++ b/docx/parts/notes.py @@ -45,3 +45,15 @@ def __init__(self, el): @property def paragraphs(self): return [Paragraph(p) for p in self._element.p_lst] + + +class NoteReference(object): + + def __init__(self, el, note_type): + self._element = el + self.type = note_type + + @property + def id(self): + return self._element.id + diff --git a/docx/text.py b/docx/text.py index daf08c000..13b2132c7 100644 --- a/docx/text.py +++ b/docx/text.py @@ -328,7 +328,19 @@ def text(self): for t in self._r.t_lst: text += t.text return text - + + @property + def endnote_reference(self): + note = self._r.endnote_ref + if note is not None: + return NoteReference(note, 'endnote') + + @property + def footnote_reference(self): + note = self._r.footnote_ref + if note is not None: + return NoteReference(note, 'footnote') + @property def underline(self): """ @@ -365,3 +377,6 @@ class Text(object): def __init__(self, t_elm): super(Text, self).__init__() self._t = t_elm + + +from docx.parts.notes import NoteReference diff --git a/notes_tests/data/notes.docx b/notes_tests/data/notes.docx index c750bdbbe8a1b8b6223c8ee5427d00050dbb3d4b..99fd000f276416b66d23fa75ba9124c7a596c097 100644 GIT binary patch delta 2815 zcmZ8jbyO2x8y*AcksF&dNO$+7m1ZzR2?eAP2?1piLuz!59v>p{6A=U)4T`kHXr&Pc zNT@KR6huG7bG}c1_n+sD=brnXd*0{Xw@r#D1wt5;Q-A@~)YO14W_7h7Ml$kK#Pg7G z{Wdg#s%J(?2>BUedC1uCnG{REoqLpmXrDP5`j^|H^be<|4O@%k^TAyS4C>w@*`LV3 zxQ3XGzO>w?nTgZ{7vW123Ue)8QWhdVLU|11zYso`fX674R&5Ey9g*YI&)*5AIEb2qF#={m;VKVLoVw(yRIM!+!iVyydn0%Qy<+Ru zB7tIXijTtN^(fxN3L5&cZ%~3ajBX3)vY2KC=d?y-nT!YK!SOmJ$6%W&e==2T`}LndO$c3j+7OH6F?9 zeATMk&@y>8zQkhimFG~C<`(XKXun0ooOb&*eAr;AXT6$(z}&Xhdvj8zOROtj%}ezy zbD}+!2xqyDXY^|=n%FJcHm=ejSZ4MGE)-OS0?V{*2Te&r)%tetYAiIPXX`O7qp%uB zC8m)J-uuXugLi3d8fIU7b#9z^E()`(v<3`o&bmx{q?nNAFMj+cCj#n4zIw_<+N2>F zB@#I&zpZOF%hA17%px|cBfXNl$W@OByDD7Vg;GoLcDwgCs!WLZ?@L0wu7XXG9{Fb5bQ&FBdiGn9IH!pX8@otS9AhO=^8?On z2dkiHO4`_8t;fCJV@v7Vp73-h#hh23Q7q0j3qq|{a>^@zz31F+yL_Yk0pta5QTGe% z`zv9*#XgZtMK7PNjw2-n8NA*DK=6w#BTX)yu>xt9Ez7Z+#@lFf2WGrIN-+!@DmM>z zLlK5L9j=a~*dDx+tv*SFd^1vA%BVUQYk4=>xaClB2E8_fCPNrgQF|YfH`D+D00|lZ z;4cYauh8(LS1nCbXJwdP9A4E^Z4}T-5aIvis&8I3s)LU37m6?Ys(avq-wz2mFN)qOOqJ8rzNAaU!JTFBcfQfVb6d4flzM_ey~n{CvfUIqu2lP`1Hu z>hO{uZuShazgG$$tCNmWYmFTEf^A!AsUJ&0?Bl4Hd--~$Iff;;>#f+5#tkrmwzsJ=PAKr_WH^- zFvGX9genS~SX3J3r!Cw2*RBAqpMZOAQ#P7t{#-NpiP|{aGSLj+Zt^)ADp_Di8vpIwuTH`pL+%QkcG4HB ze|HiO84No@$AI>UlA-V24x!CFrq-WT;48|hSo2!o<|Rk+YjgJaocC=fxyO*>8WJze zIfOrK5Bxc!)B)o3RLGl-NvD1VzUCFs-+er{v!hSZJ&;+UO`f;@p!*`)ptV%TC5j## z9Q~-)f|+PGQL8RAq%7Y;F@PhO2i--<4E7=FyPZdW+9bmF7%}NFUpib}ZZw0b~_|bE}zLl_d zS7GnE$;Y{= zBvolWPqE;*@A8bQ(S7`nLLtR#k6SyaRc9Rw9feeA#_~M^Lv}y6MjMr~$d0i$jO20zFP7n)2F6VBkC3Um0(z?y z00=8S+pnRUwi=x6`DEDNt=>U~ZmhGgGtF+G|EcH7K0}%P59_-k9{dS;OP6TgmP^V` zl#a`(jPFxFOjNAmy&36cwXs{RWy57wOGE`_G%;Kf*%?%F+se%A zGkbCcMjhbtrY1!cG{#DZm^~+2pvm7g&kgPBN9xN46spDmnAg@yvREq#R!Gj`ZKKg{ zRJXkyN^SM%wOJXq)=@{XNaGl1O?On4PPvxs^3S0w{VwD+fBq{nQLZ6qtJdOphj;Cx zI8w*1^XH(AXLcBL6Z}JXPVPHhZ{B3ib|A#bG{YfO_{)zbLBYs{f+du-sYu9-fPNM5 z)^2I~w|X89@^p?%s;Fssb!tB=r-BW5KBj|@kX1=#2i*?r5hX!5iW%0AI#{LX{bf>- zk8FbC*@q1S)_4Ni)cfC7fZVu7FVFi-U3>C!#2z< za_L3+5v+Gz0k+JYQ*%Olf5B(TL1zURSTV@!Z!*XdmD8K;pefR-rB{Swvcse}JUN{poSy;D4hf#IH$Wa+rKk=vQ5QP8qUEyI#=;CifwXnBP31v zlg?s+@X9{LW-M3v@%W_5Hu3rb)Qq&nU$1z|bRHVhobdJ?U zoVb`C7XnvpUhg!VC^O?-C29(lDTav4FJQ>5D&h5h9_W+Gt_{<8BX(h|Nr~27>F#-uh5^57&G9{WrLl9 z@Sbu06fDc16hIr6jg9Y&WBNzkjDK+$vDR$d*MFV$3c4k@beeNH6+hEo9Nkmb#n%~e z$JZ}F+R4vPD)_cH!k7#Q2K;YA)w=>8&=5M^Nqc%be^=^+9b@}lY4A`?o6#xf<2e9; S^{*3pu}n}QN(=C>;C})4%=)AN delta 2699 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 diff --git a/notes_tests/test_docx.py b/notes_tests/test_docx.py index b8300c427..c8e5c7ac1 100644 --- a/notes_tests/test_docx.py +++ b/notes_tests/test_docx.py @@ -5,7 +5,7 @@ from docx import api from docx.oxml.shared import qn -from docx.parts.notes import NotesPart, Note +from docx.parts.notes import NotesPart, Note, NoteReference from docx.oxml.parts.notes import CT_NoteReference @@ -25,7 +25,7 @@ def test_parts(): def test_notes(): part = DOC.endnotes_part assert_equals(type(part.notes), list) - assert_equals(len(part.notes), 4) + assert_equals(len(part.notes), 5) def test_footnotes(): @@ -93,8 +93,13 @@ def test_style_iterator(): def test_endnoteref(): run = DOC.paragraphs[2].runs[1] assert_equals(run.text, '') - endnoteref = run._r[1] - assert_equals(endnoteref.tag, qn('w:endnoteReference')) - assert_equals(type(endnoteref), CT_NoteReference) + _endnoteref = run._r[1] + assert_equals(_endnoteref.tag, qn('w:endnoteReference')) + assert_equals(type(_endnoteref), CT_NoteReference) + assert_equals(_endnoteref.id, 2) + endnoteref = run.endnote_reference + assert_true(endnoteref) + assert_equals(type(endnoteref), NoteReference) + assert_equals(endnoteref.id, _endnoteref.id) \ No newline at end of file From 4d958a0e208297b86824ce355d1e7b3b9edcfb96 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 20 May 2014 12:08:52 +0200 Subject: [PATCH 08/11] remove work notes --- MODS.txt | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 MODS.txt diff --git a/MODS.txt b/MODS.txt deleted file mode 100644 index fb2b06d8a..000000000 --- a/MODS.txt +++ /dev/null @@ -1,39 +0,0 @@ -content_type ------------- - -'application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml' -WML_ENDNOTES -WML_FOOTNOTES - -register part -------------- - -__init__.py -26:PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart - -api.py -183: if document_part.content_type != CT.WML_DOCUMENT_MAIN: - -opc/constants.py -275: WML_DOCUMENT_MAIN = ( - - -endnotes --------- - -endnotes = [p for p in doc._package.parts if p._partname == '/word/endnotes.xml'][0] - - -import docx -from docx.oxml.shared import qn - -doc = docx.api.Document('../docx_converter/testdata/endnotes.docx') -doc._package.parts -[p._partname for p in doc._package.parts] -endnotes = [p for p in doc._package.parts if p._partname == '/word/endnotes.xml'][0] -endnotes._element.notes_lst -endnotes.notes -n = endnotes.notes[0] -n.id -n.type -endnotes.get_note(2) From c091a89d40b6cf51285c4eeb44156e9a78a9bd9f Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 26 May 2014 11:21:58 +0200 Subject: [PATCH 09/11] move NoteReference to docx.text to prevent circular imports --- docx/api.py | 1 + docx/parts/notes.py | 12 ------------ docx/text.py | 10 +++++++++- notes_tests/test_docx.py | 3 ++- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/docx/api.py b/docx/api.py index 5cc6803a3..66a5ef754 100644 --- a/docx/api.py +++ b/docx/api.py @@ -15,6 +15,7 @@ from docx.package import Package from docx.parts.numbering import NumberingPart from docx.parts.styles import StylesPart +from docx.parts.notes import NotesPart from docx.shared import lazyproperty diff --git a/docx/parts/notes.py b/docx/parts/notes.py index 1e56a5ffc..70318de3f 100644 --- a/docx/parts/notes.py +++ b/docx/parts/notes.py @@ -45,15 +45,3 @@ def __init__(self, el): @property def paragraphs(self): return [Paragraph(p) for p in self._element.p_lst] - - -class NoteReference(object): - - def __init__(self, el, note_type): - self._element = el - self.type = note_type - - @property - def id(self): - return self._element.id - diff --git a/docx/text.py b/docx/text.py index 13b2132c7..33c3ce6ea 100644 --- a/docx/text.py +++ b/docx/text.py @@ -379,4 +379,12 @@ def __init__(self, t_elm): self._t = t_elm -from docx.parts.notes import NoteReference +class NoteReference(object): + + def __init__(self, el, note_type): + self._element = el + self.type = note_type + + @property + def id(self): + return self._element.id diff --git a/notes_tests/test_docx.py b/notes_tests/test_docx.py index c8e5c7ac1..923dc5996 100644 --- a/notes_tests/test_docx.py +++ b/notes_tests/test_docx.py @@ -5,7 +5,8 @@ from docx import api from docx.oxml.shared import qn -from docx.parts.notes import NotesPart, Note, NoteReference +from docx.text import NoteReference +from docx.parts.notes import NotesPart, Note from docx.oxml.parts.notes import CT_NoteReference From d311d64afd8b4c713e87879fc694f1b030c00ddf Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Mon, 26 May 2014 12:13:38 +0200 Subject: [PATCH 10/11] don't set a notes part if none exist in the document --- docx/api.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docx/api.py b/docx/api.py index 66a5ef754..ae0b76d1f 100644 --- a/docx/api.py +++ b/docx/api.py @@ -153,9 +153,7 @@ def _notes_part(self, rel_type): try: return self._document_part.part_related_by(rel_type) except KeyError: - notes_part = NotesPart.new() - self._document_part.relate_to(notes_part, rel_type) - return notes_part + pass def save(self, path_or_stream): """ From eb1e6231ce18b76af5416d10e1cf75d65efd3250 Mon Sep 17 00:00:00 2001 From: ludo Date: Thu, 29 May 2014 22:01:59 +0200 Subject: [PATCH 11/11] run elements, notes changes --- docx/oxml/__init__.py | 9 ++-- docx/oxml/parts/notes.py | 8 ++- docx/oxml/text.py | 16 ++++-- docx/text.py | 98 +++++++++++++++++++++++++---------- notes_tests/data/run.docx | Bin 0 -> 5256 bytes notes_tests/test_docx.py | 35 +++++++++---- notes_tests/test_docx_run.py | 35 +++++++++++++ 7 files changed, 154 insertions(+), 47 deletions(-) create mode 100644 notes_tests/data/run.docx create mode 100644 notes_tests/test_docx_run.py diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index 4b765d19f..ece0e859b 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -45,13 +45,13 @@ register_custom_element_class('w:style', CT_Style) register_custom_element_class('w:styles', CT_Styles) -from docx.oxml.parts.notes import CT_Endnotes, CT_Footnotes, CT_Note, CT_NoteReference +from docx.oxml.parts.notes import CT_Endnotes, CT_Footnotes, CT_Note, CT_EndnoteReference, CT_FootnoteReference register_custom_element_class('w:endnotes', CT_Endnotes) register_custom_element_class('w:endnote', CT_Note) register_custom_element_class('w:footnotes', CT_Footnotes) register_custom_element_class('w:footnote', CT_Note) -register_custom_element_class('w:endnoteReference', CT_NoteReference) -register_custom_element_class('w:footnoteReference', CT_NoteReference) +register_custom_element_class('w:endnoteReference', CT_EndnoteReference) +register_custom_element_class('w:footnoteReference', CT_FootnoteReference) from docx.oxml.table import CT_Row, CT_Tbl, CT_TblGrid, CT_TblPr, CT_Tc register_custom_element_class('w:tbl', CT_Tbl) @@ -62,10 +62,11 @@ register_custom_element_class('w:tr', CT_Row) from docx.oxml.text import ( - CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text, CT_Underline + CT_Tab, CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text, CT_Underline ) register_custom_element_class('w:b', CT_OnOff) register_custom_element_class('w:bCs', CT_OnOff) +register_custom_element_class('w:tab', CT_Tab) register_custom_element_class('w:br', CT_Br) register_custom_element_class('w:caps', CT_OnOff) register_custom_element_class('w:cs', CT_OnOff) diff --git a/docx/oxml/parts/notes.py b/docx/oxml/parts/notes.py index f61dac18e..cedc4b9a7 100644 --- a/docx/oxml/parts/notes.py +++ b/docx/oxml/parts/notes.py @@ -1,5 +1,4 @@ from docx.oxml.shared import OxmlBaseElement, qn -#from docx.oxml.text import CT_P class CT_Notes(OxmlBaseElement): @@ -40,3 +39,10 @@ class CT_NoteReference(OxmlBaseElement): def id(self): return int(self.attrib.get(qn('w:id'))) + +class CT_EndnoteReference(CT_NoteReference): + pass + + +class CT_FootnoteReference(CT_NoteReference): + pass diff --git a/docx/oxml/text.py b/docx/oxml/text.py index 29eeb39dc..785f25964 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -12,6 +12,14 @@ ) +class CT_Tab(OxmlBaseElement): + + @classmethod + def new(cls): + return OxmlElement('w:tab') + + + class CT_Br(OxmlBaseElement): """ ```` element, indicating a line, page, or column break in a run. @@ -296,12 +304,12 @@ def t_lst(self): return self.findall(qn('w:t')) @property - def endnote_ref(self): - return self.find(qn('w:endnoteReference')) + def endnote_refs(self): + return self.findall(qn('w:endnoteReference')) @property - def footnote_ref(self): - return self.find(qn('w:footnoteReference')) + def footnote_refs(self): + return self.findall(qn('w:footnoteReference')) @property def underline(self): diff --git a/docx/text.py b/docx/text.py index 33c3ce6ea..f8f76cb71 100644 --- a/docx/text.py +++ b/docx/text.py @@ -6,6 +6,8 @@ from __future__ import absolute_import, print_function, unicode_literals +from docx.oxml.text import CT_RPr, CT_Text, CT_Br, CT_Tab +from docx.oxml.parts.notes import CT_EndnoteReference, CT_FootnoteReference from docx.enum.text import WD_BREAK @@ -103,6 +105,43 @@ def text(self): return text +class Text(object): + """ + Proxy object wrapping ```` element. + """ + def __init__(self, t_elm): + super(Text, self).__init__() + self._t = t_elm + + +class NoteReference(object): + + def __init__(self, el, note_type=None): + self._element = el + + @property + def id(self): + return self._element.id + + +class EndnoteReference(NoteReference): + pass + + +class FootnoteReference(NoteReference): + pass + + +class RunElement(object): + + def __init__(self, el): + self._element = el + + +class LineBreak(RunElement): pass +class Tab(RunElement): pass + + class Run(object): """ Proxy object wrapping ```` element. Several of the properties on Run @@ -111,9 +150,35 @@ class Run(object): not specified directly on the run and its effective value is taken from the style hierarchy. """ + + _elements_map = { + CT_RPr:None, + CT_Text:Text, + CT_Br:LineBreak, + CT_Tab:Tab, + CT_EndnoteReference:EndnoteReference, + CT_FootnoteReference:FootnoteReference, + } + def __init__(self, r): super(Run, self).__init__() self._r = r + + def get_elements(self): + + elements_map = self._elements_map + + for el in self._r.getchildren(): + + element_type = type(el) + + if element_type not in elements_map: + raise ValueError("No mapping for element type %s" % element_type) + + wrapper = elements_map.get(element_type) + + if wrapper: + yield wrapper(el) def add_break(self, break_type=WD_BREAK.LINE): """ @@ -330,17 +395,13 @@ def text(self): return text @property - def endnote_reference(self): - note = self._r.endnote_ref - if note is not None: - return NoteReference(note, 'endnote') + def endnote_references(self): + return [EndnoteReference(el, 'endnote') for el in self._r.endnote_refs] @property - def footnote_reference(self): - note = self._r.footnote_ref - if note is not None: - return NoteReference(note, 'footnote') - + def footnote_references(self): + return [FootnoteReference(el, 'footnote') for el in self._r.footnote_refs] + @property def underline(self): """ @@ -369,22 +430,3 @@ def web_hidden(self): """ return 'webHidden' - -class Text(object): - """ - Proxy object wrapping ```` element. - """ - def __init__(self, t_elm): - super(Text, self).__init__() - self._t = t_elm - - -class NoteReference(object): - - def __init__(self, el, note_type): - self._element = el - self.type = note_type - - @property - def id(self): - return self._element.id diff --git a/notes_tests/data/run.docx b/notes_tests/data/run.docx new file mode 100644 index 0000000000000000000000000000000000000000..e2dca75d11f743b7812835a72c4e36c66f8c1504 GIT binary patch literal 5256 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 Ie81?w0S0%Yi~s-t literal 0 HcmV?d00001 diff --git a/notes_tests/test_docx.py b/notes_tests/test_docx.py index 923dc5996..4ffc82c1e 100644 --- a/notes_tests/test_docx.py +++ b/notes_tests/test_docx.py @@ -5,9 +5,9 @@ from docx import api from docx.oxml.shared import qn -from docx.text import NoteReference +from docx.text import EndnoteReference, FootnoteReference from docx.parts.notes import NotesPart, Note -from docx.oxml.parts.notes import CT_NoteReference +from docx.oxml.parts.notes import CT_EndnoteReference, CT_FootnoteReference logger = logging.getLogger('docx_converter.tests.docx') @@ -90,17 +90,32 @@ def test_style_iterator(): ] ) - -def test_endnoteref(): + +def test_endnoterefs(): run = DOC.paragraphs[2].runs[1] assert_equals(run.text, '') _endnoteref = run._r[1] assert_equals(_endnoteref.tag, qn('w:endnoteReference')) - assert_equals(type(_endnoteref), CT_NoteReference) + assert_equals(type(_endnoteref), CT_EndnoteReference) assert_equals(_endnoteref.id, 2) - endnoteref = run.endnote_reference - assert_true(endnoteref) - assert_equals(type(endnoteref), NoteReference) - assert_equals(endnoteref.id, _endnoteref.id) + endnoterefs = run.endnote_references + assert_true(endnoterefs) + assert_equals(len(endnoterefs), 1) + assert_equals(type(endnoterefs[0]), EndnoteReference) + assert_equals(endnoterefs[0].id, _endnoteref.id) + + +def test_footnoterefs(): + run = DOC.paragraphs[4].runs[1] + assert_equals(run.text, '') + _footnoteref = run._r[1] + assert_equals(_footnoteref.tag, qn('w:footnoteReference')) + assert_equals(type(_footnoteref), CT_FootnoteReference) + assert_equals(_footnoteref.id, 2) + footnoterefs = run.footnote_references + assert_true(footnoterefs) + assert_equals(len(footnoterefs), 1) + assert_equals(type(footnoterefs[0]), FootnoteReference) + assert_equals(footnoterefs[0].id, _footnoteref.id) - \ No newline at end of file + diff --git a/notes_tests/test_docx_run.py b/notes_tests/test_docx_run.py new file mode 100644 index 000000000..cf7e2ec26 --- /dev/null +++ b/notes_tests/test_docx_run.py @@ -0,0 +1,35 @@ +import os +import logging + +from types import GeneratorType +from itertools import chain + +from nose.tools import * + +from docx import api +from docx import text + + +logger = logging.getLogger('docx_converter.tests.docx_run') + +DOC = api.Document(os.path.join( + os.path.dirname(__file__), + 'data/run.docx' +)) + + +def test_get_elements_type(): + result = DOC.paragraphs[0].runs[0].get_elements() + assert_equals(type(result), GeneratorType) + assert_equals([type(e) for e in result], [text.Text]) + + +def test_element_classes(): + result = set() + for p in DOC.paragraphs: + for r in p.runs: + for el in r.get_elements(): + result.add(type(el)) + expected = set([text.Text, text.LineBreak, text.EndnoteReference, text.FootnoteReference, text.Tab]) + assert_equals(result, expected) +