From ddde81cf806a5534a01483b563651bd84cf7452d Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sat, 13 Feb 2016 23:02:55 +0000 Subject: Read embedded blips in wp:inline --- .../java/org/zwobble/mammoth/documents/Image.java | 6 ++-- .../org/zwobble/mammoth/docx/BodyXmlReader.java | 39 ++++++++++++++++++-- .../zwobble/mammoth/util/InputStreamSupplier.java | 9 +++++ .../org/zwobble/mammoth/xml/XmlElementList.java | 11 ++++++ .../zwobble/mammoth/tests/docx/BodyXmlTests.java | 42 ++++++++++++++++++++++ 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/zwobble/mammoth/util/InputStreamSupplier.java (limited to 'src') diff --git a/src/main/java/org/zwobble/mammoth/documents/Image.java b/src/main/java/org/zwobble/mammoth/documents/Image.java index 7435cbf..8022ea4 100644 --- a/src/main/java/org/zwobble/mammoth/documents/Image.java +++ b/src/main/java/org/zwobble/mammoth/documents/Image.java @@ -1,14 +1,12 @@ package org.zwobble.mammoth.documents; +import org.zwobble.mammoth.util.InputStreamSupplier; + 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 altText; private final Optional contentType; diff --git a/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java b/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java index 4a0a169..4ac44d2 100644 --- a/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java +++ b/src/main/java/org/zwobble/mammoth/docx/BodyXmlReader.java @@ -3,9 +3,11 @@ package org.zwobble.mammoth.docx; import org.zwobble.mammoth.documents.*; import org.zwobble.mammoth.results.Result; import org.zwobble.mammoth.results.Warning; +import org.zwobble.mammoth.util.InputStreamSupplier; import org.zwobble.mammoth.util.MammothOptionals; import org.zwobble.mammoth.xml.XmlElement; import org.zwobble.mammoth.xml.XmlElementLike; +import org.zwobble.mammoth.xml.XmlElementList; import org.zwobble.mammoth.xml.XmlNode; import java.util.Optional; @@ -76,6 +78,9 @@ public class BodyXmlReader { case "v:imagedata": return readImagedata(element); + case "wp:inline": + return readInline(element); + case "w:ins": case "w:smartTag": case "w:drawing": @@ -245,13 +250,41 @@ public class BodyXmlReader { private ReadResult readImagedata(XmlElement element) { Optional title = element.getAttributeOrNone("o:title"); String relationshipId = element.getAttribute("r:id"); - Relationship relationship = relationships.findRelationshipById(relationshipId); - String imagePath = "word/" + trimLeft(relationship.getTarget(), '/'); + String imagePath = relationshipIdToDocxPath(relationshipId); + return readImage(imagePath, title, () -> file.getInputStream(imagePath)); + } + + private ReadResult readInline(XmlElement element) { + Optional altText = element.findChildOrEmpty("wp:docPr").getAttributeOrNone("descr"); + XmlElementList blips = element.findChildren("a:graphic") + .findChildren("a:graphicData") + .findChildren("pic:pic") + .findChildren("pic:blipFill") + .findChildren("a:blip"); + return readBlips(blips, altText); + } + + private ReadResult readBlips(XmlElementList blips, Optional altText) { + return ReadResult.concat(transform(blips, blip -> readBlip(blip, altText))); + } + + private ReadResult readBlip(XmlElement blip, Optional altText) { + String relationshipId = blip.getAttribute("r:embed"); + String imagePath = relationshipIdToDocxPath(relationshipId); + return readImage(imagePath, altText, () -> file.getInputStream(imagePath)); + } + + private ReadResult readImage(String imagePath, Optional altText, InputStreamSupplier open) { Optional contentType = contentTypes.findContentType(imagePath); - Image image = new Image(title, contentType, () -> file.getInputStream(imagePath)); + Image image = new Image(altText, contentType, open); return success(image); } + private String relationshipIdToDocxPath(String relationshipId) { + Relationship relationship = relationships.findRelationshipById(relationshipId); + return "word/" + trimLeft(relationship.getTarget(), '/'); + } + private Optional readVal(XmlElementLike element, String name) { return element.findChildOrEmpty(name).getAttributeOrNone("w:val"); } diff --git a/src/main/java/org/zwobble/mammoth/util/InputStreamSupplier.java b/src/main/java/org/zwobble/mammoth/util/InputStreamSupplier.java new file mode 100644 index 0000000..0f42389 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/util/InputStreamSupplier.java @@ -0,0 +1,9 @@ +package org.zwobble.mammoth.util; + +import java.io.IOException; +import java.io.InputStream; + +@FunctionalInterface +public interface InputStreamSupplier { + InputStream open() throws IOException; +} diff --git a/src/main/java/org/zwobble/mammoth/xml/XmlElementList.java b/src/main/java/org/zwobble/mammoth/xml/XmlElementList.java index 9e46314..b6a68ad 100644 --- a/src/main/java/org/zwobble/mammoth/xml/XmlElementList.java +++ b/src/main/java/org/zwobble/mammoth/xml/XmlElementList.java @@ -1,5 +1,8 @@ package org.zwobble.mammoth.xml; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + import java.util.Iterator; import java.util.List; @@ -14,4 +17,12 @@ public class XmlElementList implements Iterable { public Iterator iterator() { return elements.iterator(); } + + public XmlElementList findChildren(String name) { + Iterable children = Iterables.concat( + Iterables.transform( + elements, + element -> element.findChildren(name))); + return new XmlElementList(ImmutableList.copyOf(children)); + } } 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 cc0815e..0683bff 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlTests.java @@ -17,6 +17,7 @@ import org.zwobble.mammoth.xml.XmlNodes; import java.io.IOException; import java.io.InputStreamReader; import java.util.List; +import java.util.Map; import java.util.Optional; import static com.natpryce.makeiteasy.MakeItEasy.*; @@ -403,6 +404,47 @@ public class BodyXmlTests { equalTo("Not an image at all!")); } + @Test + public void canReadInlinePictures() throws IOException { + XmlElement element = inlineImageXml(embeddedBlipXml("rId5"), "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!")); + } + + private XmlElement inlineImageXml(XmlElement blip, String description) { + return element("w:drawing", list( + element("wp:inline", imageXml(blip, description)))); + } + + private List imageXml(XmlElement blip, String description) { + return list( + element("wp:docPr", map("descr", description)), + element("a:graphic", list( + element("a:graphicData", list( + element("pic:pic", list( + element("pic:blipFill", list(blip))))))))); + } + + private XmlElement embeddedBlipXml(String relationshipId) { + return blipXml(map("r:embed", relationshipId)); + } + + private XmlElement blipXml(Map attributes) { + return element("a:blip", attributes); + } + @Test public void appropriateElementsHaveTheirChildrenReadNormally() { assertChildrenAreReadNormally("w:ins"); -- cgit v1.2.3