package org.zwobble.mammoth.internal.docx; import org.zwobble.mammoth.internal.archives.Archive; import org.zwobble.mammoth.internal.archives.Archives; import org.zwobble.mammoth.internal.documents.*; import org.zwobble.mammoth.internal.results.InternalResult; import org.zwobble.mammoth.internal.util.Casts; import org.zwobble.mammoth.internal.util.InputStreamSupplier; import org.zwobble.mammoth.internal.util.Lists; import org.zwobble.mammoth.internal.util.Optionals; import org.zwobble.mammoth.internal.xml.XmlElement; import org.zwobble.mammoth.internal.xml.XmlElementLike; import org.zwobble.mammoth.internal.xml.XmlElementList; import org.zwobble.mammoth.internal.xml.XmlNode; import java.util.*; import java.util.function.Function; import static org.zwobble.mammoth.internal.docx.ReadResult.EMPTY_SUCCESS; import static org.zwobble.mammoth.internal.docx.ReadResult.success; import static org.zwobble.mammoth.internal.util.Iterables.lazyFilter; import static org.zwobble.mammoth.internal.util.Lists.list; import static org.zwobble.mammoth.internal.util.Maps.entry; import static org.zwobble.mammoth.internal.util.Maps.lookup; import static org.zwobble.mammoth.internal.util.Sets.set; import static org.zwobble.mammoth.internal.util.Strings.trimLeft; public class BodyXmlReader { private static final Set IMAGE_TYPES_SUPPORTED_BY_BROWSERS = set( "image/png", "image/gif", "image/jpeg", "image/svg+xml", "image/tiff"); private final Styles styles; private final Numbering numbering; private final Relationships relationships; private final ContentTypes contentTypes; private final Archive file; private final FileReader fileReader; public BodyXmlReader( Styles styles, Numbering numbering, Relationships relationships, ContentTypes contentTypes, Archive file, FileReader fileReader) { this.styles = styles; this.numbering = numbering; this.relationships = relationships; this.contentTypes = contentTypes; this.file = file; this.fileReader = fileReader; } public ReadResult readElement(XmlElement element) { switch (element.getName()) { case "w:t": return success(new Text(element.innerText())); case "w:r": return readRun(element); case "w:p": return readParagraph(element); case "w:tab": return success(Tab.TAB); case "w:br": return readBreak(element); case "w:tbl": return readTable(element); case "w:tr": return readElements(element.getChildren()).map(TableRow::new); case "w:tc": return readTableCell(element); case "w:hyperlink": return readHyperlink(element); case "w:bookmarkStart": return readBookmark(element); case "w:footnoteReference": return readNoteReference(NoteType.FOOTNOTE, element); case "w:endnoteReference": return readNoteReference(NoteType.ENDNOTE, element); case "w:commentReference": return readCommentReference(element); case "w:pict": return readPict(element); case "v:imagedata": return readImagedata(element); case "wp:inline": case "wp:anchor": return readInline(element); case "w:sdt": return readSdt(element); case "w:ins": case "w:smartTag": case "w:drawing": case "v:roundrect": case "v:shape": case "v:textbox": case "w:txbxContent": return readElements(element.getChildren()); case "office-word:wrap": case "v:shadow": case "v:shapetype": case "w:bookmarkEnd": case "w:sectPr": case "w:proofErr": case "w:lastRenderedPageBreak": case "w:commentRangeStart": case "w:commentRangeEnd": case "w:del": case "w:footnoteRef": case "w:endnoteRef": case "w:annotationRef": case "w:pPr": case "w:rPr": case "w:tblPr": case "w:tblGrid": case "w:tcPr": return EMPTY_SUCCESS; default: String warning = "An unrecognised element was ignored: " + element.getName(); return ReadResult.emptyWithWarning(warning); } } private ReadResult readRun(XmlElement element) { XmlElementLike properties = element.findChildOrEmpty("w:rPr"); return ReadResult.map( readRunStyle(properties), readElements(element.getChildren()), (style, children) -> new Run( isBold(properties), isItalic(properties), isUnderline(properties), isStrikethrough(properties), readVerticalAlignment(properties), style, children)); } private boolean isBold(XmlElementLike properties) { return readBooleanElement(properties, "w:b"); } private boolean isItalic(XmlElementLike properties) { return readBooleanElement(properties, "w:i"); } private boolean isUnderline(XmlElementLike properties) { return readBooleanElement(properties, "w:u"); } private boolean isStrikethrough(XmlElementLike properties) { return readBooleanElement(properties, "w:strike"); } private boolean readBooleanElement(XmlElementLike properties, String tagName) { return properties.findChild(tagName) .map(child -> child.getAttributeOrNone("w:val") .map(value -> !value.equals("false") && !value.equals("0")) .orElse(true)) .orElse(false); } private VerticalAlignment readVerticalAlignment(XmlElementLike properties) { String verticalAlignment = readVal(properties, "w:vertAlign").orElse(""); switch (verticalAlignment) { case "superscript": return VerticalAlignment.SUPERSCRIPT; case "subscript": return VerticalAlignment.SUBSCRIPT; default: // TODO: warn if set? return VerticalAlignment.BASELINE; } } private InternalResult> readRunStyle(XmlElementLike properties) { return readStyle(properties, "w:rStyle", "Run", styles::findCharacterStyleById); } public ReadResult readElements(Iterable nodes) { return ReadResult.flatMap(lazyFilter(nodes, XmlElement.class), this::readElement); } private ReadResult readParagraph(XmlElement element) { XmlElementLike properties = element.findChildOrEmpty("w:pPr"); Optional numbering = readNumbering(properties); return ReadResult.map( readParagraphStyle(properties), readElements(element.getChildren()), (style, children) -> new Paragraph(style, numbering, children)).appendExtra(); } private InternalResult> readParagraphStyle(XmlElementLike properties) { return readStyle(properties, "w:pStyle", "Paragraph", styles::findParagraphStyleById); } private InternalResult> readStyle( XmlElementLike properties, String styleTagName, String styleType, Function> findStyleById) { return readVal(properties, styleTagName) .map(styleId -> findStyleById(styleType, styleId, findStyleById)) .orElse(InternalResult.empty()); } private InternalResult> findStyleById( String styleType, String styleId, Function> findStyleById) { Optional