Skip to content

Commit

Permalink
Read complex field checkboxes, always unchecked
Browse files Browse the repository at this point in the history
  • Loading branch information
mwilliamson committed Nov 30, 2024
1 parent a2d7d04 commit 57a988d
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,25 @@ class StatefulBodyXmlReader {
private interface ComplexField {
ComplexField UNKNOWN = new ComplexField() {};

static ComplexField begin(XmlElement fldChar) {
return new BeginComplexField(fldChar);
}

static ComplexField hyperlink(Function<List<DocumentElement>, Hyperlink> childrenToHyperlink) {
return new HyperlinkComplexField(childrenToHyperlink);
}

static ComplexField checkbox(boolean checked) {
return new CheckboxComplexField(checked);
}
}

private static class BeginComplexField implements ComplexField {
private final XmlElement fldChar;

private BeginComplexField(XmlElement fldChar) {
this.fldChar = fldChar;
}
}

private static class HyperlinkComplexField implements ComplexField {
Expand All @@ -56,6 +72,14 @@ private HyperlinkComplexField(Function<List<DocumentElement>, Hyperlink> childre
}
}

private static class CheckboxComplexField implements ComplexField {
private final boolean checked;

private CheckboxComplexField(boolean checked) {
this.checked = checked;
}
}

StatefulBodyXmlReader(
Styles styles,
Numbering numbering,
Expand Down Expand Up @@ -291,42 +315,56 @@ private ReadResult readParagraph(XmlElement element) {
private ReadResult readFieldChar(XmlElement element) {
String type = element.getAttributeOrNone("w:fldCharType").orElse("");
if (type.equals("begin")) {
complexFieldStack.add(ComplexField.UNKNOWN);
complexFieldStack.add(ComplexField.begin(element));
currentInstrText.setLength(0);
} else if (type.equals("end")) {
complexFieldStack.remove();
ComplexField complexField = complexFieldStack.remove();
if (complexField instanceof BeginComplexField) {
complexField = parseCurrentInstrText(complexField);
}
if (complexField instanceof CheckboxComplexField) {
return success(new Checkbox(((CheckboxComplexField) complexField).checked));
}
} else if (type.equals("separate")) {
String instrText = currentInstrText.toString();
ComplexField complexField = parseHyperlinkFieldCode(instrText)
.map(href -> ComplexField.hyperlink(href))
.orElse(ComplexField.UNKNOWN);
complexFieldStack.remove();
ComplexField complexFieldSeparate = complexFieldStack.remove();
ComplexField complexField = parseCurrentInstrText(complexFieldSeparate);
complexFieldStack.add(complexField);
}
return ReadResult.EMPTY_SUCCESS;
}

private ReadResult readInstrText(XmlElement element) {
currentInstrText.append(element.innerText());
return ReadResult.EMPTY_SUCCESS;
private ComplexField parseCurrentInstrText(ComplexField complexField) {
String instrText = currentInstrText.toString();
return parseInstrText(instrText, complexField);
}

private Optional<Function<List<DocumentElement>, Hyperlink>> parseHyperlinkFieldCode(String instrText) {
private ComplexField parseInstrText(String instrText, ComplexField complexField) {
Pattern externalLinkPattern = Pattern.compile("\\s*HYPERLINK \"(.*)\"");
Matcher externalLinkMatcher = externalLinkPattern.matcher(instrText);
if (externalLinkMatcher.lookingAt()) {
String href = externalLinkMatcher.group(1);
return Optional.of(children -> Hyperlink.href(href, Optional.empty(), children));
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 Optional.of(children -> Hyperlink.anchor(anchor, Optional.empty(), children));
return ComplexField.hyperlink(children -> Hyperlink.anchor(anchor, Optional.empty(), children));
}

return Optional.empty();
Pattern checkboxPattern = Pattern.compile("\\s*FORMCHECKBOX\\s*");
Matcher checkboxMatcher = checkboxPattern.matcher(instrText);
if (checkboxMatcher.lookingAt()) {
return ComplexField.checkbox(false);
}

return ComplexField.UNKNOWN;
}

private ReadResult readInstrText(XmlElement element) {
currentInstrText.append(element.innerText());
return ReadResult.EMPTY_SUCCESS;
}

private InternalResult<Optional<Style>> readParagraphStyle(XmlElementLike properties) {
Expand Down
55 changes: 55 additions & 0 deletions src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,61 @@ public void fieldWithoutSeparateFldCharIsIgnored() {
}
}

@Nested
public class CheckboxTests {
@Test
public void complexFieldCheckboxWithoutSeparateIsRead() {
XmlElement element = element("w:p", list(
element("w:r", list(
element("w:fldChar", map("w:fldCharType", "begin"))
)),
element("w:instrText", list(
textXml(" FORMCHECKBOX ")
)),
element("w:r", list(
element("w:fldChar", map("w:fldCharType", "end"))
))
));

DocumentElement paragraph = readSuccess(bodyReader(), element);

assertThat(paragraph, isParagraph(hasChildren(
isEmptyRun(),
isRun(hasChildren(
isCheckbox()
))
)));
}

@Test
public void complexFieldCheckboxWithSeparateIsRead() {
XmlElement element = element("w:p", list(
element("w:r", list(
element("w:fldChar", map("w:fldCharType", "begin"))
)),
element("w:instrText", list(
textXml(" FORMCHECKBOX ")
)),
element("w:r", list(
element("w:fldChar", map("w:fldCharType", "separate"))
)),
element("w:r", list(
element("w:fldChar", map("w:fldCharType", "end"))
))
));

DocumentElement paragraph = readSuccess(bodyReader(), element);

assertThat(paragraph, isParagraph(hasChildren(
isEmptyRun(),
isEmptyRun(),
isRun(hasChildren(
isCheckbox()
))
)));
}
}

@Test
public void runHasNoStyleIfItHasNoProperties() {
XmlElement element = runXml(list());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ static Matcher<DocumentElement> isHyperlink(Matcher<? super Hyperlink>... matche
return cast(Hyperlink.class, allOf(matchers));
}

@SafeVarargs
static Matcher<DocumentElement> isCheckbox(Matcher<? super Checkbox>... matchers) {
return cast(Checkbox.class, allOf(matchers));
}

static Matcher<Hyperlink> hasHref(String href) {
return hasProperty("href", equalTo(Optional.of(href)));
}
Expand Down

0 comments on commit 57a988d

Please sign in to comment.