diff options
10 files changed, 185 insertions, 12 deletions
diff --git a/src/main/java/org/zwobble/mammoth/Mammoth.java b/src/main/java/org/zwobble/mammoth/Mammoth.java index 9564a6f..0651944 100644 --- a/src/main/java/org/zwobble/mammoth/Mammoth.java +++ b/src/main/java/org/zwobble/mammoth/Mammoth.java @@ -8,24 +8,25 @@ import org.zwobble.mammoth.xml.XmlElement; import java.io.File; import java.io.IOException; -import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import static com.google.common.collect.Iterables.transform; public class Mammoth { public static Result<String> convertToHtml(File file) { - try (ZipFile zipFile = new ZipFile(file)) { - ZipEntry entry = zipFile.getEntry("word/document.xml"); - XmlElement documentXml = OfficeXml.parseXml(zipFile.getInputStream(entry)); + try (DocxFile zipFile = new ZippedDocxFile(new ZipFile(file))) { + XmlElement documentXml = OfficeXml.parseXml(zipFile.getInputStream("word/document.xml")); Styles styles = Styles.EMPTY; Numbering numbering = Numbering.EMPTY; Relationships relationships = Relationships.EMPTY; + ContentTypes contentTypes = ContentTypes.DEFAULT; DocumentXmlReader reader = new DocumentXmlReader(new BodyXmlReader( styles, numbering, - relationships)); + relationships, + contentTypes, + zipFile)); return reader.readElement(documentXml) .map(Mammoth::convertToHtml); } catch (IOException e) { @@ -104,6 +105,11 @@ public class Mammoth { public String visit(NoteReference noteReference) { throw new UnsupportedOperationException(); } + + @Override + public String visit(Image image) { + throw new UnsupportedOperationException(); + } }); } } diff --git a/src/main/java/org/zwobble/mammoth/documents/DocumentElementVisitor.java b/src/main/java/org/zwobble/mammoth/documents/DocumentElementVisitor.java index f3a4ca4..6874e87 100644 --- a/src/main/java/org/zwobble/mammoth/documents/DocumentElementVisitor.java +++ b/src/main/java/org/zwobble/mammoth/documents/DocumentElementVisitor.java @@ -15,4 +15,6 @@ public interface DocumentElementVisitor<T> { T visit(Hyperlink hyperlink); T visit(Bookmark bookmark); T visit(NoteReference noteReference); + + T visit(Image image); } diff --git a/src/main/java/org/zwobble/mammoth/documents/Image.java b/src/main/java/org/zwobble/mammoth/documents/Image.java new file mode 100644 index 0000000..7435cbf --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/documents/Image.java @@ -0,0 +1,39 @@ +package org.zwobble.mammoth.documents; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +public class Image implements DocumentElement { + @FunctionalInterface + public interface InputStreamSupplier { + InputStream open() throws IOException; + } + + private final Optional<String> altText; + private final Optional<String> contentType; + private final InputStreamSupplier open; + + public Image(Optional<String> altText, Optional<String> contentType, InputStreamSupplier open) { + this.altText = altText; + this.contentType = contentType; + this.open = open; + } + + public Optional<String> getAltText() { + return altText; + } + + public Optional<String> getContentType() { + return contentType; + } + + public InputStream open() throws IOException { + return open.open(); + } + + @Override + public <T> T accept(DocumentElementVisitor<T> visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java b/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java index 19f649a..4a0a169 100644 --- a/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java +++ b/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java @@ -17,16 +17,27 @@ import static org.zwobble.mammoth.docx.ReadResult.EMPTY_SUCCESS; import static org.zwobble.mammoth.docx.ReadResult.success; import static org.zwobble.mammoth.results.Warning.warning; import static org.zwobble.mammoth.util.MammothLists.list; +import static org.zwobble.mammoth.util.MammothStrings.trimLeft; public class BodyXmlReader { private final Styles styles; private final Numbering numbering; private final Relationships relationships; - - public BodyXmlReader(Styles styles, Numbering numbering, Relationships relationships) { + private final ContentTypes contentTypes; + private final DocxFile file; + + public BodyXmlReader( + Styles styles, + Numbering numbering, + Relationships relationships, + ContentTypes contentTypes, + DocxFile file) + { this.styles = styles; this.numbering = numbering; this.relationships = relationships; + this.contentTypes = contentTypes; + this.file = file; } public ReadResult readElement(XmlElement element) { @@ -62,6 +73,9 @@ public class BodyXmlReader { case "w:pict": return readPict(element); + case "v:imagedata": + return readImagedata(element); + case "w:ins": case "w:smartTag": case "w:drawing": @@ -228,6 +242,16 @@ public class BodyXmlReader { return readElements(element.children()).toExtra(); } + private ReadResult readImagedata(XmlElement element) { + Optional<String> title = element.getAttributeOrNone("o:title"); + String relationshipId = element.getAttribute("r:id"); + Relationship relationship = relationships.findRelationshipById(relationshipId); + String imagePath = "word/" + trimLeft(relationship.getTarget(), '/'); + Optional<String> contentType = contentTypes.findContentType(imagePath); + Image image = new Image(title, contentType, () -> file.getInputStream(imagePath)); + return success(image); + } + private Optional<String> readVal(XmlElementLike element, String name) { return element.findChildOrEmpty(name).getAttributeOrNone("w:val"); } diff --git a/src/main/java/org/zwobble/mammoth/docx/ContentTypes.java b/src/main/java/org/zwobble/mammoth/docx/ContentTypes.java index f36ca49..81c3c1a 100644 --- a/src/main/java/org/zwobble/mammoth/docx/ContentTypes.java +++ b/src/main/java/org/zwobble/mammoth/docx/ContentTypes.java @@ -6,7 +6,11 @@ import com.google.common.io.Files; import java.util.Map; import java.util.Optional; +import static org.zwobble.mammoth.util.MammothMaps.map; + public class ContentTypes { + public static final ContentTypes DEFAULT = new ContentTypes(map(), map()); + private static final Map<String, String> imageExtensions = ImmutableMap.<String, String>builder() .put("png", "png") .put("gif", "gif") diff --git a/src/main/java/org/zwobble/mammoth/docx/DocxFile.java b/src/main/java/org/zwobble/mammoth/docx/DocxFile.java new file mode 100644 index 0000000..b05ee1f --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/docx/DocxFile.java @@ -0,0 +1,9 @@ +package org.zwobble.mammoth.docx; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +public interface DocxFile extends Closeable { + InputStream getInputStream(String name) throws IOException; +} diff --git a/src/main/java/org/zwobble/mammoth/docx/ZippedDocxFile.java b/src/main/java/org/zwobble/mammoth/docx/ZippedDocxFile.java new file mode 100644 index 0000000..6b8528a --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/docx/ZippedDocxFile.java @@ -0,0 +1,25 @@ +package org.zwobble.mammoth.docx; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class ZippedDocxFile implements DocxFile { + private final ZipFile zipFile; + + public ZippedDocxFile(ZipFile zipFile) { + this.zipFile = zipFile; + } + + @Override + public InputStream getInputStream(String name) throws IOException { + ZipEntry entry = zipFile.getEntry("word/document.xml"); + return zipFile.getInputStream(entry); + } + + @Override + public void close() throws IOException { + zipFile.close(); + } +} diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlReaderMakers.java b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlReaderMakers.java index 5758dce..41cefd5 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlReaderMakers.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlReaderMakers.java @@ -2,10 +2,10 @@ package org.zwobble.mammoth.tests.docx; import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.Property; -import org.zwobble.mammoth.docx.BodyXmlReader; -import org.zwobble.mammoth.docx.Numbering; -import org.zwobble.mammoth.docx.Relationships; -import org.zwobble.mammoth.docx.Styles; +import org.zwobble.mammoth.docx.*; + +import java.io.IOException; +import java.io.InputStream; import static com.natpryce.makeiteasy.Property.newProperty; @@ -13,10 +13,23 @@ public class BodyXmlReaderMakers { public static final Property<BodyXmlReader, Styles> STYLES = newProperty(); public static final Property<BodyXmlReader, Numbering> NUMBERING = newProperty(); public static final Property<BodyXmlReader, Relationships> RELATIONSHIPS = newProperty(); + public static final Property<BodyXmlReader, ContentTypes> CONTENT_TYPES = newProperty(); + public static final Property<BodyXmlReader, DocxFile> DOCX_FILE = newProperty(); public static final Instantiator<BodyXmlReader> bodyReader = propertyLookup -> new BodyXmlReader( propertyLookup.valueOf(STYLES, Styles.EMPTY), propertyLookup.valueOf(NUMBERING, Numbering.EMPTY), - propertyLookup.valueOf(RELATIONSHIPS, Relationships.EMPTY)); + propertyLookup.valueOf(RELATIONSHIPS, Relationships.EMPTY), + propertyLookup.valueOf(CONTENT_TYPES, ContentTypes.DEFAULT), + propertyLookup.valueOf(DOCX_FILE, new DocxFile() { + @Override + public InputStream getInputStream(String name) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void close() throws IOException { + } + })); } 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 c8f8887..cc0815e 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java @@ -1,5 +1,6 @@ package org.zwobble.mammoth.tests.docx; +import com.google.common.io.CharStreams; import com.natpryce.makeiteasy.Maker; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -13,12 +14,15 @@ import org.zwobble.mammoth.xml.XmlElement; import org.zwobble.mammoth.xml.XmlNode; import org.zwobble.mammoth.xml.XmlNodes; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.List; import java.util.Optional; import static com.natpryce.makeiteasy.MakeItEasy.*; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasProperty; import static org.zwobble.mammoth.documents.NoteReference.endnoteReference; @@ -381,6 +385,25 @@ public class BodyXmlTests { } @Test + public void canReadImagedataElementsWithIdAttribute() throws IOException { + XmlElement element = element("v:imagedata", map("r:id", "rId5", "o:title", "It's a hat")); + 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)); + + Image image = (Image) readSuccess( + a(bodyReader, with(RELATIONSHIPS, relationships), with(DOCX_FILE, file)), + element); + assertThat(image, allOf( + hasProperty("altText", deepEquals(Optional.of("It's a hat"))), + hasProperty("contentType", deepEquals(Optional.of("image/png"))))); + assertThat( + CharStreams.toString(new InputStreamReader(image.open())), + equalTo("Not an image at all!")); + } + + @Test public void appropriateElementsHaveTheirChildrenReadNormally() { assertChildrenAreReadNormally("w:ins"); assertChildrenAreReadNormally("w:smartTag"); diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/InMemoryDocxFile.java b/src/test/java/org/zwobble/mammoth/tests/docx/InMemoryDocxFile.java new file mode 100644 index 0000000..2ed5e27 --- /dev/null +++ b/src/test/java/org/zwobble/mammoth/tests/docx/InMemoryDocxFile.java @@ -0,0 +1,28 @@ +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<String, String> entries; + + public InMemoryDocxFile(Map<String, String> entries) { + this.entries = entries; + } + + @Override + public InputStream getInputStream(String name) throws IOException { + String value = Optional.ofNullable(entries.get(name)).get(); + return new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void close() throws IOException { + } +} |
