diff options
| author | Michael Williamson <mike@zwobble.org> | 2016-02-24 08:16:16 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2016-02-24 08:16:16 +0000 |
| commit | 3e2786a004eddf968a5fa7d5a92dff5b1b5f7734 (patch) | |
| tree | 7fa1bcc1e484cd0ef782f64baf504ee83333d397 /src/main/java/org/zwobble/mammoth/internal/styles | |
| parent | 0dc3a12affc2e46c1a0df2899cf51a1ae3fdbd61 (diff) | |
Create internal package
Diffstat (limited to 'src/main/java/org/zwobble/mammoth/internal/styles')
13 files changed, 584 insertions, 0 deletions
diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/DocumentElementMatcher.java b/src/main/java/org/zwobble/mammoth/internal/styles/DocumentElementMatcher.java new file mode 100644 index 0000000..697926c --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/DocumentElementMatcher.java @@ -0,0 +1,5 @@ +package org.zwobble.mammoth.internal.styles; + +public interface DocumentElementMatcher<T> { + boolean matches(T element); +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/DocumentElementMatching.java b/src/main/java/org/zwobble/mammoth/internal/styles/DocumentElementMatching.java new file mode 100644 index 0000000..a0c5aec --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/DocumentElementMatching.java @@ -0,0 +1,26 @@ +package org.zwobble.mammoth.internal.styles; + +import org.zwobble.mammoth.internal.documents.Style; + +import java.util.Optional; +import java.util.function.BiPredicate; + +class DocumentElementMatching { + static boolean matchesStyle(Optional<String> styleId, Optional<String> styleName, Optional<Style> style) { + return matchesStyleId(styleId, style) && matchesStyleName(styleName, style); + } + + private static boolean matchesStyleId(Optional<String> styleId, Optional<Style> style) { + return matches(styleId, style.map(Style::getStyleId), Object::equals); + } + + private static boolean matchesStyleName(Optional<String> styleName, Optional<Style> style) { + return matches(styleName, style.flatMap(Style::getName), String::equalsIgnoreCase); + } + + static <T> boolean matches(Optional<T> required, Optional<T> actual, BiPredicate<T, T> areEqual) { + return required + .map(requiredValue -> actual.map(actualValue -> areEqual.test(requiredValue, actualValue)).orElse(false)) + .orElse(true); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/HtmlPath.java b/src/main/java/org/zwobble/mammoth/internal/styles/HtmlPath.java new file mode 100644 index 0000000..a20043a --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/HtmlPath.java @@ -0,0 +1,51 @@ +package org.zwobble.mammoth.internal.styles; + +import com.google.common.collect.Lists; +import org.zwobble.mammoth.internal.html.HtmlNode; + +import java.util.List; +import java.util.Map; + +import static org.zwobble.mammoth.internal.util.MammothLists.list; +import static org.zwobble.mammoth.internal.util.MammothMaps.map; + +public class HtmlPath { + public static final HtmlPath EMPTY = new HtmlPath(list()); + + public static HtmlPath element(String tagName) { + return element(tagName, map()); + } + + public static HtmlPath element(String tagName, Map<String, String> attributes) { + return new HtmlPath(list(new HtmlPathElement(list(tagName), attributes, false))); + } + + public static HtmlPath collapsibleElement(String tagName) { + return collapsibleElement(tagName, map()); + } + + public static HtmlPath collapsibleElement(List<String> tagNames) { + return collapsibleElement(tagNames, map()); + } + + public static HtmlPath collapsibleElement(String tagName, Map<String, String> attributes) { + return collapsibleElement(list(tagName), attributes); + } + + public static HtmlPath collapsibleElement(List<String> tagNames, Map<String, String> attributes) { + return new HtmlPath(list(new HtmlPathElement(tagNames, attributes, true))); + } + + private final List<HtmlPathElement> elements; + + public HtmlPath(List<HtmlPathElement> elements) { + this.elements = elements; + } + + public List<HtmlNode> wrap(List<HtmlNode> nodes) { + for (HtmlPathElement element : Lists.reverse(elements)) { + nodes = element.wrap(nodes); + } + return nodes; + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/HtmlPathElement.java b/src/main/java/org/zwobble/mammoth/internal/styles/HtmlPathElement.java new file mode 100644 index 0000000..7f110a4 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/HtmlPathElement.java @@ -0,0 +1,38 @@ +package org.zwobble.mammoth.internal.styles; + +import org.zwobble.mammoth.internal.html.HtmlElement; +import org.zwobble.mammoth.internal.html.HtmlNode; + +import java.util.List; +import java.util.Map; + +import static org.zwobble.mammoth.internal.util.MammothLists.list; +import static org.zwobble.mammoth.internal.util.MammothMaps.map; + +public class HtmlPathElement { + public static HtmlPathElement fresh(String tagName) { + return new HtmlPathElement(list(tagName), map(), false); + } + + public static HtmlPathElement collapsible(String tagName) { + return collapsible(tagName, map()); + } + + public static HtmlPathElement collapsible(String tagName, Map<String, String> attributes) { + return new HtmlPathElement(list(tagName), attributes, true); + } + + private final List<String> tagNames; + private final Map<String, String> attributes; + private final boolean isCollapsible; + + public HtmlPathElement(List<String> tagNames, Map<String, String> attributes, boolean isCollapsible) { + this.tagNames = tagNames; + this.attributes = attributes; + this.isCollapsible = isCollapsible; + } + + public List<HtmlNode> wrap(List<HtmlNode> nodes) { + return list(new HtmlElement(tagNames, attributes, nodes, isCollapsible)); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/ParagraphMatcher.java b/src/main/java/org/zwobble/mammoth/internal/styles/ParagraphMatcher.java new file mode 100644 index 0000000..50d5df9 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/ParagraphMatcher.java @@ -0,0 +1,50 @@ +package org.zwobble.mammoth.internal.styles; + +import org.zwobble.mammoth.internal.documents.NumberingLevel; +import org.zwobble.mammoth.internal.documents.Paragraph; + +import java.util.Optional; + +public class ParagraphMatcher implements DocumentElementMatcher<Paragraph> { + public static final ParagraphMatcher ANY = new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.empty()); + + public static ParagraphMatcher styleId(String styleId) { + return new ParagraphMatcher(Optional.of(styleId), Optional.empty(), Optional.empty()); + } + + public static ParagraphMatcher styleName(String styleName) { + return new ParagraphMatcher(Optional.empty(), Optional.of(styleName), Optional.empty()); + } + + public static ParagraphMatcher orderedList(String level) { + return new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.of(NumberingLevel.ordered(level))); + } + + public static ParagraphMatcher unorderedList(String level) { + return new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.of(NumberingLevel.unordered(level))); + } + + private final Optional<String> styleId; + private final Optional<String> styleName; + private final Optional<NumberingLevel> numbering; + + public ParagraphMatcher(Optional<String> styleId, Optional<String> styleName, Optional<NumberingLevel> numbering) { + this.styleId = styleId; + this.styleName = styleName; + this.numbering = numbering; + } + + @Override + public boolean matches(Paragraph paragraph) { + return matchesStyle(paragraph) && matchesNumbering(paragraph); + } + + private boolean matchesStyle(Paragraph paragraph) { + return DocumentElementMatching.matchesStyle(styleId, styleName, paragraph.getStyle()); + } + + private boolean matchesNumbering(Paragraph paragraph) { + return DocumentElementMatching.matches(numbering, paragraph.getNumbering(), (first, second) -> + first.isOrdered() == second.isOrdered() && first.getLevelIndex().equalsIgnoreCase(second.getLevelIndex())); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/RunMatcher.java b/src/main/java/org/zwobble/mammoth/internal/styles/RunMatcher.java new file mode 100644 index 0000000..93b50dd --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/RunMatcher.java @@ -0,0 +1,30 @@ +package org.zwobble.mammoth.internal.styles; + +import org.zwobble.mammoth.internal.documents.Run; + +import java.util.Optional; + +public class RunMatcher implements DocumentElementMatcher<Run> { + public static final RunMatcher ANY = new RunMatcher(Optional.empty(), Optional.empty()); + + public static RunMatcher styleId(String styleId) { + return new RunMatcher(Optional.of(styleId), Optional.empty()); + } + + public static RunMatcher styleName(String styleName) { + return new RunMatcher(Optional.empty(), Optional.of(styleName)); + } + + private final Optional<String> styleId; + private final Optional<String> styleName; + + public RunMatcher(Optional<String> styleId, Optional<String> styleName) { + this.styleId = styleId; + this.styleName = styleName; + } + + @Override + public boolean matches(Run run) { + return DocumentElementMatching.matchesStyle(styleId, styleName, run.getStyle()); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/StyleMap.java b/src/main/java/org/zwobble/mammoth/internal/styles/StyleMap.java new file mode 100644 index 0000000..7d28bf1 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/StyleMap.java @@ -0,0 +1,57 @@ +package org.zwobble.mammoth.internal.styles; + +import com.google.common.collect.Iterables; +import org.zwobble.mammoth.internal.documents.Paragraph; +import org.zwobble.mammoth.internal.documents.Run; + +import java.util.List; +import java.util.Optional; + +public class StyleMap { + public static StyleMapBuilder builder() { + return new StyleMapBuilder(); + } + + public static final StyleMap EMPTY = new StyleMapBuilder().build(); + + private final HtmlPath underline; + private final HtmlPath strikethrough; + private final List<StyleMapping<Paragraph>> paragraphStyles; + private final List<StyleMapping<Run>> runStyles; + + public StyleMap( + HtmlPath underline, + HtmlPath strikethrough, + List<StyleMapping<Paragraph>> paragraphStyles, + List<StyleMapping<Run>> runStyles) + { + this.underline = underline; + this.strikethrough = strikethrough; + this.paragraphStyles = paragraphStyles; + this.runStyles = runStyles; + } + + public HtmlPath getUnderline() { + return underline; + } + + public HtmlPath getStrikethrough() { + return strikethrough; + } + + public Optional<HtmlPath> getParagraphHtmlPath(Paragraph paragraph) { + com.google.common.base.Optional<StyleMapping<Paragraph>> mapping = Iterables.tryFind( + paragraphStyles, + styleMapping -> styleMapping.matches(paragraph)); + return Optional.ofNullable(mapping.orNull()) + .map(StyleMapping::getHtmlPath); + } + + public Optional<HtmlPath> getRunHtmlPath(Run run) { + com.google.common.base.Optional<StyleMapping<Run>> mapping = Iterables.tryFind( + runStyles, + styleMapping -> styleMapping.matches(run)); + return Optional.ofNullable(mapping.orNull()) + .map(StyleMapping::getHtmlPath); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/StyleMapBuilder.java b/src/main/java/org/zwobble/mammoth/internal/styles/StyleMapBuilder.java new file mode 100644 index 0000000..6300d29 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/StyleMapBuilder.java @@ -0,0 +1,41 @@ +package org.zwobble.mammoth.internal.styles; + +import com.google.common.collect.ImmutableList; +import org.zwobble.mammoth.internal.documents.Paragraph; +import org.zwobble.mammoth.internal.documents.Run; + +public class StyleMapBuilder { + private HtmlPath underline; + private HtmlPath strikethrough; + private final ImmutableList.Builder<StyleMapping<Paragraph>> paragraphStyles = ImmutableList.builder(); + private final ImmutableList.Builder<StyleMapping<Run>> runStyles = ImmutableList.builder(); + + public StyleMapBuilder() { + this.underline = HtmlPath.EMPTY; + this.strikethrough = HtmlPath.collapsibleElement("s"); + } + + public StyleMapBuilder underline(HtmlPath path) { + this.underline = path; + return this; + } + + public StyleMapBuilder strikethrough(HtmlPath path) { + this.strikethrough = path; + return this; + } + + public StyleMapBuilder mapParagraph(ParagraphMatcher matcher, HtmlPath path) { + paragraphStyles.add(new StyleMapping<Paragraph>(matcher, path)); + return this; + } + + public StyleMapBuilder mapRun(RunMatcher matcher, HtmlPath path) { + runStyles.add(new StyleMapping<Run>(matcher, path)); + return this; + } + + public StyleMap build() { + return new StyleMap(underline, strikethrough, paragraphStyles.build(), runStyles.build()); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/StyleMapping.java b/src/main/java/org/zwobble/mammoth/internal/styles/StyleMapping.java new file mode 100644 index 0000000..5512c22 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/StyleMapping.java @@ -0,0 +1,19 @@ +package org.zwobble.mammoth.internal.styles; + +public class StyleMapping<T> { + private final DocumentElementMatcher<T> matcher; + private final HtmlPath htmlPath; + + public StyleMapping(DocumentElementMatcher<T> matcher, HtmlPath htmlPath) { + this.matcher = matcher; + this.htmlPath = htmlPath; + } + + public boolean matches(T element) { + return matcher.matches(element); + } + + public HtmlPath getHtmlPath() { + return htmlPath; + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/ListVar.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/ListVar.java new file mode 100644 index 0000000..74b3dbc --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/ListVar.java @@ -0,0 +1,34 @@ +package org.zwobble.mammoth.internal.styles.parsing; + + +import com.google.common.collect.ImmutableList; +import org.parboiled.support.Var; + +import java.util.List; + +import static org.zwobble.mammoth.internal.util.MammothLists.list; + +class ListVar<T> extends Var<ImmutableList.Builder<T>> { + boolean append(Var<T> element) { + return append(element.get()); + } + + boolean append(T element) { + if (get() == null) { + set(ImmutableList.builder()); + } + return set(get().add(element)); + } + + public List<T> build() { + if (get() == null) { + return list(); + } else { + return get().build(); + } + } + + public boolean isEmpty() { + return get() == null; + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Parsing.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Parsing.java new file mode 100644 index 0000000..bef3c0b --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/Parsing.java @@ -0,0 +1,21 @@ +package org.zwobble.mammoth.internal.styles.parsing; + +import org.parboiled.BaseParser; +import org.parboiled.Parboiled; +import org.parboiled.Rule; +import org.parboiled.parserunners.ReportingParseRunner; +import org.parboiled.support.ParsingResult; + +import java.util.function.Function; + +public class Parsing { + public static <T, U extends BaseParser<T>, V> void parse(Class<U> parserType, Function<U, Rule> rule, String value) { + U parser = Parboiled.createParser(parserType); + ReportingParseRunner<T> runner = new ReportingParseRunner<>(rule.apply(parser)); + ParsingResult<T> result = runner.run(value); + if (result.hasErrors()) { + // TODO: wrap in result + throw new RuntimeException("Parse error"); + } + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java new file mode 100644 index 0000000..b43e300 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMapParser.java @@ -0,0 +1,34 @@ +package org.zwobble.mammoth.internal.styles.parsing; + +import org.parboiled.support.Var; +import org.zwobble.mammoth.internal.styles.StyleMap; +import org.zwobble.mammoth.internal.styles.StyleMapBuilder; + +import java.util.List; + +import static java.util.Arrays.asList; + +public class StyleMapParser { + public static StyleMap parse(String input) { + return parse(asList(input.split("\\r?\\n"))); + } + + public static StyleMap parse(List<String> lines) { + Var<StyleMapBuilder> styleMap = new Var<>(StyleMap.builder()); + for (String line : lines) { + handleLine(styleMap, line); + } + return styleMap.get().build(); + } + + private static void handleLine(Var<StyleMapBuilder> styleMap, String line) { + if (line.startsWith("#")) { + return; + } + line = line.trim(); + if (line.isEmpty()) { + return; + } + Parsing.parse(StyleMappingParser.class, parser -> parser.StyleMapping(styleMap), line); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingParser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingParser.java new file mode 100644 index 0000000..5c1500c --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/StyleMappingParser.java @@ -0,0 +1,178 @@ +package org.zwobble.mammoth.internal.styles.parsing; + +import org.parboiled.Action; +import org.parboiled.BaseParser; +import org.parboiled.Context; +import org.parboiled.Rule; +import org.parboiled.support.Var; +import org.zwobble.mammoth.internal.documents.NumberingLevel; +import org.zwobble.mammoth.internal.styles.*; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.function.BiFunction; + +import static org.zwobble.mammoth.internal.util.MammothMaps.map; + +public class StyleMappingParser extends BaseParser<StyleMapBuilder> { + Rule StyleMapping(Var<StyleMapBuilder> styleMap) { + Var<BiFunction<StyleMapBuilder, HtmlPath, StyleMapBuilder>> documentElementMatcher = new Var<>(); + Var<HtmlPath> path = new Var<>(); + return Sequence( + DocumentElementMatcher(documentElementMatcher), Whitespace(), "=>", Whitespace(), HtmlPath(path), EOI, + styleMap.set(documentElementMatcher.get().apply(styleMap.get(), path.get()))); + } + + Rule DocumentElementMatcher(Var<BiFunction<StyleMapBuilder, HtmlPath, StyleMapBuilder>> updateBuilder) { + Var<ParagraphMatcher> paragraphMatcher = new Var<>(); + Var<RunMatcher> runMatcher = new Var<>(); + return FirstOf( + Sequence( + ParagraphMatcher(paragraphMatcher), + new Action() { + @Override + public boolean run(Context context) { + updateBuilder.set((styleMap, htmlPath) -> styleMap.mapParagraph(paragraphMatcher.get(), htmlPath)); + return true; + } + }), + Sequence( + RunMatcher(runMatcher), + new Action() { + @Override + public boolean run(Context context) { + updateBuilder.set((styleMap, htmlPath) -> styleMap.mapRun(runMatcher.get(), htmlPath)); + return true; + } + }), + Sequence( + "u", + new Action() { + @Override + public boolean run(Context context) { + updateBuilder.set(StyleMapBuilder::underline); + return true; + } + }), + Sequence( + "s", + new Action() { + @Override + public boolean run(Context context) { + updateBuilder.set(StyleMapBuilder::strikethrough); + return true; + } + })); + } + + public Rule ParagraphMatcher(Var<ParagraphMatcher> matcher) { + Var<Optional<String>> styleId = new Var<>(); + Var<Optional<String>> styleName = new Var<>(); + Var<Optional<NumberingLevel>> numbering = new Var<>(); + return Sequence( + "p", StyleId(styleId), StyleName(styleName), Numbering(numbering), + matcher.set(new ParagraphMatcher(styleId.get(), styleName.get(), numbering.get()))); + } + + public Rule RunMatcher(Var<RunMatcher> matcher) { + Var<Optional<String>> styleId = new Var<>(); + Var<Optional<String>> styleName = new Var<>(); + return Sequence( + "r", StyleId(styleId), StyleName(styleName), + matcher.set(new RunMatcher(styleId.get(), styleName.get()))); + } + + Rule StyleId(Var<Optional<String>> styleId) { + return Sequence(styleId.set(Optional.empty()), Optional(".", Identifier(), styleId.set(Optional.of(match())))); + } + + Rule StyleName(Var<Optional<String>> styleName) { + Var<String> value = new Var<>(); + return Sequence( + styleName.set(Optional.empty()), + Optional("[style-name=", SingleQuotedString(value), "]", styleName.set(Optional.of(value.get())))); + } + + Rule Numbering(Var<Optional<NumberingLevel>> numbering) { + Var<Boolean> isOrdered = new Var<>(); + Var<BigInteger> level = new Var<>(); + return Sequence( + numbering.set(Optional.empty()), + Optional(Sequence( + ":", NumberingType(isOrdered), "(", Number(level), ")", + numbering.set(Optional.of(new NumberingLevel(level.get().subtract(BigInteger.ONE).toString(), isOrdered.get())))))); + } + + Rule NumberingType(Var<Boolean> isOrdered) { + return FirstOf( + Sequence("ordered-list", isOrdered.set(true)), + Sequence("unordered-list", isOrdered.set(false))); + } + + Rule Number(Var<BigInteger> level) { + return Sequence( + OneOrMore(CharRange('0', '9')), + level.set(new BigInteger(match()))); + } + + public Rule HtmlPath(Var<HtmlPath> htmlPath) { + ListVar<HtmlPathElement> elements = new ListVar<>(); + return Sequence(Optional(HtmlPathElements(elements)), htmlPath.set(new HtmlPath(elements.build()))); + } + + Rule HtmlPathElements(ListVar<HtmlPathElement> elements) { + Var<HtmlPathElement> element = new Var<>(); + return Sequence( + HtmlPathElement(element), + elements.append(element), + ZeroOrMore(Sequence(Whitespace(), Ch('>'), Whitespace(), HtmlPathElement(element), elements.append(element)))); + } + + Rule HtmlPathElement(Var<HtmlPathElement> element) { + ListVar<String> tagNames = new ListVar<>(); + ListVar<String> classNames = new ListVar<>(); + Var<Boolean> fresh = new Var<>(); + return Sequence( + TagNames(tagNames), + ClassNames(classNames), + Fresh(fresh), + element.set(new HtmlPathElement( + tagNames.build(), + classNames.isEmpty() ? map() : map("class", String.join(" ", classNames.build())), + !fresh.get()))); + } + + Rule TagNames(ListVar<String> tagNames) { + return Sequence( + Identifier(), + tagNames.append(match()), + ZeroOrMore(Sequence("|", Identifier(), tagNames.append(match())))); + } + + Rule ClassNames(ListVar<String> classNames) { + Var<String> className = new Var<>(); + return ZeroOrMore(Sequence(ClassName(className), classNames.append(className))); + } + + Rule ClassName(Var<String> className) { + return Sequence(".", Identifier(), className.set(match())); + } + + Rule Fresh(Var<Boolean> fresh) { + return Sequence(fresh.set(false), Optional(":fresh", fresh.set(true))); + } + + Rule SingleQuotedString(Var<String> value) { + return Sequence("'", ZeroOrMore(NoneOf("\'")), value.set(match()), "'"); + } + + Rule Identifier() { + return Sequence( + FirstOf(CharRange('a', 'z'), CharRange('A', 'Z')), + ZeroOrMore(FirstOf(CharRange('a', 'z'), CharRange('A', 'Z'), CharRange('0', '9'), Ch('-'), Ch('_')))); + } + + Rule Whitespace() { + return ZeroOrMore(AnyOf(" \t")); + } +} |
