Skip to content

Commit 8e7a17a

Browse files
author
Steve Canny
committed
oxml: add CT_Body.add_p()
1 parent c924d2a commit 8e7a17a

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

docx/oxml/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
"""
1414

1515
from docx.oxml.base import register_custom_element_class
16+
from docx.oxml.parts import CT_Body
1617
from docx.oxml.text import CT_P
1718

1819

1920
# ===========================================================================
2021
# custom element class mappings
2122
# ===========================================================================
2223

24+
register_custom_element_class('w:body', CT_Body)
2325
register_custom_element_class('w:p', CT_P)

docx/oxml/parts.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# oxml/parts.py
4+
#
5+
# Copyright (C) 2013 Steve Canny scanny@cisco.com
6+
#
7+
# This module is part of python-docx and is released under the MIT License:
8+
# http://www.opensource.org/licenses/mit-license.php
9+
10+
"""
11+
Custom element classes that correspond to OPC parts like <w:document>.
12+
"""
13+
14+
from docx.oxml.base import OxmlBaseElement
15+
from docx.oxml.text import CT_P
16+
17+
18+
class CT_Body(OxmlBaseElement):
19+
"""
20+
``<w:body>``, the container element for the main document story in
21+
``document.xml``.
22+
"""
23+
def add_p(self):
24+
"""
25+
Return a new <w:p> element that has been added at the end of any
26+
existing body content.
27+
"""
28+
p = CT_P.new()
29+
if hasattr(self, 'sectPr'):
30+
self.sectPr.addprevious(p)
31+
else:
32+
self.append(p)
33+
return p
34+
35+
@property
36+
def _has_sectPr(self):
37+
"""
38+
Return True if this <w:body> element has a <w:sectPr> child element,
39+
False otherwise.
40+
"""
41+
return hasattr(self, 'sectPr')

tests/oxml/test_parts.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# test_parts.py
4+
#
5+
# Copyright (C) 2013 Steve Canny scanny@cisco.com
6+
#
7+
# This module is part of python-docx and is released under the MIT License:
8+
# http://www.opensource.org/licenses/mit-license.php
9+
10+
"""Test suite for the docx.oxml.parts module."""
11+
12+
from docx.oxml.text import CT_P
13+
14+
from ..unitdata import a_body
15+
16+
17+
class DescribeCT_Body(object):
18+
19+
def it_can_add_a_p_to_itself(self):
20+
"""
21+
Return a newly created |CT_P| element that has been added after any
22+
existing content.
23+
"""
24+
cases = (
25+
(a_body(), a_body().with_p()),
26+
(a_body().with_sectPr(), a_body().with_p().with_sectPr()),
27+
)
28+
for before_body_bldr, after_body_bldr in cases:
29+
body = before_body_bldr.element
30+
# exercise -----------------
31+
p = body.add_p()
32+
# verify -------------------
33+
assert body.xml == after_body_bldr.xml
34+
assert isinstance(p, CT_P)

tests/unitdata.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,47 @@ def with_nsdecls(self):
4343
return self
4444

4545

46+
class CT_BodyBuilder(BaseBuilder):
47+
"""
48+
Test data builder for CT_Body (<w:body>) XML element that appears in
49+
document.xml files.
50+
"""
51+
def __init__(self):
52+
"""Establish instance variables with default values"""
53+
super(CT_BodyBuilder, self).__init__()
54+
self._p = None
55+
self._sectPr = None
56+
57+
@property
58+
def is_empty(self):
59+
return self._p is None and self._sectPr is None
60+
61+
def with_p(self):
62+
"""Add an empty paragraph element"""
63+
self._p = a_p()
64+
return self
65+
66+
def with_sectPr(self):
67+
"""Add an empty section properties element"""
68+
self._sectPr = a_sectPr()
69+
return self
70+
71+
@property
72+
def xml(self):
73+
"""Return element XML based on attribute settings"""
74+
indent = ' ' * self._indent
75+
if self.is_empty:
76+
xml = '%s<w:body %s/>\n' % (indent, nsdecls('w'))
77+
else:
78+
xml = '%s<w:body %s>\n' % (indent, nsdecls('w'))
79+
if self._p:
80+
xml += self._p.with_indent(self._indent+2).xml
81+
if self._sectPr:
82+
xml += self._sectPr.with_indent(self._indent+2).xml
83+
xml += '%s</w:body>\n' % indent
84+
return xml
85+
86+
4687
class CT_PBuilder(BaseBuilder):
4788
"""
4889
Test data builder for a CT_P (<w:p>) XML element that appears within the
@@ -60,6 +101,33 @@ def xml(self):
60101
return xml
61102

62103

104+
class CT_SectPrBuilder(BaseBuilder):
105+
"""
106+
Test data builder for a CT_SectPr (<w:sectPr>) XML element that appears
107+
within the body element of a document.xml file.
108+
"""
109+
def __init__(self):
110+
"""Establish instance variables with default values"""
111+
super(CT_SectPrBuilder, self).__init__()
112+
113+
@property
114+
def xml(self):
115+
"""Return element XML based on attribute settings"""
116+
tmpl = '%s<w:sectPr/>\n'
117+
indent = ' ' * self._indent
118+
return tmpl % (indent)
119+
120+
121+
def a_body():
122+
"""Return a CT_BodyBuilder instance"""
123+
return CT_BodyBuilder()
124+
125+
63126
def a_p():
64127
"""Return a CT_PBuilder instance"""
65128
return CT_PBuilder()
129+
130+
131+
def a_sectPr():
132+
"""Return a CT_SectPr instance"""
133+
return CT_SectPrBuilder()

0 commit comments

Comments
 (0)