diff options
| author | Michael Williamson <mike@zwobble.org> | 2018-01-28 12:08:00 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2018-01-28 12:08:00 +0000 |
| commit | f9c7505139d9287e1a5747ebca754797520ff93f (patch) | |
| tree | e8b73540e11de6988af96a80cf9457759acf531b /src/main/java/org | |
| parent | def58df60701d5575d0fa8afddb6cd27c875fb30 (diff) | |
Adjust style for easier transformation
Diffstat (limited to 'src/main/java/org')
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java | 400 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java | 54 |
2 files changed, 236 insertions, 218 deletions
diff --git a/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java b/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java index 3f3576f..8117f58 100644 --- a/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java +++ b/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java @@ -67,7 +67,7 @@ public class DocumentToHtml { private final boolean isHeader; Context() { - this(false); + this.isHeader = false; } Context(boolean isHeader) { @@ -145,225 +145,227 @@ public class DocumentToHtml { return convertToHtml(element.getChildren(), context); } - private List<HtmlNode> convertToHtml(DocumentElement element, Context context) { - return element.accept(new DocumentElementVisitor<List<HtmlNode>, Context>() { - @Override - public List<HtmlNode> visit(Paragraph paragraph, Context context) { - Supplier<List<HtmlNode>> children = () -> { - List<HtmlNode> content = convertChildrenToHtml(paragraph, context); - return preserveEmptyParagraphs ? cons(Html.FORCE_WRITE, content) : content; - }; - HtmlPath mapping = styleMap.getParagraphHtmlPath(paragraph) - .orElseGet(() -> { - if (paragraph.getStyle().isPresent()) { - warnings.add("Unrecognised paragraph style: " + paragraph.getStyle().get().describe()); - } - return HtmlPath.element("p"); - }); - return mapping.wrap(children).get(); - } + private class ElementConverterVisitor implements DocumentElementVisitor<List<HtmlNode>, Context> { + @Override + public List<HtmlNode> visit(Paragraph paragraph, Context context) { + Supplier<List<HtmlNode>> children = () -> { + List<HtmlNode> content = convertChildrenToHtml(paragraph, context); + return preserveEmptyParagraphs ? cons(Html.FORCE_WRITE, content) : content; + }; + HtmlPath mapping = styleMap.getParagraphHtmlPath(paragraph) + .orElseGet(() -> { + if (paragraph.getStyle().isPresent()) { + warnings.add("Unrecognised paragraph style: " + paragraph.getStyle().get().describe()); + } + return HtmlPath.element("p"); + }); + return mapping.wrap(children).get(); + } - @Override - public List<HtmlNode> visit(Run run, Context context) { - Supplier<List<HtmlNode>> nodes = () -> convertChildrenToHtml(run, context); - if (run.isSmallCaps()) { - nodes = styleMap.getSmallCaps().orElse(HtmlPath.EMPTY).wrap(nodes); - } - if (run.isStrikethrough()) { - nodes = styleMap.getStrikethrough().orElse(HtmlPath.collapsibleElement("s")).wrap(nodes); - } - if (run.isUnderline()) { - nodes = styleMap.getUnderline().orElse(HtmlPath.EMPTY).wrap(nodes); - } - if (run.getVerticalAlignment() == VerticalAlignment.SUBSCRIPT) { - nodes = HtmlPath.collapsibleElement("sub").wrap(nodes); - } - if (run.getVerticalAlignment() == VerticalAlignment.SUPERSCRIPT) { - nodes = HtmlPath.collapsibleElement("sup").wrap(nodes); - } - if (run.isItalic()) { - nodes = styleMap.getItalic().orElse(HtmlPath.collapsibleElement("em")).wrap(nodes); - } - if (run.isBold()) { - nodes = styleMap.getBold().orElse(HtmlPath.collapsibleElement("strong")).wrap(nodes); - } - HtmlPath mapping = styleMap.getRunHtmlPath(run) - .orElseGet(() -> { - if (run.getStyle().isPresent()) { - warnings.add("Unrecognised run style: " + run.getStyle().get().describe()); - } - return HtmlPath.EMPTY; - }); - return mapping.wrap(nodes).get(); + @Override + public List<HtmlNode> visit(Run run, Context context) { + Supplier<List<HtmlNode>> nodes = () -> convertChildrenToHtml(run, context); + if (run.isSmallCaps()) { + nodes = styleMap.getSmallCaps().orElse(HtmlPath.EMPTY).wrap(nodes); } - - @Override - public List<HtmlNode> visit(Text text, Context context) { - if (text.getValue().isEmpty()) { - return list(); - } else { - return list(Html.text(text.getValue())); - } + if (run.isStrikethrough()) { + nodes = styleMap.getStrikethrough().orElse(HtmlPath.collapsibleElement("s")).wrap(nodes); } - - @Override - public List<HtmlNode> visit(Tab tab, Context context) { - return list(Html.text("\t")); + if (run.isUnderline()) { + nodes = styleMap.getUnderline().orElse(HtmlPath.EMPTY).wrap(nodes); } - - @Override - public List<HtmlNode> visit(Break breakElement, Context context) { - HtmlPath mapping = styleMap.getBreakHtmlPath(breakElement) - .orElseGet(() -> { - if (breakElement.getType() == Break.Type.LINE) { - return HtmlPath.element("br"); - } else { - return HtmlPath.EMPTY; - } - }); - return mapping.wrap(() -> list()).get(); + if (run.getVerticalAlignment() == VerticalAlignment.SUBSCRIPT) { + nodes = HtmlPath.collapsibleElement("sub").wrap(nodes); } - - @Override - public List<HtmlNode> visit(Table table, Context context) { - HtmlPath mapping = styleMap.getTableHtmlPath(table) - .orElse(HtmlPath.element("table")); - return mapping.wrap(() -> generateTableChildren(table, context)).get(); + if (run.getVerticalAlignment() == VerticalAlignment.SUPERSCRIPT) { + nodes = HtmlPath.collapsibleElement("sup").wrap(nodes); } - - private List<HtmlNode> generateTableChildren(Table table, Context context) { - int bodyIndex = findIndex(table.getChildren(), child -> !isHeader(child)) - .orElse(table.getChildren().size()); - if (bodyIndex == 0) { - return convertToHtml(table.getChildren(), context.isHeader(false)); - } else { - List<HtmlNode> headRows = convertToHtml( - table.getChildren().subList(0, bodyIndex), - context.isHeader(true) - ); - List<HtmlNode> bodyRows = convertToHtml( - table.getChildren().subList(bodyIndex, table.getChildren().size()), - context.isHeader(false) - ); - return list( - Html.element("thead", headRows), - Html.element("tbody", bodyRows) - ); - } + if (run.isItalic()) { + nodes = styleMap.getItalic().orElse(HtmlPath.collapsibleElement("em")).wrap(nodes); } - - private boolean isHeader(DocumentElement child) { - return tryCast(TableRow.class, child) - .map(TableRow::isHeader) - .orElse(false); + if (run.isBold()) { + nodes = styleMap.getBold().orElse(HtmlPath.collapsibleElement("strong")).wrap(nodes); } + HtmlPath mapping = styleMap.getRunHtmlPath(run) + .orElseGet(() -> { + if (run.getStyle().isPresent()) { + warnings.add("Unrecognised run style: " + run.getStyle().get().describe()); + } + return HtmlPath.EMPTY; + }); + return mapping.wrap(nodes).get(); + } - @Override - public List<HtmlNode> visit(TableRow tableRow, Context context) { - return list(Html.element("tr", convertChildrenToHtml(tableRow, context))); + @Override + public List<HtmlNode> visit(Text text, Context context) { + if (text.getValue().isEmpty()) { + return list(); + } else { + return list(Html.text(text.getValue())); } + } - @Override - public List<HtmlNode> visit(TableCell tableCell, Context context) { - String tagName = context.isHeader ? "th" : "td"; - Map<String, String> attributes = new HashMap<>(); - if (tableCell.getColspan() != 1) { - attributes.put("colspan", Integer.toString(tableCell.getColspan())); - } - if (tableCell.getRowspan() != 1) { - attributes.put("rowspan", Integer.toString(tableCell.getRowspan())); - } - return list(Html.element(tagName, attributes, - Lists.cons(Html.FORCE_WRITE, convertChildrenToHtml(tableCell, context)))); - } + @Override + public List<HtmlNode> visit(Tab tab, Context context) { + return list(Html.text("\t")); + } - @Override - public List<HtmlNode> visit(Hyperlink hyperlink, Context context) { - Map<String, String> attributes = mutableMap("href", generateHref(hyperlink)); - hyperlink.getTargetFrame().ifPresent(targetFrame -> - attributes.put("target", targetFrame) - ); + @Override + public List<HtmlNode> visit(Break breakElement, Context context) { + HtmlPath mapping = styleMap.getBreakHtmlPath(breakElement) + .orElseGet(() -> { + if (breakElement.getType() == Break.Type.LINE) { + return HtmlPath.element("br"); + } else { + return HtmlPath.EMPTY; + } + }); + return mapping.wrap(() -> list()).get(); + } - return list(Html.collapsibleElement("a", attributes, convertChildrenToHtml(hyperlink, context))); - } + @Override + public List<HtmlNode> visit(Table table, Context context) { + HtmlPath mapping = styleMap.getTableHtmlPath(table) + .orElse(HtmlPath.element("table")); + return mapping.wrap(() -> generateTableChildren(table, context)).get(); + } - private String generateHref(Hyperlink hyperlink) { - if (hyperlink.getHref().isPresent()) { - return hyperlink.getHref().get(); - } else if (hyperlink.getAnchor().isPresent()) { - return "#" + generateId(hyperlink.getAnchor().get()); - } else { - return ""; - } + private List<HtmlNode> generateTableChildren(Table table, Context context) { + int bodyIndex = findIndex(table.getChildren(), child -> !isHeader(child)) + .orElse(table.getChildren().size()); + if (bodyIndex == 0) { + return convertToHtml(table.getChildren(), context.isHeader(false)); + } else { + List<HtmlNode> headRows = convertToHtml( + table.getChildren().subList(0, bodyIndex), + context.isHeader(true) + ); + List<HtmlNode> bodyRows = convertToHtml( + table.getChildren().subList(bodyIndex, table.getChildren().size()), + context.isHeader(false) + ); + return list( + Html.element("thead", headRows), + Html.element("tbody", bodyRows) + ); } + } - @Override - public List<HtmlNode> visit(Bookmark bookmark, Context context) { - return list(Html.element("a", map("id", generateId(bookmark.getName())), list(Html.FORCE_WRITE))); - } + private boolean isHeader(DocumentElement child) { + return tryCast(TableRow.class, child) + .map(TableRow::isHeader) + .orElse(false); + } - @Override - public List<HtmlNode> visit(NoteReference noteReference, Context context) { - noteReferences.add(noteReference); - String noteAnchor = generateNoteHtmlId(noteReference.getNoteType(), noteReference.getNoteId()); - String noteReferenceAnchor = generateNoteRefHtmlId(noteReference.getNoteType(), noteReference.getNoteId()); - return list(Html.element("sup", list( - Html.element("a", map("href", "#" + noteAnchor, "id", noteReferenceAnchor), list( - Html.text("[" + noteReferences.size() + "]")))))); - } + @Override + public List<HtmlNode> visit(TableRow tableRow, Context context) { + return list(Html.element("tr", convertChildrenToHtml(tableRow, context))); + } - @Override - public List<HtmlNode> visit(CommentReference commentReference, Context context) { - return styleMap.getCommentReference().orElse(HtmlPath.IGNORE).wrap(() -> { - String commentId = commentReference.getCommentId(); - Comment comment = lookup(comments, commentId) - .orElseThrow(() -> new RuntimeException("Referenced comment could not be found, id: " + commentId)); - String label = "[" + comment.getAuthorInitials().orElse("") + (referencedComments.size() + 1) + "]"; - referencedComments.add(new ReferencedComment(label, comment)); - - // TODO: Remove duplication with note references - return list(Html.element( - "a", - map( - "href", "#" + generateReferentHtmlId("comment", commentId), - "id", generateReferenceHtmlId("comment", commentId)), - list(Html.text(label)))); - }).get(); + @Override + public List<HtmlNode> visit(TableCell tableCell, Context context) { + String tagName = context.isHeader ? "th" : "td"; + Map<String, String> attributes = new HashMap<>(); + if (tableCell.getColspan() != 1) { + attributes.put("colspan", Integer.toString(tableCell.getColspan())); + } + if (tableCell.getRowspan() != 1) { + attributes.put("rowspan", Integer.toString(tableCell.getRowspan())); } + return list(Html.element(tagName, attributes, + Lists.cons(Html.FORCE_WRITE, convertChildrenToHtml(tableCell, context)))); + } + + @Override + public List<HtmlNode> visit(Hyperlink hyperlink, Context context) { + Map<String, String> attributes = mutableMap("href", generateHref(hyperlink)); + hyperlink.getTargetFrame().ifPresent(targetFrame -> + attributes.put("target", targetFrame) + ); - @Override - public List<HtmlNode> visit(Image image, Context context) { - // TODO: custom image handlers - // TODO: handle empty content type - return image.getContentType() - .map(contentType -> { - try { - Map<String, String> attributes = new HashMap<>(imageConverter.convert(new org.zwobble.mammoth.images.Image() { - @Override - public Optional<String> getAltText() { - return image.getAltText(); - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public InputStream getInputStream() throws IOException { - return image.open(); - } - })); - image.getAltText().ifPresent(altText -> attributes.put("alt", altText)); - return list(Html.element("img", attributes)); - } catch (IOException exception) { - warnings.add(exception.getMessage()); - return Lists.<HtmlNode>list(); - } - }) - .orElse(list()); + return list(Html.collapsibleElement("a", attributes, convertChildrenToHtml(hyperlink, context))); + } + + private String generateHref(Hyperlink hyperlink) { + if (hyperlink.getHref().isPresent()) { + return hyperlink.getHref().get(); + } else if (hyperlink.getAnchor().isPresent()) { + return "#" + generateId(hyperlink.getAnchor().get()); + } else { + return ""; } - }, context); + } + + @Override + public List<HtmlNode> visit(Bookmark bookmark, Context context) { + return list(Html.element("a", map("id", generateId(bookmark.getName())), list(Html.FORCE_WRITE))); + } + + @Override + public List<HtmlNode> visit(NoteReference noteReference, Context context) { + noteReferences.add(noteReference); + String noteAnchor = generateNoteHtmlId(noteReference.getNoteType(), noteReference.getNoteId()); + String noteReferenceAnchor = generateNoteRefHtmlId(noteReference.getNoteType(), noteReference.getNoteId()); + return list(Html.element("sup", list( + Html.element("a", map("href", "#" + noteAnchor, "id", noteReferenceAnchor), list( + Html.text("[" + noteReferences.size() + "]")))))); + } + + @Override + public List<HtmlNode> visit(CommentReference commentReference, Context context) { + return styleMap.getCommentReference().orElse(HtmlPath.IGNORE).wrap(() -> { + String commentId = commentReference.getCommentId(); + Comment comment = lookup(comments, commentId) + .orElseThrow(() -> new RuntimeException("Referenced comment could not be found, id: " + commentId)); + String label = "[" + comment.getAuthorInitials().orElse("") + (referencedComments.size() + 1) + "]"; + referencedComments.add(new ReferencedComment(label, comment)); + + // TODO: Remove duplication with note references + return list(Html.element( + "a", + map( + "href", "#" + generateReferentHtmlId("comment", commentId), + "id", generateReferenceHtmlId("comment", commentId)), + list(Html.text(label)))); + }).get(); + } + + @Override + public List<HtmlNode> visit(Image image, Context context) { + // TODO: custom image handlers + // TODO: handle empty content type + return image.getContentType() + .map(contentType -> { + try { + Map<String, String> attributes = new HashMap<>(imageConverter.convert(new org.zwobble.mammoth.images.Image() { + @Override + public Optional<String> getAltText() { + return image.getAltText(); + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public InputStream getInputStream() throws IOException { + return image.open(); + } + })); + image.getAltText().ifPresent(altText -> attributes.put("alt", altText)); + return list(Html.element("img", attributes)); + } catch (IOException exception) { + warnings.add(exception.getMessage()); + return Lists.<HtmlNode>list(); + } + }) + .orElse(list()); + } + } + + private List<HtmlNode> convertToHtml(DocumentElement element, Context context) { + return element.accept(new ElementConverterVisitor(), context); } private String generateNoteHtmlId(NoteType noteType, String noteId) { diff --git a/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java b/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java index a616aac..f8060a9 100644 --- a/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java +++ b/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java @@ -21,11 +21,6 @@ import static org.zwobble.mammoth.internal.util.Lists.*; import static org.zwobble.mammoth.internal.util.Strings.trimLeft; public class DocumentReader { - @FunctionalInterface - private interface PartWithBodyReader { - <T> T readPart(String name, BiFunction<XmlElement, BodyXmlReader, T> read, Optional<T> defaultValue); - } - public static InternalResult<Document> readDocument(Optional<Path> path, Archive zipFile) { PartPaths partPaths = findPartPaths(zipFile); @@ -33,20 +28,7 @@ public class DocumentReader { Numbering numbering = readNumbering(zipFile, partPaths); ContentTypes contentTypes = readContentTypes(zipFile); FileReader fileReader = new PathRelativeFileReader(path); - PartWithBodyReader partReader = new PartWithBodyReader() { - @Override - public <T> T readPart(String name, BiFunction<XmlElement, BodyXmlReader, T> readPart, Optional<T> defaultValue) { - Relationships relationships = readRelationships(zipFile, findRelationshipsPathFor(name)); - BodyXmlReader bodyReader = new BodyXmlReader(styles, numbering, relationships, contentTypes, zipFile, fileReader); - if (defaultValue.isPresent()) { - return tryParseOfficeXml(zipFile, name) - .map(root -> readPart.apply(root, bodyReader)) - .orElse(defaultValue.get()); - } else { - return readPart.apply(parseOfficeXml(zipFile, name), bodyReader); - } - } - }; + PartWithBodyReader partReader = new PartWithBodyReader(zipFile, contentTypes, fileReader, numbering, styles); return InternalResult.flatMap( readNotes(partReader, partPaths), readComments(partReader, partPaths), @@ -58,6 +40,40 @@ public class DocumentReader { ); } + private static class PartWithBodyReader { + private final Archive zipFile; + private final ContentTypes contentTypes; + private final FileReader fileReader; + private final Numbering numbering; + private final Styles styles; + + public PartWithBodyReader( + Archive zipFile, + ContentTypes contentTypes, + FileReader fileReader, + Numbering numbering, + Styles styles + ) { + this.zipFile = zipFile; + this.contentTypes = contentTypes; + this.fileReader = fileReader; + this.numbering = numbering; + this.styles = styles; + } + + <T> T readPart(String name, BiFunction<XmlElement, BodyXmlReader, T> readPart, Optional<T> defaultValue) { + Relationships relationships = readRelationships(zipFile, findRelationshipsPathFor(name)); + BodyXmlReader bodyReader = new BodyXmlReader(styles, numbering, relationships, contentTypes, zipFile, fileReader); + if (defaultValue.isPresent()) { + return tryParseOfficeXml(zipFile, name) + .map(root -> readPart.apply(root, bodyReader)) + .orElse(defaultValue.get()); + } else { + return readPart.apply(parseOfficeXml(zipFile, name), bodyReader); + } + } + } + public static class PartPaths { private final String mainDocument; private final String comments; |
