diff options
7 files changed, 116 insertions, 4 deletions
diff --git a/src/main/java/org/zwobble/mammoth/internal/archives/Archive.java b/src/main/java/org/zwobble/mammoth/internal/archives/Archive.java index 4440234..2f5a4ae 100644 --- a/src/main/java/org/zwobble/mammoth/internal/archives/Archive.java +++ b/src/main/java/org/zwobble/mammoth/internal/archives/Archive.java @@ -7,4 +7,5 @@ import java.util.Optional; public interface Archive extends Closeable { Optional<InputStream> tryGetInputStream(String name) throws IOException; + boolean exists(String name); } diff --git a/src/main/java/org/zwobble/mammoth/internal/archives/InMemoryArchive.java b/src/main/java/org/zwobble/mammoth/internal/archives/InMemoryArchive.java index 9ad7134..0844e8d 100644 --- a/src/main/java/org/zwobble/mammoth/internal/archives/InMemoryArchive.java +++ b/src/main/java/org/zwobble/mammoth/internal/archives/InMemoryArchive.java @@ -46,6 +46,11 @@ public class InMemoryArchive implements MutableArchive { } @Override + public boolean exists(String name) { + return entries.containsKey(name); + } + + @Override public void writeEntry(String path, String content) { entries.put(path, content.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/org/zwobble/mammoth/internal/archives/ZippedArchive.java b/src/main/java/org/zwobble/mammoth/internal/archives/ZippedArchive.java index 853ced9..47be074 100644 --- a/src/main/java/org/zwobble/mammoth/internal/archives/ZippedArchive.java +++ b/src/main/java/org/zwobble/mammoth/internal/archives/ZippedArchive.java @@ -29,6 +29,11 @@ public class ZippedArchive implements Archive { } @Override + public boolean exists(String name) { + return zipFile.getEntry(name) != null; + } + + @Override public void close() throws IOException { zipFile.close(); } 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 4ef9c37..9d1b270 100644 --- a/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java +++ b/src/main/java/org/zwobble/mammoth/internal/docx/DocumentReader.java @@ -14,7 +14,8 @@ import java.nio.file.Path; import java.util.List; import java.util.Optional; -import static org.zwobble.mammoth.internal.util.Lists.list; +import static org.zwobble.mammoth.internal.util.Lists.*; +import static org.zwobble.mammoth.internal.util.Strings.trimLeft; public class DocumentReader { @FunctionalInterface @@ -23,12 +24,15 @@ public class DocumentReader { } public static InternalResult<Document> readDocument(Optional<Path> path, Archive zipFile) { + Relationships packageRelationships = readPackageRelationships(zipFile); + String documentFilename = findDocumentFilename(zipFile, packageRelationships); + Styles styles = readStyles(zipFile); Numbering numbering = readNumbering(zipFile); ContentTypes contentTypes = readContentTypes(zipFile); FileReader fileReader = new PathRelativeFileReader(path); BodyReaders bodyReaders = name -> { - Relationships relationships = readRelationships(zipFile, name); + Relationships relationships = readRelationshipsFor(zipFile, name); return new BodyXmlReader(styles, numbering, relationships, contentTypes, zipFile, fileReader); }; return InternalResult.flatMap( @@ -36,10 +40,30 @@ public class DocumentReader { readComments(zipFile, bodyReaders), (notes, comments) -> { DocumentXmlReader reader = new DocumentXmlReader(bodyReaders.forName("document"), notes, comments); - return reader.readElement(parseOfficeXml(zipFile, "word/document.xml")); + return reader.readElement(parseOfficeXml(zipFile, documentFilename)); }); } + private static Relationships readPackageRelationships(Archive archive) { + return readRelationships(archive, "_rels/.rels"); + } + + private static String findDocumentFilename(Archive archive, Relationships packageRelationships) { + String officeDocumentType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"; + List<String> targets = eagerConcat( + eagerMap( + packageRelationships.findTargetsByType(officeDocumentType), + target -> trimLeft(target, '/') + ), + list("word/document.xml") + ); + List<String> validTargets = eagerFilter(targets, archive::exists); + return tryGetFirst(validTargets) + .orElseThrow(() -> new PassThroughException( + new IOException("Could not find word/document.xml in ZIP file. Are you sure this is a valid .docx file?") + )); + } + private static InternalResult<List<Comment>> readComments(Archive file, BodyReaders bodyReaders) { return tryParseOfficeXml(file, "word/comments.xml") .map(new CommentXmlReader(bodyReaders.forName("comments"))::readElement) @@ -75,8 +99,12 @@ public class DocumentReader { .orElse(ContentTypes.DEFAULT); } + private static Relationships readRelationshipsFor(Archive zipFile, String name) { + return readRelationships(zipFile, "word/_rels/" + name + ".xml.rels"); + } + private static Relationships readRelationships(Archive zipFile, String name) { - return tryParseOfficeXml(zipFile, "word/_rels/" + name + ".xml.rels") + return tryParseOfficeXml(zipFile, name) .map(RelationshipsXml::readRelationshipsXmlElement) .orElse(Relationships.EMPTY); } diff --git a/src/main/java/org/zwobble/mammoth/internal/util/Lists.java b/src/main/java/org/zwobble/mammoth/internal/util/Lists.java index 3fbb5dd..b6d6570 100644 --- a/src/main/java/org/zwobble/mammoth/internal/util/Lists.java +++ b/src/main/java/org/zwobble/mammoth/internal/util/Lists.java @@ -80,6 +80,14 @@ public class Lists { return stream(iterable).collect(Collectors.toList()); } + public static <T> Optional<T> tryGetFirst(List<T> list) { + if (list.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(list.get(0)); + } + } + public static <T> Optional<T> tryGetLast(List<T> list) { if (list.isEmpty()) { return Optional.empty(); 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 acbf95f..1a172ce 100644 --- a/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlReaderMakers.java +++ b/src/test/java/org/zwobble/mammoth/tests/docx/BodyXmlReaderMakers.java @@ -23,6 +23,11 @@ public class BodyXmlReaderMakers { } @Override + public boolean exists(String name) { + throw new UnsupportedOperationException(); + } + + @Override public void close() throws IOException { } }), diff --git a/src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java b/src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java new file mode 100644 index 0000000..d7f1a11 --- /dev/null +++ b/src/test/java/org/zwobble/mammoth/tests/docx/DocumentReaderTests.java @@ -0,0 +1,60 @@ +package org.zwobble.mammoth.tests.docx; + +import org.junit.jupiter.api.Test; +import org.zwobble.mammoth.internal.archives.InMemoryArchive; +import org.zwobble.mammoth.internal.documents.Document; +import org.zwobble.mammoth.internal.docx.DocumentReader; +import org.zwobble.mammoth.internal.results.InternalResult; +import org.zwobble.mammoth.internal.xml.NamespacePrefixes; +import org.zwobble.mammoth.internal.xml.XmlWriter; + +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.zwobble.mammoth.internal.util.Lists.list; +import static org.zwobble.mammoth.internal.util.Maps.map; +import static org.zwobble.mammoth.internal.xml.XmlNodes.element; +import static org.zwobble.mammoth.internal.xml.XmlNodes.text; +import static org.zwobble.mammoth.tests.ResultMatchers.isInternalSuccess; +import static org.zwobble.mammoth.tests.documents.DocumentElementMakers.*; + +public class DocumentReaderTests { + private final NamespacePrefixes mainDocumentNamespaces = NamespacePrefixes.builder() + .put("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main") + .build(); + private final NamespacePrefixes relationshipsNamespaces = NamespacePrefixes.builder() + .defaultPrefix("http://schemas.openxmlformats.org/package/2006/relationships") + .build(); + + @Test + public void mainDocumentIsFoundUsingPackageRelationships() { + InternalResult<Document> result = DocumentReader.readDocument(Optional.empty(), InMemoryArchive.fromStrings(map( + "word/document2.xml", XmlWriter.toString( + element("w:document", list( + element("w:body", list( + element("w:p", list( + element("w:r", list( + element("w:t", list(text("Hello."))) + )) + )) + )) + )), + mainDocumentNamespaces + ), + "_rels/.rels", XmlWriter.toString( + element("Relationships", list( + element("Relationship", map( + "Id", "rId1", + "Target", "/word/document2.xml", + "Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" + )) + )), + relationshipsNamespaces + ) + ))); + + assertThat(result, isInternalSuccess(document( + withChildren(paragraphWithText("Hello.")) + ))); + } +} |
