feat(docx): export a link as a link, and an anchor as a bookmark - #707
Merged
Merged
Conversation
Every link the document carried was dropped. A reader opened an export and found the text of a link with nothing behind it, and a reference to another section that went nowhere -- which is most of the point of handing a document over editable rather than as a PDF. A linkTarget becomes a w:hyperlink: a relationship carrying the address for an external one, w:anchor for one of the document's own anchors. A run's own link wins over the paragraph's, because a sentence with one linked phrase in it is the ordinary case and the paragraph's link is the fallback for the rest of that sentence. An anchor becomes a w:bookmarkStart / w:bookmarkEnd pair wrapping the paragraph's text -- opened and closed in two calls, since both elements append to the end of the paragraph and doing it in one leaves a bookmark around nothing and a reader landing above the text rather than on it. Word takes far less of a name than a document gives: letters, digits and underscores, starting with a letter, forty characters. A name it dislikes is not a broken link but a file it refuses to open. Names are therefore cleaned in one place and remembered -- a link and the bookmark it points at are written at different times by different parts of the export and have to agree -- and two anchors that clean to one name stay two bookmarks rather than collapsing onto each other. Forward references need nothing extra: neither side resolves anything at write time. The outline is deliberately not written. bookmarkOptions builds a PDF outline tree; Word's Navigation Pane comes from heading styles, and promoting an anchored paragraph to a heading would restyle the document. Measured by opening the export in Word 16.0: its own PDF carries two live links, one to the address and one jumping within the document -- behaviour, not the presence of the XML. Tests: eight over the exported package -- a paragraph link and its relationship, one linked phrase among plain runs, an internal link matching the bookmark its anchor became, a forward reference, the bookmark wrapping the text, two anchors cleaning to one name staying apart, an anchor starting with a digit, and a plain paragraph carrying neither. Verification: ./mvnw -B -ntp clean verify over the seven-module gate, 1784 tests, BUILD SUCCESS; render-docx 182 to 190; the examples module separately, 93 tests, BUILD SUCCESS.
| if (name != null) { | ||
| CTHyperlink link = para.getCTP().addNewHyperlink(); | ||
| link.setAnchor(name); | ||
| return new XWPFRun(link.addNewR(), para); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Every link the document carried was dropped. A reader opened an export and found the text
of a link with nothing behind it, and a reference to another section that went nowhere. For
a document handed over to be edited rather than read, that is most of the reason for handing
it over at all — a PDF at least keeps its links.
What changed
A
linkTargetbecomes aw:hyperlink— a relationship carrying the address for anexternal one,
w:anchorfor one of the document's own anchors. An external link goesthrough POI's
createHyperlinkRun, which makes the relationship the part needs; POI has nohelper for an anchor, so that one is built here.
A run's own link wins over the paragraph's. A sentence with one linked phrase in it is
the ordinary case: the paragraph's link is the fallback for the rest of the sentence rather
than something the phrase overrides away.
An
anchor(...)becomes aw:bookmarkStart/w:bookmarkEndpair wrapping theparagraph's text. Opened and closed in two calls, because both elements append to the end
of the paragraph — doing it in one leaves a bookmark around nothing and a reader landing
above the text instead of on it.
Names are cleaned once and remembered (
DocxBookmarkNames). Word takes far less of aname than a document gives: letters, digits and underscores, starting with a letter, forty
characters. A name it dislikes is not a broken link but a file it refuses to open. A link
and the bookmark it points at are written at different times by different parts of the
export, so both go through one mapping —
Terms & conditionsbecomesTerms_conditionsonboth sides — and two anchors that clean to one name stay two bookmarks rather than
collapsing onto each other. Forward references need nothing extra: neither side resolves
anything at write time.
The outline is deliberately not written.
bookmarkOptionsbuilds a PDF outline tree;Word's Navigation Pane comes from heading styles, and promoting an anchored paragraph to a
heading would restyle the document. The capability matrix and the DOCX recipe say so rather
than leaving it to be discovered.
Verification
./mvnw -B -ntp clean verifyover the seven-module gate → BUILD SUCCESS, 1784 tests../mvnw -B -ntp test -f examples/pom.xml→ 93 tests, BUILD SUCCESS; no committed previewdrifted.
render-docx goes from 182 to 190 tests.
DocxLinkTestcovers a paragraph link and therelationship behind it, one linked phrase among plain runs, an internal link matching the
bookmark its anchor became, a forward reference, the bookmark wrapping the text rather than
sitting before it, two anchors that clean to one name staying apart, an anchor starting with
a digit, and a plain paragraph carrying neither.
Checked by opening the export in Word 16.0 and reading its own PDF back: two live links,
one to the address and one jumping within the document. Behaviour, not the presence of the
XML — the same standard the font work was held to.
Lane: canonical —
document.backend.semantic.docxonly; no engine, layout or paginationinternals are touched.