summaryrefslogtreecommitdiff
path: root/src/main/java/org/zwobble/mammoth/DocumentConverter.java
blob: abd99bb60ecca145293b204747d69ae2dc0bc43c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package org.zwobble.mammoth;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import org.zwobble.mammoth.internal.DocumentToHtml;
import org.zwobble.mammoth.internal.DocumentToHtmlOptions;
import org.zwobble.mammoth.internal.documents.DocumentElement;
import org.zwobble.mammoth.internal.documents.HasChildren;
import org.zwobble.mammoth.internal.documents.Paragraph;
import org.zwobble.mammoth.internal.documents.Text;
import org.zwobble.mammoth.internal.docx.DocxFile;
import org.zwobble.mammoth.internal.docx.InMemoryDocxFile;
import org.zwobble.mammoth.internal.docx.ZippedDocxFile;
import org.zwobble.mammoth.internal.html.Html;
import org.zwobble.mammoth.internal.results.InternalResult;
import org.zwobble.mammoth.internal.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;

import static org.zwobble.mammoth.internal.docx.DocumentReader.readDocument;
import static org.zwobble.mammoth.internal.util.MammothLists.list;

public class DocumentConverter {
    private final DocumentToHtmlOptions options;

    public DocumentConverter() {
        this(DocumentToHtmlOptions.DEFAULT);
    }

    private DocumentConverter(DocumentToHtmlOptions options) {
        this.options = options;
    }

    /**
     * A string to prepend to any generated IDs,
     * such as those used by bookmarks, footnotes and endnotes.
     * Defaults to the empty string.
     */
    public DocumentConverter idPrefix(String idPrefix) {
        return new DocumentConverter(options.idPrefix(idPrefix));
    }

    /**
     * By default, empty paragraphs are ignored.
     * Call this to preserve empty paragraphs in the output.
     */
    public DocumentConverter preserveEmptyParagraphs() {
        return new DocumentConverter(options.preserveEmptyParagraphs());
    }

    /**
     * Add a style map to specify the mapping of Word styles to HTML.
     * The most recently added style map has the greatest precedence.
     */
    public DocumentConverter addStyleMap(String styleMap) {
        return new DocumentConverter(options.addStyleMap(styleMap));
    }

    /**
     * By default, any added style maps are combined with the default style map.
     * Call this to stop using the default style map altogether.
     */
    public DocumentConverter disableDefaultStyleMap() {
        return new DocumentConverter(options.disableDefaultStyleMap());
    }

    /**
     * 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.
     */
    public Result<String> convertToHtml(InputStream stream) throws IOException {
        return withDocxFile(stream, zipFile ->
            convertToHtml(Optional.empty(), zipFile));
    }

    /**
     * Converts {@code file} into an HTML string.
     */
    public Result<String> convertToHtml(File file) throws IOException {
        return withDocxFile(file, zipFile ->
            convertToHtml(Optional.of(file.toPath()), zipFile));
    }

    private InternalResult<String> convertToHtml(Optional<Path> path, DocxFile zipFile) {
        return readDocument(path, zipFile)
            .flatMap(nodes -> DocumentToHtml.convertToHtml(nodes, options))
            .map(Html::stripEmpty)
            .map(Html::collapse)
            .map(Html::write);
    }

    /**
     * Extract the raw text of the document.
     * This will ignore all formatting in the document.
     * Each paragraph is followed by two newlines.
     */
    public Result<String> extractRawText(InputStream stream) throws IOException {
        return withDocxFile(stream, zipFile ->
            extractRawText(Optional.empty(), zipFile));
    }

    /**
     * Extract the raw text of the document.
     * This will ignore all formatting in the document.
     * Each paragraph is followed by two newlines.
     */
    public Result<String> extractRawText(File file) throws IOException {
        return withDocxFile(file, zipFile ->
            extractRawText(Optional.of(file.toPath()), zipFile));
    }

    private InternalResult<String> extractRawText(Optional<Path> path, DocxFile zipFile) {
        return readDocument(path, zipFile)
            .map(DocumentConverter::extractRawTextOfChildren);
    }

    private static <T> T withDocxFile(File file, Function<DocxFile, T> function) throws IOException {
        try (DocxFile zipFile = new ZippedDocxFile(new ZipFile(file))) {
            return function.apply(zipFile);
        }
    }

    private static <T> T withDocxFile(InputStream stream, Function<DocxFile, T> function) throws IOException {
        try (DocxFile zipFile = InMemoryDocxFile.fromStream(stream)) {
            return function.apply(zipFile);
        }
    }

    private static String extractRawTextOfChildren(HasChildren parent) {
        return extractRawText(parent.getChildren());
    }

    private static String extractRawText(List<DocumentElement> nodes) {
        return String.join("", Iterables.transform(nodes, node -> extractRawText(node)));
    }

    private static String extractRawText(DocumentElement node) {
        return Casts.tryCast(Text.class, node)
            .map(Text::getValue)
            .orElseGet(() -> {
                List<DocumentElement> children = Casts.tryCast(HasChildren.class, node)
                    .map(HasChildren::getChildren)
                    .orElse(list());
                String suffix = node instanceof Paragraph ? "\n\n" : "";
                return extractRawText(children) + suffix;
            });
    }
}