diff options
| author | Michael Williamson <mike@zwobble.org> | 2017-03-27 19:20:19 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2017-03-27 19:20:19 +0100 |
| commit | 244ed7482aedf44a8afccc7b2f7f77c977974f82 (patch) | |
| tree | 9d19d3bf802de4dde5a03127a59b612e5df87f46 | |
| parent | b1388b638a872de859d13fc348a80f1d2e2fb634 (diff) | |
Add custom image converters
| -rw-r--r-- | NEWS | 2 | ||||
| -rw-r--r-- | README.md | 59 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/DocumentConverter.java | 9 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/images/Image.java | 11 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/images/ImageConverter.java | 13 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java | 28 | ||||
| -rw-r--r-- | src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java | 44 | ||||
| -rw-r--r-- | src/test/java/org/zwobble/mammoth/tests/MammothTests.java | 23 |
8 files changed, 170 insertions, 19 deletions
@@ -29,6 +29,8 @@ * Handle hyperlinks created with complex fields. +* Support custom image converters. + # 0.0.4 * Add support for comments. @@ -136,6 +136,26 @@ DocumentConverter converter = new DocumentConverter() .disableDefaultStyleMap(); ``` +#### Custom image handlers + +By default, images are converted to `<img>` elements with the source included inline in the `src` attribute. +This behaviour can be changed by calling `imageConverter()` with an [image converter](#image-converters) . + +For instance, the following would replicate the default behaviour: + +```java +DocumentConverter converter = new DocumentConverter() + .imageConverter(image -> { + String base64 = streamToBase64(image::getInputStream); + String src = "data:" + image.getContentType() + ";base64," + base64; + Map<String, String> attributes = new HashMap<>(); + attributes.put("src", src); + return attributes; + }); +``` + +where `streamToBase64` is a function that reads an input stream and encodes it as a Base64 string. + #### Bold By default, bold text is wrapped in `<strong>` tags. @@ -239,6 +259,10 @@ Methods: such as those used by bookmarks, footnotes and endnotes. Defaults to the empty string. +* `DocumentConverter imageConverter(ImageConverter.ImgElement imageConverter)`: + By default, images are converted to `<img>` elements with the source included inline in the `src` attribute. + Call this to change how images are converted. + #### `Result<T>` Represents the result of a conversion. Methods: @@ -247,6 +271,40 @@ Represents the result of a conversion. Methods: * `Set<String> getWarnings()`: any warnings generated during the conversion. +#### Image converters + +An image converter can be created by implementing `ImageConverter.ImgElement`. +This creates an `<img>` element for each image in the original docx. +The interface has a single method, `Map<String, String> convert(Image image)`. +The `image` argument is the image element being converted, +and has the following methods: + +* `InputStream getInputStream()`: open the image file. + +* `String getContentType()`: the content type of the image, such as `image/png`. + +* `Optional<String> getAltText()`: the alt text of the image, if any. + +`convert()` should return a `Map` of attributes for the `<img>` element. +At a minimum, this should include the `src` attribute. +If any alt text is found for the image, +this will be automatically added to the element's attributes. + +For instance, the following replicates the default image conversion: + +```java +DocumentConverter converter = new DocumentConverter() + .imageConverter(image -> { + String base64 = streamToBase64(image::getInputStream); + String src = "data:" + image.getContentType() + ";base64," + base64; + Map<String, String> attributes = new HashMap<>(); + attributes.put("src", src); + return attributes; + }); +``` + +where `streamToBase64` is a function that reads an input stream and encodes it as a Base64 string. + ## Writing style maps A style map is made up of a number of style mappings separated by new lines. @@ -441,7 +499,6 @@ You can nest elements to any depth. Compared to the JavaScript and Python implementations, the following features are currently missing: -* Custom image handlers * CLI * Writing embedded style maps * Markdown support diff --git a/src/main/java/org/zwobble/mammoth/DocumentConverter.java b/src/main/java/org/zwobble/mammoth/DocumentConverter.java index feba5ac..2dea8e8 100644 --- a/src/main/java/org/zwobble/mammoth/DocumentConverter.java +++ b/src/main/java/org/zwobble/mammoth/DocumentConverter.java @@ -1,5 +1,6 @@ package org.zwobble.mammoth; +import org.zwobble.mammoth.images.ImageConverter; import org.zwobble.mammoth.internal.InternalDocumentConverter; import org.zwobble.mammoth.internal.conversion.DocumentToHtmlOptions; @@ -60,6 +61,14 @@ public class DocumentConverter { } /** + * By default, images are converted to {@code <img>} elements with the source included inline in the {@code src} attribute. + * Call this to change how images are converted. + */ + public DocumentConverter imageConverter(ImageConverter.ImgElement imageConverter) { + return new DocumentConverter(options.imageConverter(imageConverter)); + } + + /** * Converts {@code stream} into an HTML string. * Note that using this method instead of {@link #convertToHtml(File file)} * means that relative paths to other files, such as images, cannot be resolved. diff --git a/src/main/java/org/zwobble/mammoth/images/Image.java b/src/main/java/org/zwobble/mammoth/images/Image.java new file mode 100644 index 0000000..977e27d --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/images/Image.java @@ -0,0 +1,11 @@ +package org.zwobble.mammoth.images; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +public interface Image { + Optional<String> getAltText(); + String getContentType(); + InputStream getInputStream() throws IOException; +} diff --git a/src/main/java/org/zwobble/mammoth/images/ImageConverter.java b/src/main/java/org/zwobble/mammoth/images/ImageConverter.java new file mode 100644 index 0000000..83480f2 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/images/ImageConverter.java @@ -0,0 +1,13 @@ +package org.zwobble.mammoth.images; + +import java.io.IOException; +import java.util.Map; + +public class ImageConverter { + private ImageConverter() { + } + + public interface ImgElement { + Map<String, String> convert(Image image) throws IOException; + } +} 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 cfa4f56..bdb3d01 100644 --- a/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java +++ b/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtml.java @@ -1,16 +1,17 @@ package org.zwobble.mammoth.internal.conversion; +import org.zwobble.mammoth.images.ImageConverter; import org.zwobble.mammoth.internal.documents.*; import org.zwobble.mammoth.internal.html.Html; import org.zwobble.mammoth.internal.html.HtmlNode; import org.zwobble.mammoth.internal.results.InternalResult; import org.zwobble.mammoth.internal.styles.HtmlPath; import org.zwobble.mammoth.internal.styles.StyleMap; -import org.zwobble.mammoth.internal.util.Base64Encoding; import org.zwobble.mammoth.internal.util.Lists; import org.zwobble.mammoth.internal.util.Maps; import java.io.IOException; +import java.io.InputStream; import java.util.*; import java.util.function.Supplier; @@ -53,6 +54,7 @@ public class DocumentToHtml { private final String idPrefix; private final boolean preserveEmptyParagraphs; private final StyleMap styleMap; + private final ImageConverter.ImgElement imageConverter; private final Map<String, Comment> comments; private final List<NoteReference> noteReferences = new ArrayList<>(); private final List<ReferencedComment> referencedComments = new ArrayList<>(); @@ -62,6 +64,7 @@ public class DocumentToHtml { this.idPrefix = options.idPrefix(); this.preserveEmptyParagraphs = options.shouldPreserveEmptyParagraphs(); this.styleMap = options.styleMap(); + this.imageConverter = options.imageConverter(); this.comments = Maps.toMapWithKey(comments, Comment::getCommentId); } @@ -278,14 +281,23 @@ public class DocumentToHtml { return image.getContentType() .map(contentType -> { try { - Map<String, String> attributes = new HashMap<>(); - - String base64 = Base64Encoding.streamToBase64(image::open); - String src = "data:" + contentType + ";base64," + base64; - attributes.put("src", src); - + 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()); diff --git a/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java b/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java index 8a709af..52b2558 100644 --- a/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java +++ b/src/main/java/org/zwobble/mammoth/internal/conversion/DocumentToHtmlOptions.java @@ -1,11 +1,28 @@ package org.zwobble.mammoth.internal.conversion; +import org.zwobble.mammoth.images.ImageConverter; import org.zwobble.mammoth.internal.styles.DefaultStyles; import org.zwobble.mammoth.internal.styles.StyleMap; import org.zwobble.mammoth.internal.styles.parsing.StyleMapParser; +import org.zwobble.mammoth.internal.util.Base64Encoding; + +import static org.zwobble.mammoth.internal.util.Maps.map; public class DocumentToHtmlOptions { - public static final DocumentToHtmlOptions DEFAULT = new DocumentToHtmlOptions("", false, StyleMap.EMPTY, StyleMap.EMPTY, false, false); + public static final DocumentToHtmlOptions DEFAULT = new DocumentToHtmlOptions( + "", + false, + StyleMap.EMPTY, + StyleMap.EMPTY, + false, + false, + image -> { + String base64 = Base64Encoding.streamToBase64(image::getInputStream); + String src = "data:" + image.getContentType() + ";base64," + base64; + return map("src", src); + } + + ); private final String idPrefix; private final boolean preserveEmptyParagraphs; @@ -13,6 +30,7 @@ public class DocumentToHtmlOptions { private final StyleMap embeddedStyleMap; private final boolean disableDefaultStyleMap; private final boolean disableEmbeddedStyleMap; + private final ImageConverter.ImgElement imageConverter; public DocumentToHtmlOptions( String idPrefix, @@ -20,7 +38,8 @@ public class DocumentToHtmlOptions { StyleMap styleMap, StyleMap embeddedStyleMap, boolean disableDefaultStyleMap, - boolean disableEmbeddedStyleMap + boolean disableEmbeddedStyleMap, + ImageConverter.ImgElement imageConverter ) { this.idPrefix = idPrefix; this.preserveEmptyParagraphs = preserveEmptyParagraphs; @@ -28,14 +47,15 @@ public class DocumentToHtmlOptions { this.embeddedStyleMap = embeddedStyleMap; this.disableDefaultStyleMap = disableDefaultStyleMap; this.disableEmbeddedStyleMap = disableEmbeddedStyleMap; + this.imageConverter = imageConverter; } public DocumentToHtmlOptions idPrefix(String prefix) { - return new DocumentToHtmlOptions(prefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap); + return new DocumentToHtmlOptions(prefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter); } public DocumentToHtmlOptions preserveEmptyParagraphs() { - return new DocumentToHtmlOptions(idPrefix, true, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap); + return new DocumentToHtmlOptions(idPrefix, true, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter); } public DocumentToHtmlOptions addStyleMap(String styleMap) { @@ -43,19 +63,23 @@ public class DocumentToHtmlOptions { } public DocumentToHtmlOptions addStyleMap(StyleMap styleMap) { - return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, this.styleMap.update(styleMap), embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap); + return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, this.styleMap.update(styleMap), embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter); } public DocumentToHtmlOptions disableDefaultStyleMap() { - return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, true, disableEmbeddedStyleMap); + return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, true, disableEmbeddedStyleMap, imageConverter); } public DocumentToHtmlOptions disableEmbeddedStyleMap() { - return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, true); + return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, true, imageConverter); } public DocumentToHtmlOptions addEmbeddedStyleMap(StyleMap embeddedStyleMap) { - return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap); + return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter); + } + + public DocumentToHtmlOptions imageConverter(ImageConverter.ImgElement imageConverter) { + return new DocumentToHtmlOptions(idPrefix, preserveEmptyParagraphs, styleMap, embeddedStyleMap, disableDefaultStyleMap, disableEmbeddedStyleMap, imageConverter); } public String idPrefix() { @@ -77,4 +101,8 @@ public class DocumentToHtmlOptions { styleMap = styleMap.update(this.styleMap); return styleMap; } + + public ImageConverter.ImgElement imageConverter() { + return imageConverter; + } } diff --git a/src/test/java/org/zwobble/mammoth/tests/MammothTests.java b/src/test/java/org/zwobble/mammoth/tests/MammothTests.java index ac81c76..393f1a7 100644 --- a/src/test/java/org/zwobble/mammoth/tests/MammothTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/MammothTests.java @@ -3,11 +3,16 @@ package org.zwobble.mammoth.tests; import org.junit.Test; import org.zwobble.mammoth.DocumentConverter; import org.zwobble.mammoth.Result; -import org.zwobble.mammoth.internal.docx.EmbeddedStyleMap; +import org.zwobble.mammoth.images.ImageConverter; import org.zwobble.mammoth.internal.archives.InMemoryArchive; +import org.zwobble.mammoth.internal.docx.EmbeddedStyleMap; import org.zwobble.mammoth.internal.styles.parsing.ParseException; +import org.zwobble.mammoth.internal.util.Base64Encoding; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Function; @@ -15,6 +20,7 @@ import java.util.function.Function; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.zwobble.mammoth.internal.util.Lists.list; +import static org.zwobble.mammoth.internal.util.Maps.map; import static org.zwobble.mammoth.tests.ResultMatchers.isResult; import static org.zwobble.mammoth.tests.ResultMatchers.isSuccess; import static org.zwobble.mammoth.tests.util.MammothAsserts.assertThrows; @@ -132,6 +138,19 @@ public class MammothTests { } @Test + public void imageConversionCanBeCustomised() throws IOException { + ImageConverter.ImgElement imageConverter = image -> { + String base64 = Base64Encoding.streamToBase64(image::getInputStream); + String src = base64.substring(0, 2) + "," + image.getContentType(); + return map("src", src); + }; + assertThat( + convertToHtml("tiny-picture.docx", converter -> converter.imageConverter(imageConverter)), + isSuccess("<p><img src=\"iV,image/png\" /></p>") + ); + } + + @Test public void contentTypesAreRead() throws IOException { assertThat( convertToHtml("tiny-picture-custom-content-type.docx"), |
