Skip to content

Commit 90237e8

Browse files
committed
add feature: Paragraph.add_hyperlink() python-openxml#74
1 parent bc34ec9 commit 90237e8

8 files changed

Lines changed: 333 additions & 0 deletions

File tree

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@
151151
152152
.. |Run| replace:: :class:`.Run`
153153
154+
.. |Hyperlink| replace:: :class:`Hyperlink`
155+
154156
.. |Section| replace:: :class:`.Section`
155157
156158
.. |Sections| replace:: :class:`.Sections`
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
2+
Hyperlink
3+
=========
4+
5+
Word allows hyperlinks to be placed in the document or existing objects to be
6+
turned into hyperlinks.
7+
8+
Hyperlinks can point to a named object or range within the current document or
9+
to an external resource. Hyperlinks can contain multiple runs of text.
10+
11+
12+
Candidate protocol
13+
------------------
14+
15+
Add a simple hyperlink with text and url:
16+
17+
>>> hyperlink = paragraph.add_hyperlink(text='Google', url='http://google.com')
18+
>>> hyperlink.text
19+
'Google'
20+
>>> hyperlink.url
21+
'http://google.com'
22+
>>> hyperlink.anchor
23+
None
24+
>>> len(hyperlink.runs)
25+
1
26+
27+
Add multiple runs to a hyperlink:
28+
29+
>>> hyperlink = paragraph.add_hyperlink(url='http://github.com')
30+
>>> hyperlink.add_run('A')
31+
>>> hyperlink.add_run('formatted').italic = True
32+
>>> hyperlink.add_run('link').bold = True
33+
>>> len(hyperlink.runs)
34+
3
35+
36+
Add a hyperlink pointing to a named range in the current document:
37+
38+
>>> hyperlink = paragraph.add_hyperlink(text='Section 1', anchor='section1')
39+
>>> hyperlink.anchor
40+
'section1'
41+
>>> hyperlink.url
42+
None
43+
44+
Turning an existing object into a hyperlink:
45+
46+
>>> existing_object = document.add_paragraph('Some text')
47+
>>> hyperlink = existing_object.hyperlink(url='http://google.com')
48+
>>> hyperlink.text
49+
'Some text'
50+
>>> len(hyperlink.runs)
51+
1
52+
53+
54+
Specimen XML
55+
------------
56+
57+
.. highlight:: xml
58+
59+
A simple hyperlink to an external url::
60+
61+
<w:hyperlink r:id="rId9">
62+
<w:r>
63+
<w:rPr>
64+
<w:rStyle w:val="Hyperlink"/>
65+
</w:rPr>
66+
<w:t>Google</w:t>
67+
</w:r>
68+
</w:hyperlink>
69+
70+
71+
The relationship for the above url::
72+
73+
<Relationships xmlns="…">
74+
<Relationship Id="rId9" Mode="External" Target=http://google.com />
75+
</Relationships>
76+
77+
A hyperlink to an internal named range::
78+
79+
<w:hyperlink r:anchor="section1">
80+
<w:r>
81+
<w:rPr>
82+
<w:rStyle w:val="Hyperlink"/>
83+
</w:rPr>
84+
<w:t>Google</w:t>
85+
</w:r>
86+
</w:hyperlink>
87+
88+
A hyperlink with multiple runs of text::
89+
90+
<w:hyperlink r:id="rId2">
91+
<w:r>
92+
<w:rPr>
93+
<w:rStyle w:val="Hyperlink"/>
94+
</w:rPr>
95+
<w:t>A</w:t>
96+
</w:r>
97+
<w:r>
98+
<w:rPr>
99+
<w:rStyle w:val="Hyperlink"/>
100+
<w:i/>
101+
</w:rPr>
102+
<w:t xml:space="preserve">formatted</w:t>
103+
</w:r>
104+
<w:r>
105+
<w:rPr>
106+
<w:rStyle w:val="Hyperlink"/>
107+
<w:b/>
108+
</w:rPr>
109+
<w:t xml:space="preserve">link</w:t>
110+
</w:r>
111+
</w:hyperlink>
112+
113+
114+
Resources
115+
---------
116+
117+
* `Document Members (Word) on MSDN`_
118+
* `Hyperlink Members (Word) on MSDN`_
119+
* `Hyperlinks Members (Word) on MSDN`_
120+
* `Hyperlink Class (OpenXML.Office2010.CustomUI) on MSDN`_
121+
* `Hyperlink Class (OpenXML.Wordprocessing) on MSDN`_
122+
123+
124+
.. _Document Members (Word) on MSDN:
125+
http://msdn.microsoft.com/en-us/library/office/ff840898.aspx
126+
127+
.. _Hyperlink Members (Word) on MSDN:
128+
http://msdn.microsoft.com/en-us/library/office/ff195109.aspx
129+
130+
.. _Hyperlinks Members (Word) on MSDN:
131+
http://msdn.microsoft.com/en-us/library/office/ff192421.aspx
132+
133+
.. _Hyperlink Class (OpenXML.Office2010.CustomUI) on MSDN:
134+
http://msdn.microsoft.com/en-us/library/documentformat.openxml.office2010.customui.hyperlink.aspx
135+
136+
.. _Hyperlink Class (OpenXML.Wordprocessing) on MSDN:
137+
http://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.hyperlink.aspx
138+
139+
140+
MS API
141+
------
142+
143+
The Hyperlinks property on Document holds references to hyperlink
144+
objects in the MS API.
145+
146+
Hyperlinks contain the following properties:
147+
148+
* Address
149+
* SubAddress
150+
* EmailSubject
151+
* ExtraInfoRequired
152+
* Range (In python-docx this would be the runs inside the hyperlink)
153+
* ScreenTip
154+
* Shape
155+
* Target (where to open the hyperlink. e.g. "_blank", "_left", "_top", "_self", "_parent" etc)
156+
* TextToDisplay
157+
* Type (msoHyperlinkRange, msoHyperlinkShape or msoHyperlinkInlineShape)
158+
159+
160+
Spec references
161+
---------------
162+
163+
* 17.16.17 hyperlink (Hyperlink)
164+
* 2.3.61 CT_Hyperlink

docs/dev/analysis/features/text/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ Text
1010
font-color
1111
underline
1212
run-content
13+
hyperlink
1314
breaks

docx/oxml/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
180180
from .text.paragraph import CT_P
181181
register_element_cls('w:p', CT_P)
182182

183+
from .text.hyperlink import CT_Hyperlink
184+
register_element_cls('w:hyperlink', CT_Hyperlink)
185+
183186
from .text.parfmt import CT_Ind, CT_Jc, CT_PPr, CT_Spacing
184187
register_element_cls('w:ind', CT_Ind)
185188
register_element_cls('w:jc', CT_Jc)

docx/oxml/text/hyperlink.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Custom element classes related to hyperlinks (CT_Hyperlink).
5+
"""
6+
7+
from ..ns import qn
8+
from ..simpletypes import ST_RelationshipId
9+
from ..xmlchemy import (
10+
BaseOxmlElement, RequiredAttribute, ZeroOrMore
11+
)
12+
13+
14+
class CT_Hyperlink(BaseOxmlElement):
15+
"""
16+
``<w:hyperlink>`` element, containing the properties and text for a hyperlink.
17+
18+
The ``<w:hyperlink>`` contains a ``<w:r>`` element which holds all the
19+
visible content. The ``<w:hyperlink>`` has an attribute ``r:id`` which
20+
holds an ID relating a URL in the document's relationships.
21+
"""
22+
r = ZeroOrMore('w:r')
23+
rid = RequiredAttribute('r:id', ST_RelationshipId)
24+
25+
@property
26+
def relationship(self):
27+
"""
28+
String contained in ``r:id`` attribute of <w:hyperlink>. It should
29+
point to a URL in the document's relationships.
30+
"""
31+
val = self.get(qn('r:id'))
32+
return val
33+
34+
@relationship.setter
35+
def relationship(self, rId):
36+
self.set(qn('r:id'), rId)
37+
self.set(qn('w:history'), '1')
38+
39+
def clear_content(self):
40+
"""
41+
Remove all child r elements
42+
"""
43+
r_to_rm = []
44+
for child in self[:]:
45+
if child.tag == qn('w:r'):
46+
r_to_rm.append(child)
47+
for r in r_to_rm:
48+
self.remove(r)

docx/oxml/text/paragraph.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class CT_P(BaseOxmlElement):
1414
"""
1515
pPr = ZeroOrOne('w:pPr')
1616
r = ZeroOrMore('w:r')
17+
hyperlink = ZeroOrMore('w:hyperlink')
1718

1819
def _insert_pPr(self, pPr):
1920
self.insert(0, pPr)

docx/text/hyperlink.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Hyperlink proxy objects.
5+
"""
6+
7+
from __future__ import (
8+
absolute_import, division, print_function, unicode_literals
9+
)
10+
from .run import Run
11+
from ..shared import Parented
12+
from docx.opc.constants import RELATIONSHIP_TYPE as RT
13+
14+
15+
class Hyperlink(Parented):
16+
"""
17+
Proxy object wrapping ``<w:hyperlink>`` element, which in turn contains a
18+
``<w:r>`` element. It has two main properties: The *url* it points to and
19+
the *text* that is shown on the page.
20+
"""
21+
def __init__(self, hyperlink, parent):
22+
super(Hyperlink, self).__init__(parent)
23+
self._hyperlink = self.element = hyperlink
24+
25+
@property
26+
def url(self):
27+
"""
28+
Read/write. The relationship ID the Hyperlink points to, or |None| if
29+
it has no directly-applied relationship. Setting this property sets
30+
the The ``r:id`` attribute of the ``<w:rPr>`` element inside the
31+
hyperlink.
32+
"""
33+
part = self.part
34+
rId = self._hyperlink.relationship
35+
url = part.target_ref(rId) if rId else ''
36+
return url
37+
38+
@url.setter
39+
def url(self, url):
40+
part = self.part
41+
rId = part.relate_to(url, RT.HYPERLINK, is_external=True)
42+
self._hyperlink.relationship = rId
43+
44+
@property
45+
def runs(self):
46+
"""
47+
Sequence of |Run| instances corresponding to the <w:r> elements in
48+
this hyperlink.
49+
"""
50+
return [Run(r, self) for r in self._hyperlink.r_lst]
51+
52+
def add_run(self, text=None, style=None):
53+
"""
54+
Append a run to this hyperlink containing *text* and having character
55+
style identified by style ID *style*. *text* can contain tab
56+
(``\\t``) characters, which are converted to the appropriate XML form
57+
for a tab. *text* can also include newline (``\\n``) or carriage
58+
return (``\\r``) characters, each of which is converted to a line
59+
break.
60+
"""
61+
r = self._hyperlink.add_r()
62+
run = Run(r, self)
63+
if text:
64+
run.text = text
65+
if style:
66+
run.style = style
67+
return run
68+
69+
@property
70+
def text(self):
71+
text = ''
72+
for run in self.runs:
73+
text += run.text
74+
return text
75+
76+
@text.setter
77+
def text(self, text):
78+
self._hyperlink.clear_content()
79+
self.add_run(text)
80+
81+
82+
class Text(object):
83+
"""
84+
Proxy object wrapping ``<w:t>`` element.
85+
"""
86+
def __init__(self, t_elm):
87+
super(Text, self).__init__()
88+
self._t = t_elm

docx/text/paragraph.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ..enum.style import WD_STYLE_TYPE
1212
from .parfmt import ParagraphFormat
1313
from .run import Run
14+
from .hyperlink import Hyperlink
1415
from ..shared import Parented
1516

1617

@@ -39,6 +40,31 @@ def add_run(self, text=None, style=None):
3940
run.style = style
4041
return run
4142

43+
def add_hyperlink(self, text=None, url=None, style=None):
44+
"""
45+
Append a run to this paragraph containing *text* and having character
46+
style identified by style ID *style*. *text* can contain tab
47+
(``\\t``) characters, which are converted to the appropriate XML form
48+
for a tab. *text* can also include newline (``\\n``) or carriage
49+
return (``\\r``) characters, each of which is converted to a line
50+
break.
51+
"""
52+
53+
h = self._p.add_hyperlink()
54+
hyperlink = Hyperlink(h, self)
55+
56+
r = h.add_r()
57+
run = Run(r, hyperlink)
58+
59+
if text:
60+
run.text = text
61+
if style:
62+
run.style = style
63+
64+
if url:
65+
hyperlink.url = url
66+
return hyperlink
67+
4268
@property
4369
def alignment(self):
4470
"""

0 commit comments

Comments
 (0)