Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
_scratch/
Session.vim
/.tox/


2 changes: 1 addition & 1 deletion docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from docx.api import Document # noqa

__version__ = '0.8.10'
__version__ = '0.9.15'


# register custom Part classes with opc package reader
Expand Down
15 changes: 15 additions & 0 deletions docx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ def add_page_break(self):
paragraph.add_run().add_break(WD_BREAK.PAGE)
return paragraph

def get_new_list(self, abstractNumId):
"""
Returns a new numId that references given abstractNumId
"""
return self.numbering.numbering_definitions.add_num(abstractNumId, True)

def add_paragraph(self, text='', style=None):
"""
Return a paragraph newly added to the end of the document, populated
Expand Down Expand Up @@ -154,6 +160,14 @@ def styles(self):
"""
return self._part.styles

@property
def numbering(self):
"""
A "Provides access to numbering part
"""
x = self._part.numbering_part
return self._part.numbering_part

@property
def tables(self):
"""
Expand Down Expand Up @@ -191,6 +205,7 @@ class _Body(BlockItemContainer):
Proxy for ``<w:body>`` element in this document, having primarily a
container role.
"""

def __init__(self, body_elm, parent):
super(_Body, self).__init__(body_elm, parent)
self._body = body_elm
Expand Down
4 changes: 3 additions & 1 deletion docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):

from .shared import CT_DecimalNumber, CT_OnOff, CT_String # noqa
register_element_cls("w:evenAndOddHeaders", CT_OnOff)
register_element_cls("w:trackRevisions", CT_OnOff)
register_element_cls("w:titlePg", CT_OnOff)


Expand Down Expand Up @@ -242,7 +243,8 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:tabs', CT_TabStops)
register_element_cls('w:widowControl', CT_OnOff)

from .text.run import CT_Br, CT_R, CT_Text # noqa
from .text.run import CT_Br, CT_R, CT_Text, CT_Hyperlink # noqa
register_element_cls('w:br', CT_Br)
register_element_cls('w:r', CT_R)
register_element_cls('w:t', CT_Text)
register_element_cls('w:hyperlink', CT_Hyperlink)
5 changes: 4 additions & 1 deletion docx/oxml/numbering.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,16 @@ class CT_Numbering(BaseOxmlElement):
"""
num = ZeroOrMore('w:num', successors=('w:numIdMacAtCleanup',))

def add_num(self, abstractNum_id):
def add_num(self, abstractNum_id, restart=False):
"""
Return a newly added CT_Num (<w:num>) element referencing the
abstract numbering definition identified by *abstractNum_id*.
"""
next_num_id = self._next_numId
num = CT_Num.new(next_num_id, abstractNum_id)
if restart:
num.add_lvlOverride(ilvl=0).add_startOverride(1)

return self._insert_num(num)

def num_having_numId(self, numId):
Expand Down
16 changes: 16 additions & 0 deletions docx/oxml/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class CT_Settings(BaseOxmlElement):
"w:readModeInkLockDown", "w:smartTagType", "sl:schemaLibrary",
"w:shapeDefaults", "w:doNotEmbedSmartTags", "w:decimalSymbol", "w:listSeparator"
)
trackRevisions = ZeroOrOne("w:trackRevisions", successors=())
evenAndOddHeaders = ZeroOrOne("w:evenAndOddHeaders", successors=_tag_seq[48:])
del _tag_seq

Expand All @@ -62,3 +63,18 @@ def evenAndOddHeaders_val(self, value):
self._remove_evenAndOddHeaders()
else:
self.get_or_add_evenAndOddHeaders().val = value

@property
def trackRevisions_val(self):
"""value of `w:trackRevisions/@w:val` or |None| if not present."""
trackRevisions = self.trackRevisions
if trackRevisions is None:
return False
return trackRevisions.val

@trackRevisions_val.setter
def trackRevisions_val(self, value):
if value in [None, False]:
self._remove_trackRevisions()
else:
self.get_or_add_trackRevisions().val = value
30 changes: 28 additions & 2 deletions docx/oxml/text/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"""

from ..ns import qn
from ..simpletypes import ST_BrClear, ST_BrType
from ..simpletypes import ST_BrClear, ST_BrType, ST_RelationshipId
from ..xmlchemy import (
BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne
BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne, OneOrMore, RequiredAttribute
)

from docx.styles import style


class CT_Br(BaseOxmlElement):
"""
Expand Down Expand Up @@ -164,3 +166,27 @@ def flush(self):
if text:
self._r.add_t(text)
del self._bfr[:]


class CT_Hyperlink(BaseOxmlElement):
"""
``<w:hyperlink>`` element, containing the properties and text for a hyperlink.
The ``<w:hyperlink>`` contains a ``<w:r>`` element which holds all the
visible content. The ``<w:hyperlink>`` has an attribute ``r:id`` which
holds an ID relating a URL in the document's relationships.
"""
r = ZeroOrMore('w:r')
rid = RequiredAttribute('r:id', ST_RelationshipId)

@property
def relationship(self):
"""
String contained in ``r:id`` attribute of <w:hyperlink>. It should
point to a URL in the document's relationships.
"""
val = self.get(qn('r:id'))
return val
@relationship.setter
def relationship(self, rId):
self.set(qn('r:id'), rId)
self.set(qn('w:history'), '1')
4 changes: 4 additions & 0 deletions docx/parts/numbering.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ class _NumberingDefinitions(object):
Collection of |_NumberingDefinition| instances corresponding to the
``<w:num>`` elements in a numbering part.
"""

def __init__(self, numbering_elm):
super(_NumberingDefinitions, self).__init__()
self._numbering = numbering_elm

def add_num(self, abstractNum_id, restart=False):
return self._numbering.add_num(abstractNum_id, restart).numId

def __len__(self):
return len(self._numbering.num_lst)
12 changes: 12 additions & 0 deletions docx/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,15 @@ def odd_and_even_pages_header_footer(self):
@odd_and_even_pages_header_footer.setter
def odd_and_even_pages_header_footer(self, value):
self._element.evenAndOddHeaders_val = value

@property
def track_revisions(self):
"""True if this document has distinct odd and even page headers and footers.

Read/write.
"""
return self._element.trackRevisions_val

@track_revisions.setter
def track_revisions(self, value):
self._element.trackRevisions_val = value
15 changes: 15 additions & 0 deletions docx/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ def __new__(cls, twips):
return Length.__new__(cls, emu)


class IntOrPercent(object):
"""
Integer or a percentage value
"""

def __init__(self, str):
if str[-1] == "%":
self.is_percent = True
self.value = int(str[:-1])
else:
self.is_percent = False
self.value = int(str)


class RGBColor(tuple):
"""
Immutable value object defining a particular RGB color.
Expand Down Expand Up @@ -238,6 +252,7 @@ class Parented(object):
such as add or drop a relationship. Provides ``self._parent`` attribute
to subclasses.
"""

def __init__(self, parent):
super(Parented, self).__init__()
self._parent = parent
Expand Down
154 changes: 154 additions & 0 deletions docx/text/paragraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
from .parfmt import ParagraphFormat
from .run import Run
from ..shared import Parented
from docx.opc.constants import RELATIONSHIP_TYPE as RT


class Paragraph(Parented):
"""
Proxy object wrapping ``<w:p>`` element.
"""

def __init__(self, p, parent):
super(Paragraph, self).__init__(parent)
self._p = self._element = p
Expand Down Expand Up @@ -136,10 +138,162 @@ def text(self, text):
self.clear()
self.add_run(text)

@property
def num_id(self):
"""
Return the numId of the parent list.
"""
if self._p.pPr is None:
return None
if self._p.pPr.numPr is None:
return None
if self._p.pPr.numPr.numId is None:
return None
return self._p.pPr.numPr.numId.val

@num_id.setter
def num_id(self, num_id):
"""
Set the numId of the parent list.
"""
self._p.get_or_add_pPr().get_or_add_numPr().get_or_add_numId().val = num_id

@property
def level(self):
"""
Return the indentation level of the parent list.
"""
if self._p.pPr is None:
return None
if self._p.pPr.numPr is None:
return None
if self._p.pPr.numPr.ilvl is None:
return None
return self._p.pPr.numPr.ilvl.val

@level.setter
def level(self, lvl):
"""
Set the indentation level of the parent list.
"""
self._p.get_or_add_pPr().get_or_add_numPr().get_or_add_ilvl().val = lvl

@property
def ind_left(self):
"""
Return the indentation level of the parent list.
"""
if self._p.pPr is None:
return None
if self._p.pPr.ind is None:
return None
return self._p.pPr.ind_left

@level.setter
def ind_left(self, left):
"""
Set the indentation level of the parent list.
"""
self._p.get_or_add_pPr().ind_left = left

def _insert_paragraph_before(self):
"""
Return a newly created paragraph, inserted directly before this
paragraph.
"""
p = self._p.add_p_before()
return Paragraph(p, self._parent)

def add_hyperlink(self, text=None, url=None):
"""
Append a run to this paragraph containing *text* and having character
style identified by style ID *style*. *text* can contain tab
(``\\t``) characters, which are converted to the appropriate XML form
for a tab. *text* can also include newline (``\\n``) or carriage
return (``\\r``) characters, each of which is converted to a line
break.
"""
h = self._p.add_hyperlink()
# hyperlink = Hyperlink(h, self)
r = h.add_r()

run = Run(r, self)

if text:
# hyperlink.text = text
run.text = text

hyperlink = Hyperlink(h, self)
if url:
hyperlink.url = url
return hyperlink



class Hyperlink(Parented):
"""
Proxy object wrapping ``<w:hyperlink>`` element, which in turn contains a
``<w:r>`` element. It has two main properties: The *url* it points to and
the *text* that is shown on the page.
"""
def __init__(self, hyperlink, parent):
super(Hyperlink, self).__init__(parent)
self._hyperlink = hyperlink

@property
def url(self):
"""
Read/write. The relationship ID the Hyperlink points to, or |None| if
it has no directly-applied relationship. Setting this property sets
the The ``r:id`` attribute of the ``<w:rPr>`` element inside the
hyperlink.
"""
part = self.part
rId = self._hyperlink.relationship
url = part.target_ref(rId) if rId else ''
return url

@url.setter
def url(self, url):
part = self.part
rId = part.relate_to(url, RT.HYPERLINK, is_external=True)
self._hyperlink.relationship = rId

@property
def text(self):
"""
Read/write. The String content of the hyperlink that is visible on the
page.
"""
text = ''
for run in self.runs:
text += run.text
return text

@text.setter
def text(self, text):
self.clear()
self.add_run(text)

@property
def runs(self):

return [Run(r, self) for r in self._hyperlink.r_lst]

def add_run(self, text=None, style=None):
"""
Append a run to this hyperlink containing *text* and having character
style identified by style ID *style*. *text* can contain tab
(``\\t``) characters, which are converted to the appropriate XML form
for a tab. *text* can also include newline (``\\n``) or carriage
return (``\\r``) characters, each of which is converted to a line
break.
"""
r = self._hyperlink.add_r()
run = Run(r, self)
if text:
run.text = text
if style:
run.style = style
return run