Skip to content

Commit

Permalink
add feature: Paragraph.add_hyperlink() python-openxml#74
Browse files Browse the repository at this point in the history
  • Loading branch information
tanyunshi committed Mar 17, 2015
1 parent bc34ec9 commit 90237e8
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@
.. |Run| replace:: :class:`.Run`
.. |Hyperlink| replace:: :class:`Hyperlink`
.. |Section| replace:: :class:`.Section`
.. |Sections| replace:: :class:`.Sections`
Expand Down
164 changes: 164 additions & 0 deletions docs/dev/analysis/features/text/hyperlink.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@

Hyperlink
=========

Word allows hyperlinks to be placed in the document or existing objects to be
turned into hyperlinks.

Hyperlinks can point to a named object or range within the current document or
to an external resource. Hyperlinks can contain multiple runs of text.


Candidate protocol
------------------

Add a simple hyperlink with text and url:

>>> hyperlink = paragraph.add_hyperlink(text='Google', url='http://google.com')
>>> hyperlink.text
'Google'
>>> hyperlink.url
'http://google.com'
>>> hyperlink.anchor
None
>>> len(hyperlink.runs)
1

Add multiple runs to a hyperlink:

>>> hyperlink = paragraph.add_hyperlink(url='http://github.com')
>>> hyperlink.add_run('A')
>>> hyperlink.add_run('formatted').italic = True
>>> hyperlink.add_run('link').bold = True
>>> len(hyperlink.runs)
3

Add a hyperlink pointing to a named range in the current document:

>>> hyperlink = paragraph.add_hyperlink(text='Section 1', anchor='section1')
>>> hyperlink.anchor
'section1'
>>> hyperlink.url
None

Turning an existing object into a hyperlink:

>>> existing_object = document.add_paragraph('Some text')
>>> hyperlink = existing_object.hyperlink(url='http://google.com')
>>> hyperlink.text
'Some text'
>>> len(hyperlink.runs)
1


Specimen XML
------------

.. highlight:: xml

A simple hyperlink to an external url::

<w:hyperlink r:id="rId9">
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
</w:rPr>
<w:t>Google</w:t>
</w:r>
</w:hyperlink>


The relationship for the above url::

<Relationships xmlns="…">
<Relationship Id="rId9" Mode="External" Target=http://google.com />
</Relationships>

A hyperlink to an internal named range::

<w:hyperlink r:anchor="section1">
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
</w:rPr>
<w:t>Google</w:t>
</w:r>
</w:hyperlink>

A hyperlink with multiple runs of text::

<w:hyperlink r:id="rId2">
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
</w:rPr>
<w:t>A</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
<w:i/>
</w:rPr>
<w:t xml:space="preserve">formatted</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="Hyperlink"/>
<w:b/>
</w:rPr>
<w:t xml:space="preserve">link</w:t>
</w:r>
</w:hyperlink>


Resources
---------

* `Document Members (Word) on MSDN`_
* `Hyperlink Members (Word) on MSDN`_
* `Hyperlinks Members (Word) on MSDN`_
* `Hyperlink Class (OpenXML.Office2010.CustomUI) on MSDN`_
* `Hyperlink Class (OpenXML.Wordprocessing) on MSDN`_


.. _Document Members (Word) on MSDN:
http://msdn.microsoft.com/en-us/library/office/ff840898.aspx

.. _Hyperlink Members (Word) on MSDN:
http://msdn.microsoft.com/en-us/library/office/ff195109.aspx

.. _Hyperlinks Members (Word) on MSDN:
http://msdn.microsoft.com/en-us/library/office/ff192421.aspx

.. _Hyperlink Class (OpenXML.Office2010.CustomUI) on MSDN:
http://msdn.microsoft.com/en-us/library/documentformat.openxml.office2010.customui.hyperlink.aspx

.. _Hyperlink Class (OpenXML.Wordprocessing) on MSDN:
http://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.hyperlink.aspx


MS API
------

The Hyperlinks property on Document holds references to hyperlink
objects in the MS API.

Hyperlinks contain the following properties:

* Address
* SubAddress
* EmailSubject
* ExtraInfoRequired
* Range (In python-docx this would be the runs inside the hyperlink)
* ScreenTip
* Shape
* Target (where to open the hyperlink. e.g. "_blank", "_left", "_top", "_self", "_parent" etc)
* TextToDisplay
* Type (msoHyperlinkRange, msoHyperlinkShape or msoHyperlinkInlineShape)


Spec references
---------------

* 17.16.17 hyperlink (Hyperlink)
* 2.3.61 CT_Hyperlink
1 change: 1 addition & 0 deletions docs/dev/analysis/features/text/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ Text
font-color
underline
run-content
hyperlink
breaks
3 changes: 3 additions & 0 deletions docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
from .text.paragraph import CT_P
register_element_cls('w:p', CT_P)

from .text.hyperlink import CT_Hyperlink
register_element_cls('w:hyperlink', CT_Hyperlink)

from .text.parfmt import CT_Ind, CT_Jc, CT_PPr, CT_Spacing
register_element_cls('w:ind', CT_Ind)
register_element_cls('w:jc', CT_Jc)
Expand Down
48 changes: 48 additions & 0 deletions docx/oxml/text/hyperlink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# encoding: utf-8

"""
Custom element classes related to hyperlinks (CT_Hyperlink).
"""

from ..ns import qn
from ..simpletypes import ST_RelationshipId
from ..xmlchemy import (
BaseOxmlElement, RequiredAttribute, ZeroOrMore
)


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')

def clear_content(self):
"""
Remove all child r elements
"""
r_to_rm = []
for child in self[:]:
if child.tag == qn('w:r'):
r_to_rm.append(child)
for r in r_to_rm:
self.remove(r)
1 change: 1 addition & 0 deletions docx/oxml/text/paragraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class CT_P(BaseOxmlElement):
"""
pPr = ZeroOrOne('w:pPr')
r = ZeroOrMore('w:r')
hyperlink = ZeroOrMore('w:hyperlink')

def _insert_pPr(self, pPr):
self.insert(0, pPr)
Expand Down
88 changes: 88 additions & 0 deletions docx/text/hyperlink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# encoding: utf-8

"""
Hyperlink proxy objects.
"""

from __future__ import (
absolute_import, division, print_function, unicode_literals
)
from .run import Run
from ..shared import Parented
from docx.opc.constants import RELATIONSHIP_TYPE as RT


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 = self.element = 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 runs(self):
"""
Sequence of |Run| instances corresponding to the <w:r> elements in
this hyperlink.
"""
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

@property
def text(self):
text = ''
for run in self.runs:
text += run.text
return text

@text.setter
def text(self, text):
self._hyperlink.clear_content()
self.add_run(text)


class Text(object):
"""
Proxy object wrapping ``<w:t>`` element.
"""
def __init__(self, t_elm):
super(Text, self).__init__()
self._t = t_elm
26 changes: 26 additions & 0 deletions docx/text/paragraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ..enum.style import WD_STYLE_TYPE
from .parfmt import ParagraphFormat
from .run import Run
from .hyperlink import Hyperlink
from ..shared import Parented


Expand Down Expand Up @@ -39,6 +40,31 @@ def add_run(self, text=None, style=None):
run.style = style
return run

def add_hyperlink(self, text=None, url=None, style=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, hyperlink)

if text:
run.text = text
if style:
run.style = style

if url:
hyperlink.url = url
return hyperlink

@property
def alignment(self):
"""
Expand Down

0 comments on commit 90237e8

Please sign in to comment.