Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for text alignment in paragraphs #43

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,20 @@ Modifiers must be used in the correct order:
h1.section-title:fresh
```

#### Text Alignment

To convert text alignment, it is possible to match paragraphs with text-align attribute.

```
p[text-align='center'] => p.center:fresh
p[text-align='right'] => p.right:fresh
p[text-align='justify'] => p.justify:fresh
p[style-name='Heading 1', text-align='center'] => h1.center:fresh
p[style-name='Heading 1', text-align='right'] => h1.right:fresh
```

Note: Order is important. Last selector wins!

#### Separators

To specify a separator to place between the contents of paragraphs that are collapsed together,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.zwobble.mammoth.internal.documents;

public class Alignment {
private final String value;

public Alignment(String value){
this.value = value;
}

public String getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@

public class Paragraph implements DocumentElement, HasChildren {
private final Optional<Style> style;
private final Optional<Alignment> alignment;
private final Optional<NumberingLevel> numbering;
private final ParagraphIndent indent;
private final List<DocumentElement> children;

public Paragraph(
Optional<Style> style,
Optional<Alignment> alignment,
Optional<NumberingLevel> numbering,
ParagraphIndent indent,
List<DocumentElement> children
) {
this.style = style;
this.alignment = alignment;
this.numbering = numbering;
this.indent = indent;
this.children = children;
Expand All @@ -25,6 +28,10 @@ public Optional<Style> getStyle() {
return style;
}

public Optional<Alignment> getAlignment() {
return alignment;
}

public Optional<NumberingLevel> getNumbering() {
return numbering;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ private ReadResult readParagraph(XmlElement element) {
return ReadResult.success(list());
} else {
ParagraphIndent indent = readParagraphIndent(properties);
Optional<Alignment> alignment = readParagraphAlignment(properties);
List<XmlNode> childrenXml = element.getChildren();
if (!deletedParagraphContents.isEmpty()) {
childrenXml = eagerConcat(deletedParagraphContents, childrenXml);
Expand All @@ -277,7 +278,7 @@ private ReadResult readParagraph(XmlElement element) {
return ReadResult.map(
readParagraphStyle(properties),
readElements(childrenXml),
(style, children) -> new Paragraph(style, readNumbering(style, properties), indent, children)).appendExtra();
(style, children) -> new Paragraph(style, alignment, readNumbering(style, properties), indent, children)).appendExtra();
}
}

Expand Down Expand Up @@ -388,6 +389,21 @@ private ParagraphIndent readParagraphIndent(XmlElementLike properties) {
);
}

private Optional<Alignment> readParagraphAlignment(XmlElementLike properties) {
String align = properties
.findChild("w:jc")
.map(jc -> jc.getAttributeOrNone("w:val").orElse(""))
.orElse("");

switch (align) {
case "left": return Optional.of(new Alignment("left"));
case "center": return Optional.of(new Alignment("center"));
case "right": return Optional.of(new Alignment("right"));
case "both": return Optional.of(new Alignment("justify"));
default: return Optional.empty();
}
}

private ReadResult readSymbol(XmlElement element) {
Optional<String> font = element.getAttributeOrNone("w:font");
Optional<String> charValue = element.getAttributeOrNone("w:char");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,49 @@
package org.zwobble.mammoth.internal.styles;

import org.zwobble.mammoth.internal.documents.Alignment;
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 final ParagraphMatcher ANY = new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());

public static ParagraphMatcher styleId(String styleId) {
return new ParagraphMatcher(Optional.of(styleId), Optional.empty(), Optional.empty());
return new ParagraphMatcher(Optional.of(styleId), Optional.empty(), Optional.empty(), Optional.empty());
}

public static ParagraphMatcher styleName(String styleName) {
return styleName(new EqualToStringMatcher(styleName));
}

public static ParagraphMatcher styleName(StringMatcher styleName) {
return new ParagraphMatcher(Optional.empty(), Optional.of(styleName), Optional.empty());
return new ParagraphMatcher(Optional.empty(), Optional.of(styleName), Optional.empty(), Optional.empty());
}

public static ParagraphMatcher orderedList(String level) {
return new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.of(NumberingLevel.ordered(level)));
return new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.of(NumberingLevel.ordered(level)), Optional.empty());
}

public static ParagraphMatcher unorderedList(String level) {
return new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.of(NumberingLevel.unordered(level)));
return new ParagraphMatcher(Optional.empty(), Optional.empty(), Optional.of(NumberingLevel.unordered(level)), Optional.empty());
}

private final Optional<String> styleId;
private final Optional<StringMatcher> styleName;
private final Optional<NumberingLevel> numbering;
private final Optional<StringMatcher> textAlign;

public ParagraphMatcher(Optional<String> styleId, Optional<StringMatcher> styleName, Optional<NumberingLevel> numbering) {
public ParagraphMatcher(Optional<String> styleId, Optional<StringMatcher> styleName, Optional<NumberingLevel> numbering, Optional<StringMatcher> textAlign) {
this.styleId = styleId;
this.styleName = styleName;
this.numbering = numbering;
this.textAlign = textAlign;
}

@Override
public boolean matches(Paragraph paragraph) {
return matchesStyle(paragraph) && matchesNumbering(paragraph);
return matchesStyle(paragraph) && matchesNumbering(paragraph) && matchesTextAlign(paragraph);
}

private boolean matchesStyle(Paragraph paragraph) {
Expand All @@ -51,4 +54,9 @@ private boolean matchesNumbering(Paragraph paragraph) {
return DocumentElementMatching.matches(numbering, paragraph.getNumbering(), (first, second) ->
first.isOrdered() == second.isOrdered() && first.getLevelIndex().equalsIgnoreCase(second.getLevelIndex()));
}

private boolean matchesTextAlign(Paragraph paragraph) {
Optional<String> alignment = paragraph.getAlignment().map(x -> x.getValue());
return DocumentElementMatching.matches(textAlign, alignment, (first, second) -> first.matches(second));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
import org.zwobble.mammoth.internal.styles.*;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;

import static org.zwobble.mammoth.internal.styles.parsing.LineParseException.lineParseException;
import static org.zwobble.mammoth.internal.util.Lists.list;

public class DocumentMatcherParser {
public static BiConsumer<StyleMapBuilder, HtmlPath> parse(TokenIterator<TokenType> tokens) {
Expand Down Expand Up @@ -46,9 +51,11 @@ public static BiConsumer<StyleMapBuilder, HtmlPath> parse(TokenIterator<TokenTyp

private static ParagraphMatcher parseParagraphMatcher(TokenIterator<TokenType> tokens) {
Optional<String> styleId = parseStyleId(tokens);
Optional<StringMatcher> styleName = parseStyleName(tokens);
Function<String, Optional<StringMatcher>> options = parseOptions(tokens, list("style-name", "text-align"));
Optional<StringMatcher> styleName = options.apply("style-name");
Optional<StringMatcher> textAlign = options.apply("text-align");
Optional<NumberingLevel> numbering = parseNumbering(tokens);
return new ParagraphMatcher(styleId, styleName, numbering);
return new ParagraphMatcher(styleId, styleName, numbering, textAlign);
}

private static RunMatcher parseRunMatcher(TokenIterator<TokenType> tokens) {
Expand All @@ -67,6 +74,35 @@ private static Optional<String> parseStyleId(TokenIterator<TokenType> tokens) {
return TokenParser.parseClassName(tokens);
}

private static Function<String, Optional<StringMatcher>> parseOptions(TokenIterator<TokenType> tokens, List<String> properties) {
if (tokens.trySkip(TokenType.SYMBOL, "[")) {
Map<String, StringMatcher> options = new HashMap<String, StringMatcher>();
Function<String, Optional<StringMatcher>> result = property -> options.containsKey(property) ? Optional.of(options.get(property)) : Optional.empty();
while (tokens.peekTokenType() == TokenType.IDENTIFIER) {
String identifier = tokens.nextValue(TokenType.IDENTIFIER);
if (properties.contains(identifier)) {
StringMatcher stringMatcher = parseStringMatcher(tokens);
options.put(identifier, stringMatcher);
}
else {
throw lineParseException(tokens.next(), "Expected " + String.join(" or ", properties) + " but got token " + tokens.next().getValue());
}
if (tokens.peekTokenType() == TokenType.WHITESPACE) tokens.skip(TokenType.WHITESPACE);
if (tokens.isNext(TokenType.SYMBOL, ",")) tokens.skip(TokenType.SYMBOL, ",");
else {
// can not use break because couscous fails converting it.
tokens.skip(TokenType.SYMBOL, "]");
return result;
}
if (tokens.peekTokenType() == TokenType.WHITESPACE) tokens.skip(TokenType.WHITESPACE);
}
tokens.skip(TokenType.SYMBOL, "]");
return result;
} else {
return property -> Optional.empty();
}
}

private static Optional<StringMatcher> parseStyleName(TokenIterator<TokenType> tokens) {
if (tokens.trySkip(TokenType.SYMBOL, "[")) {
tokens.skip(TokenType.IDENTIFIER, "style-name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static List<Token<TokenType>> tokeniseToList(String line) {
TokenType.UNKNOWN,
list(
RegexTokeniser.rule(TokenType.IDENTIFIER, identifierCharacter + "(?:" + identifierCharacter + "|[0-9])*"),
RegexTokeniser.rule(TokenType.SYMBOL, ":|>|=>|\\^=|=|\\(|\\)|\\[|\\]|\\||!|\\."),
RegexTokeniser.rule(TokenType.SYMBOL, ":|>|=>|\\^=|=|\\(|\\)|\\[|\\]|\\||!|\\.|,"),
RegexTokeniser.rule(TokenType.WHITESPACE, "\\s+"),
RegexTokeniser.rule(TokenType.STRING, stringPrefix + "'"),
RegexTokeniser.rule(TokenType.UNTERMINATED_STRING, stringPrefix),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class DocumentElementMakers {
private static final ArgumentKey<Boolean> STRIKETHROUGH = new ArgumentKey<>("strikethrough");
private static final ArgumentKey<Boolean> ALL_CAPS = new ArgumentKey<>("allCaps");
private static final ArgumentKey<Boolean> SMALL_CAPS = new ArgumentKey<>("smallCaps");
private static final ArgumentKey<Optional<Alignment>> ALIGNMENT = new ArgumentKey<>("Alignment");
private static final ArgumentKey<VerticalAlignment> VERTICAL_ALIGNMENT = new ArgumentKey<>("verticalAlignment");
private static final ArgumentKey<List<DocumentElement>> CHILDREN = new ArgumentKey<>("children");
private static final ArgumentKey<Boolean> IS_HEADER = new ArgumentKey<>("isHeader");
Expand Down Expand Up @@ -55,6 +56,10 @@ public static Argument<Boolean> withStrikethrough(boolean strikethrough) {
return arg(STRIKETHROUGH, strikethrough);
}

public static Argument<Optional<Alignment>> withAlignment(Optional<Alignment> alignment) {
return arg(ALIGNMENT, alignment);
}

public static Argument<Boolean> withAllCaps(boolean allCaps) {
return arg(ALL_CAPS, allCaps);
}
Expand Down Expand Up @@ -100,6 +105,7 @@ public static Paragraph paragraph(Object... args) {
Arguments arguments = new Arguments(args);
return new Paragraph(
arguments.get(STYLE, Optional.empty()),
arguments.get(ALIGNMENT, Optional.empty()),
arguments.get(NUMBERING, Optional.empty()),
new ParagraphIndent(
Optional.empty(),
Expand Down