diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-03-12 21:24:22 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-03-12 21:24:22 +0000 |
| commit | 271b5f05d16576d479530663657c31ef37f1391b (patch) | |
| tree | 55fe0e95f5ece9c79447b62b705caa237390458d | |
| parent | 076de3f0567b44e349f7f7af99e5c4ad215646a2 (diff) | |
Handle hyperlink complex fields with unquoted hrefs
| -rw-r--r-- | NEWS | 2 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java | 21 | ||||
| -rw-r--r-- | src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java | 68 |
3 files changed, 71 insertions, 20 deletions
@@ -2,6 +2,8 @@ * Handle hyperlinked wp:anchor and wp:inline elements. +* Handle hyperlink complex fields with unquoted hrefs. + # 1.11.0 * Ignore style definitions using a style ID that has already been used. diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java b/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java index 9983d09..dab5fa6 100644 --- a/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java +++ b/src/main/java/org/zwobble/mammoth/internal/docx/StatefulBodyXmlReader.java @@ -346,18 +346,15 @@ class StatefulBodyXmlReader { } private ComplexField parseInstrText(String instrText, XmlElementLike fldChar) { - Pattern externalLinkPattern = Pattern.compile("\\s*HYPERLINK \"(.*)\""); - Matcher externalLinkMatcher = externalLinkPattern.matcher(instrText); - if (externalLinkMatcher.lookingAt()) { - String href = externalLinkMatcher.group(1); - return ComplexField.hyperlink(children -> Hyperlink.href(href, Optional.empty(), children)); - } - - Pattern internalLinkPattern = Pattern.compile("\\s*HYPERLINK\\s+\\\\l\\s+\"(.*)\""); - Matcher internalLinkMatcher = internalLinkPattern.matcher(instrText); - if (internalLinkMatcher.lookingAt()) { - String anchor = internalLinkMatcher.group(1); - return ComplexField.hyperlink(children -> Hyperlink.anchor(anchor, Optional.empty(), children)); + Pattern linkPattern = Pattern.compile("^\\s*HYPERLINK\\s+(\\\\l\\s+)?(?:\"(.*)\"|([^\\\\]\\S*))"); + Matcher linkMatcher = linkPattern.matcher(instrText); + if (linkMatcher.lookingAt()) { + String location = linkMatcher.group(2) == null ? linkMatcher.group(3) : linkMatcher.group(2); + if (linkMatcher.group(1) == null) { + return ComplexField.hyperlink(children -> Hyperlink.href(location, Optional.empty(), children)); + } else { + return ComplexField.hyperlink(children -> Hyperlink.anchor(location, Optional.empty(), children)); + } } Pattern checkboxPattern = Pattern.compile("\\s*FORMCHECKBOX\\s*"); diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java index 71d8627..135ad54 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java @@ -374,13 +374,14 @@ public class BodyXmlTests { } @Test - public void runsInAComplexFieldForHyperlinksWithoutSwitchAreReadAsExternalHyperlinks() { - XmlElement hyperlinkRunXml = runXml("this is a hyperlink"); + public void runsInAComplexFieldForHyperlinksWithoutSwitchWithQuotedLocationAreReadAsExternalHyperlinks() { XmlElement element = paragraphXml(list( BEGIN_COMPLEX_FIELD, - HYPERLINK_INSTRTEXT, + element("w:instrText", list( + XmlNodes.text(" HYPERLINK \"http://example.com\"") + )), SEPARATE_COMPLEX_FIELD, - hyperlinkRunXml, + runXml("this is a hyperlink"), END_COMPLEX_FIELD )); DocumentElement paragraph = readSuccess(bodyReader(), element); @@ -389,7 +390,7 @@ public class BodyXmlTests { isEmptyRun(), isEmptyHyperlinkedRun(), isHyperlinkedRun( - hasHref(URI), + hasHref("http://example.com"), hasChildren( isTextElement("this is a hyperlink") ) @@ -399,15 +400,66 @@ public class BodyXmlTests { } @Test - public void runsInAComplexFieldForHyperlinksWithLSwitchAreReadAsInternalHyperlinks() { - XmlElement hyperlinkRunXml = runXml("this is a hyperlink"); + public void runsInAComplexFieldForHyperlinksWithoutSwitchWithUnquotedLocationAreReadAsExternalHyperlinks() { + XmlElement element = paragraphXml(list( + BEGIN_COMPLEX_FIELD, + element("w:instrText", list( + XmlNodes.text(" HYPERLINK http://example.com") + )), + SEPARATE_COMPLEX_FIELD, + runXml("this is a hyperlink"), + END_COMPLEX_FIELD + )); + DocumentElement paragraph = readSuccess(bodyReader(), element); + + assertThat(paragraph, isParagraph(hasChildren( + isEmptyRun(), + isEmptyHyperlinkedRun(), + isHyperlinkedRun( + hasHref("http://example.com"), + hasChildren( + isTextElement("this is a hyperlink") + ) + ), + isEmptyRun() + ))); + } + + @Test + public void runsInAComplexFieldForHyperlinksWithLSwitchWithQuotedLocationAreReadAsInternalHyperlinks() { XmlElement element = paragraphXml(list( BEGIN_COMPLEX_FIELD, element("w:instrText", list( XmlNodes.text(" HYPERLINK \\l \"InternalLink\"") )), SEPARATE_COMPLEX_FIELD, - hyperlinkRunXml, + runXml("this is a hyperlink"), + END_COMPLEX_FIELD + )); + DocumentElement paragraph = readSuccess(bodyReader(), element); + + assertThat(paragraph, isParagraph(hasChildren( + isEmptyRun(), + isEmptyHyperlinkedRun(), + isHyperlinkedRun( + hasAnchor("InternalLink"), + hasChildren( + isTextElement("this is a hyperlink") + ) + ), + isEmptyRun() + ))); + } + + @Test + public void runsInAComplexFieldForHyperlinksWithLSwitchWithUnquotedLocationAreReadAsInternalHyperlinks() { + XmlElement element = paragraphXml(list( + BEGIN_COMPLEX_FIELD, + element("w:instrText", list( + XmlNodes.text(" HYPERLINK \\l InternalLink") + )), + SEPARATE_COMPLEX_FIELD, + runXml("this is a hyperlink"), END_COMPLEX_FIELD )); DocumentElement paragraph = readSuccess(bodyReader(), element); |
