Skip to content
Merged
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
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
Release History
---------------

2.3.0 (2025-06-15)
++++++++++++++++++

* Implement support to add bookmarks in a paragraph
* Implement support for creating internal hyperlinks to bookmarks

2.2.2 (2025-06-15)
++++++++++++++++++

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Key differences at a glance:
- Supporting the ability to transform word documents into PDF's ([1](https://skelmis-docx.readthedocs.io/en/latest/api/utility.html#skelmis.docx.utility.document_to_pdf))
- Horizontal rules + paragraph bounding boxes / borders ([1](https://skelmis-docx.readthedocs.io/en/latest/api/text.html#skelmis.docx.text.paragraph.Paragraph.insert_horizontal_rule), [2](https://skelmis-docx.readthedocs.io/en/latest/api/text.html#skelmis.docx.text.paragraph.Paragraph.draw_paragraph_border))
- External hyperlinks ([1](https://skelmis-docx.readthedocs.io/en/latest/api/text.html#skelmis.docx.text.paragraph.Paragraph.add_external_hyperlink))
- Internal hyperlinks (Linking to bookmarks) ([1](https://skelmis-docx.readthedocs.io/en/latest/api/text.html#skelmis.docx.text.paragraph.Paragraph.add_internal_hyperlink))
- Creating bookmarks ([1](https://skelmis-docx.readthedocs.io/en/latest/api/text.html#skelmis.docx.text.paragraph.Paragraph.add_bookmark))
- The ability to insert a customisable Table of Contents (ToC) ([1](https://skelmis-docx.readthedocs.io/en/latest/api/text.html#skelmis.docx.text.paragraph.Paragraph.insert_table_of_contents))

## Installation
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "skelmis-docx"
version = "2.2.2"
version = "2.3.0"
description = "Create, read, and update Microsoft Word .docx files."
authors = [{ name = "Skelmis", email = "skelmis.craft@gmail.com" }]
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/skelmis/docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
if TYPE_CHECKING:
from skelmis.docx.opc.part import Part

__version__ = "2.2.2"
__version__ = "2.3.0"


__all__ = ["Document"]
Expand Down
70 changes: 70 additions & 0 deletions src/skelmis/docx/text/paragraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Iterator, List, cast

from skelmis.docx.enum.style import WD_STYLE_TYPE
Expand Down Expand Up @@ -32,6 +33,74 @@ def __init__(self, p: CT_P, parent: t.ProvidesStoryPart):
super(Paragraph, self).__init__(parent)
self._p = self._element = p

# noinspection PyTypeChecker
def add_internal_hyperlink(
self,
bookmark_name: str,
display_text: str,
) -> Hyperlink:
"""Add an internal hyperlink to a bookmark within the document.

:param bookmark_name: The name of the bookmark as provided to ``add_bookmark``.
:param display_text: The display text to put in the document and associate with this link.
"""
hyperlink = OxmlElement("w:hyperlink")

hyperlink.set(
qn("w:anchor"),
bookmark_name,
)

new_run = OxmlElement("w:r")
new_run.append(OxmlElement("w:rPr"))
new_run.text = display_text
hyperlink.append(new_run)
self._p.append(hyperlink)
return Hyperlink(hyperlink, self)

def add_bookmark(self, name: str, display_text: str | None = None, *, bookmark_id=None):
"""Add a bookmark to the document for later linking to.

Note that this method does not support bookmarking against various cells in a table.
Currently only paragraph based textual bookmarks are supported.

:param name: The name of the bookmark. Unless ``bookmark_id`` is also set
this name should be unique across the document.
:param display_text: The text to display; and associate with; alongside the bookmark.
:param bookmark_id: If you don't want the internal bookmark ID to be
set to the ``name`` parameter, set this.
"""
if bookmark_id is None:
# ID should be unique,
# so we make an assumption name also is and call it a day
bookmark_id = re.sub(r"\s+", "_", name)

self._start_bookmark(name, bookmark_id)

if display_text is not None:
text = OxmlElement("w:r")
text.text = display_text
# noinspection PyTypeChecker
self._p.append(text)

self._end_bookmark(bookmark_id)

def _start_bookmark(self, name: str, bookmark_id):
"""Add's a 'bookmarkStart' entry."""
# http://officeopenxml.com/WPbookmark.php
start = OxmlElement("w:bookmarkStart")
start.set(qn("w:id"), bookmark_id)
start.set(qn("w:name"), name)
# noinspection PyTypeChecker
self._p.append(start)

def _end_bookmark(self, bookmark_id):
"""Add's a 'bookmarkEnd' entry."""
end = OxmlElement("w:bookmarkEnd")
end.set(qn("w:id"), bookmark_id)
# noinspection PyTypeChecker
self._p.append(end)

def insert_table_of_contents(
self,
*,
Expand Down Expand Up @@ -197,6 +266,7 @@ def insert_table_of_contents(
# noinspection PyProtectedMember
run._r.append(item)

# noinspection PyTypeChecker
def add_external_hyperlink(
self,
url: str,
Expand Down