From c1604e510cc831a00f6105df35c68a856f061b65 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 21 Feb 2016 09:44:16 +0000 Subject: Allow input streams to be read --- src/main/java/org/zwobble/mammoth/Mammoth.java | 42 ++++++++++++++----- .../org/zwobble/mammoth/docx/DocumentReader.java | 25 +++++++++++- .../org/zwobble/mammoth/docx/InMemoryDocxFile.java | 46 +++++++++++++++++++++ .../org/zwobble/mammoth/tests/MammothTests.java | 47 +++++++++++++++------- .../zwobble/mammoth/tests/docx/BodyXmlTests.java | 4 +- .../mammoth/tests/docx/InMemoryDocxFile.java | 28 ------------- 6 files changed, 135 insertions(+), 57 deletions(-) create mode 100644 src/main/java/org/zwobble/mammoth/docx/InMemoryDocxFile.java delete mode 100644 src/test/java/org/zwobble/mammoth/tests/docx/InMemoryDocxFile.java diff --git a/src/main/java/org/zwobble/mammoth/Mammoth.java b/src/main/java/org/zwobble/mammoth/Mammoth.java index 93c639a..4963c3a 100644 --- a/src/main/java/org/zwobble/mammoth/Mammoth.java +++ b/src/main/java/org/zwobble/mammoth/Mammoth.java @@ -7,6 +7,7 @@ import org.zwobble.mammoth.documents.HasChildren; import org.zwobble.mammoth.documents.Paragraph; import org.zwobble.mammoth.documents.Text; import org.zwobble.mammoth.docx.DocxFile; +import org.zwobble.mammoth.docx.InMemoryDocxFile; import org.zwobble.mammoth.docx.ZippedDocxFile; import org.zwobble.mammoth.html.Html; import org.zwobble.mammoth.results.Result; @@ -18,6 +19,8 @@ import org.zwobble.mammoth.util.Casts; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.zip.ZipFile; @@ -61,30 +64,47 @@ public class Mammoth { .mapParagraph(ParagraphMatcher.styleName("Normal"), HtmlPath.element("p")) .build(); - public static Result convertToHtml(File file) { + public static Result convertToHtml(InputStream stream) throws IOException { + return convertToHtml(stream, Options.DEFAULT); + } + + public static Result convertToHtml(InputStream stream, Options options) throws IOException { + return withDocxFile(stream, zipFile -> + convertToHtml(Optional.empty(), zipFile, options)); + } + + public static Result convertToHtml(File file) throws IOException { return convertToHtml(file, Options.DEFAULT); } - public static Result convertToHtml(File file, Options options) { + public static Result convertToHtml(File file, Options options) throws IOException { return withDocxFile(file, zipFile -> - readDocument(Optional.of(file.toPath()), zipFile) - .flatMap(nodes -> DocumentConverter.convertToHtml(options.idPrefix, options.preserveEmptyParagraphs, DEFAULT_STYLE_MAP, nodes)) - .map(Html::stripEmpty) - .map(Html::collapse) - .map(Html::write)); + convertToHtml(Optional.of(file.toPath()), zipFile, options)); } - public static Result extractRawText(File file) { + private static Result convertToHtml(Optional path, DocxFile zipFile, Options options) { + return readDocument(path, zipFile) + .flatMap(nodes -> DocumentConverter.convertToHtml(options.idPrefix, options.preserveEmptyParagraphs, DEFAULT_STYLE_MAP, nodes)) + .map(Html::stripEmpty) + .map(Html::collapse) + .map(Html::write); + } + + public static Result extractRawText(File file) throws IOException { return withDocxFile(file, zipFile -> readDocument(Optional.of(file.toPath()), zipFile) .map(Mammoth::extractRawTextOfChildren)); } - private static T withDocxFile(File file, Function function) { + private static T withDocxFile(File file, Function function) throws IOException { try (DocxFile zipFile = new ZippedDocxFile(new ZipFile(file))) { return function.apply(zipFile); - } catch (IOException e) { - throw new UnsupportedOperationException("Should return a result of failure"); + } + } + + private static T withDocxFile(InputStream stream, Function function) throws IOException { + try (DocxFile zipFile = InMemoryDocxFile.fromStream(stream)) { + return function.apply(zipFile); } } diff --git a/src/main/java/org/zwobble/mammoth/docx/DocumentReader.java b/src/main/java/org/zwobble/mammoth/docx/DocumentReader.java index 284dd89..1fc0fc8 100644 --- a/src/main/java/org/zwobble/mammoth/docx/DocumentReader.java +++ b/src/main/java/org/zwobble/mammoth/docx/DocumentReader.java @@ -7,6 +7,9 @@ import org.zwobble.mammoth.util.MammothLists; import org.zwobble.mammoth.xml.XmlElement; import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Path; import java.util.Optional; @@ -24,7 +27,14 @@ public class DocumentReader { ContentTypes contentTypes = readContentTypes(zipFile); FileReader fileReader = uri -> { try { - return path.get().toUri().resolve(uri).toURL().openStream(); + Optional absoluteUri = asAbsoluteUri(uri); + if (absoluteUri.isPresent()) { + return open(absoluteUri.get()); + } else if (path.isPresent()) { + return open(path.get().toUri().resolve(uri)); + } else { + throw new IOException("path of document is unknown, but is required for relative URI"); + } } catch (IOException exception) { throw new IOException("could not open external image '" + uri + "': " + exception.getMessage()); } @@ -40,6 +50,19 @@ public class DocumentReader { }); } + private static InputStream open(URI uri) throws IOException { + return uri.toURL().openStream(); + } + + private static Optional asAbsoluteUri(String uriString) { + try { + URI uri = new URI(uriString); + return uri.isAbsolute() ? Optional.of(uri) : Optional.empty(); + } catch (URISyntaxException exception) { + return Optional.empty(); + } + } + private static Result readNotes(DocxFile file, BodyReaders bodyReaders) { return Result.map( tryParseOfficeXml(file, "word/footnotes.xml") diff --git a/src/main/java/org/zwobble/mammoth/docx/InMemoryDocxFile.java b/src/main/java/org/zwobble/mammoth/docx/InMemoryDocxFile.java new file mode 100644 index 0000000..3c6d4b4 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/docx/InMemoryDocxFile.java @@ -0,0 +1,46 @@ +package org.zwobble.mammoth.docx; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.io.ByteStreams; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Optional; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class InMemoryDocxFile implements DocxFile { + public static DocxFile fromStream(InputStream stream) throws IOException { + ZipInputStream zipStream = new ZipInputStream(stream); + ImmutableMap.Builder entries = ImmutableMap.builder(); + ZipEntry entry; + while ((entry = zipStream.getNextEntry()) != null) { + entries.put(entry.getName(), ByteStreams.toByteArray(zipStream)); + } + return new InMemoryDocxFile(entries.build()); + } + + public static DocxFile fromStrings(Map entries) { + return new InMemoryDocxFile(Maps.transformValues(entries, value -> value.getBytes(StandardCharsets.UTF_8))); + } + + private final Map entries; + + public InMemoryDocxFile(Map entries) { + this.entries = entries; + } + + @Override + public Optional tryGetInputStream(String name) throws IOException { + return Optional.ofNullable(entries.get(name)) + .map(ByteArrayInputStream::new); + } + + @Override + public void close() throws IOException { + } +} diff --git a/src/test/java/org/zwobble/mammoth/tests/MammothTests.java b/src/test/java/org/zwobble/mammoth/tests/MammothTests.java index 603d39a..52efde2 100644 --- a/src/test/java/org/zwobble/mammoth/tests/MammothTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/MammothTests.java @@ -16,42 +16,42 @@ import static org.zwobble.mammoth.tests.DeepReflectionMatcher.deepEquals; public class MammothTests { @Test - public void docxContainingOneParagraphIsConvertedToSingleParagraphElement() { + public void docxContainingOneParagraphIsConvertedToSingleParagraphElement() throws IOException { assertThat( convertToHtml("single-paragraph.docx"), deepEquals(success("

Walking on imported air

"))); } @Test - public void canReadFilesWithUtf8Bom() { + public void canReadFilesWithUtf8Bom() throws IOException { assertThat( convertToHtml("utf8-bom.docx"), deepEquals(success("

This XML has a byte order mark.

"))); } @Test - public void emptyParagraphsAreIgnoredByDefault() { + public void emptyParagraphsAreIgnoredByDefault() throws IOException { assertThat( convertToHtml("empty.docx"), deepEquals(success(""))); } @Test - public void emptyParagraphsArePreservedIfIgnoreEmptyParagraphsIsFalse() { + public void emptyParagraphsArePreservedIfIgnoreEmptyParagraphsIsFalse() throws IOException { assertThat( convertToHtml("empty.docx", Mammoth.Options.DEFAULT.preserveEmptyParagraphs()), deepEquals(success("

"))); } @Test - public void simpleListIsConvertedToListElements() { + public void simpleListIsConvertedToListElements() throws IOException { assertThat( convertToHtml("simple-list.docx"), deepEquals(success("
  • Apple
  • Banana
"))); } @Test - public void wordTablesAreConvertedToHtmlTables() { + public void wordTablesAreConvertedToHtmlTables() throws IOException { assertThat( convertToHtml("tables.docx"), deepEquals(success( @@ -64,7 +64,7 @@ public class MammothTests { } @Test - public void inlineImagesAreIncludedInOutput() { + public void inlineImagesAreIncludedInOutput() throws IOException { assertThat( convertToHtml("tiny-picture.docx"), deepEquals(success("

"))); @@ -85,6 +85,23 @@ public class MammothTests { } } + @Test + public void warnIfDocumentHasImagesStoredOutsideOfDocumentWhenPathOfDocumentIsUnknown() throws IOException { + Path tempDirectory = Files.createTempDirectory("mammoth-"); + try { + Path documentPath = tempDirectory.resolve("external-picture.docx"); + Files.copy(TestData.file("external-picture.docx").toPath(), documentPath); + assertThat( + Mammoth.convertToHtml(documentPath.toUri().toURL().openStream()), + allOf( + hasProperty("value", equalTo("")), + hasProperty("warnings", contains( + hasProperty("message", startsWith("could not open external image 'tiny-picture.png': path of document is unknown, but is required for relative URI")))))); + } finally { + tempDirectory.toFile().delete(); + } + } + @Test public void warnIfImagesStoredOutsideOfDocumentAreNotFound() throws IOException { Path tempDirectory = Files.createTempDirectory("mammoth-"); @@ -103,14 +120,14 @@ public class MammothTests { } @Test - public void contentTypesAreRead() { + public void contentTypesAreRead() throws IOException { assertThat( convertToHtml("tiny-picture-custom-content-type.docx"), deepEquals(success("

"))); } @Test - public void footnotesAreAppendedToText() { + public void footnotesAreAppendedToText() throws IOException { assertThat( convertToHtml("footnotes.docx", Mammoth.Options.DEFAULT.idPrefix("doc-42-")), deepEquals(success( @@ -122,7 +139,7 @@ public class MammothTests { } @Test - public void endNotesAreAppendedToText() { + public void endNotesAreAppendedToText() throws IOException { assertThat( convertToHtml("endnotes.docx", Mammoth.Options.DEFAULT.idPrefix("doc-42-")), deepEquals(success( @@ -134,7 +151,7 @@ public class MammothTests { } @Test - public void relationshipsAreReadForEachFileContainingBodyXml() { + public void relationshipsAreReadForEachFileContainingBodyXml() throws IOException { assertThat( convertToHtml("footnote-hyperlink.docx", Mammoth.Options.DEFAULT.idPrefix("doc-42-")), deepEquals(success( @@ -143,25 +160,25 @@ public class MammothTests { } @Test - public void textBoxesAreRead() { + public void textBoxesAreRead() throws IOException { assertThat( convertToHtml("text-box.docx"), deepEquals(success("

Datum plane

"))); } @Test - public void canExtractRawText() { + public void canExtractRawText() throws IOException { assertThat( Mammoth.extractRawText(TestData.file("simple-list.docx")), deepEquals(success("Apple\n\nBanana\n\n"))); } - private Result convertToHtml(String name) { + private Result convertToHtml(String name) throws IOException { File file = TestData.file(name); return Mammoth.convertToHtml(file); } - private Result convertToHtml(String name, Mammoth.Options options) { + private Result convertToHtml(String name, Mammoth.Options options) throws IOException { File file = TestData.file(name); return Mammoth.convertToHtml(file, options); } 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 2c1d505..8b31452 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java @@ -437,7 +437,7 @@ public class BodyXmlTests { Relationships relationships = new Relationships(map( "rId5", new Relationship("media/hat.png"))); String imageBytes = "Not an image at all!"; - DocxFile file = new InMemoryDocxFile(map("word/media/hat.png", imageBytes)); + DocxFile file = InMemoryDocxFile.fromStrings(map("word/media/hat.png", imageBytes)); Image image = (Image) readSuccess( a(bodyReader, with(RELATIONSHIPS, relationships), with(DOCX_FILE, file)), @@ -465,7 +465,7 @@ public class BodyXmlTests { XmlElement element = inlineImageXml(embeddedBlipXml("rId5"), ""); Relationships relationships = new Relationships(map( "rId5", new Relationship("media/hat.emf"))); - DocxFile file = new InMemoryDocxFile(map("word/media/hat.emf", "Not an image at all!")); + DocxFile file = InMemoryDocxFile.fromStrings(map("word/media/hat.emf", "Not an image at all!")); ContentTypes contentTypes = new ContentTypes(map("emf", "image/x-emf"), map()); Result result = read( diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/InMemoryDocxFile.java b/src/test/java/org/zwobble/mammoth/tests/docx/InMemoryDocxFile.java deleted file mode 100644 index 4aef42e..0000000 --- a/src/test/java/org/zwobble/mammoth/tests/docx/InMemoryDocxFile.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.zwobble.mammoth.tests.docx; - -import org.zwobble.mammoth.docx.DocxFile; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.Optional; - -public class InMemoryDocxFile implements DocxFile { - private final Map entries; - - public InMemoryDocxFile(Map entries) { - this.entries = entries; - } - - @Override - public Optional tryGetInputStream(String name) throws IOException { - return Optional.ofNullable(entries.get(name)) - .map(value -> new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public void close() throws IOException { - } -} -- cgit v1.2.3