From 9bb9e645808bb8adcd3179bb688c3d70755a62fd Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sat, 17 Feb 2024 14:18:13 +0000 Subject: Support attributes in HTML paths in style mappings --- .../internal/styles/parsing/HtmlPathParser.java | 74 +++++++++++++++++----- .../tests/styles/parsing/HtmlPathParsingTests.java | 14 ++++ 2 files changed, 73 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java index a52ed64..ec615f8 100644 --- a/src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java +++ b/src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java @@ -5,13 +5,10 @@ import org.zwobble.mammoth.internal.styles.HtmlPath; import org.zwobble.mammoth.internal.styles.HtmlPathElement; import org.zwobble.mammoth.internal.styles.HtmlPathElements; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import static org.zwobble.mammoth.internal.styles.parsing.TokenParser.parseIdentifier; -import static org.zwobble.mammoth.internal.util.Maps.map; +import static org.zwobble.mammoth.internal.styles.parsing.TokenParser.parseString; public class HtmlPathParser { public static HtmlPath parse(TokenIterator tokens) { @@ -22,6 +19,18 @@ public class HtmlPathParser { } } + private static class Attribute { + private final String name; + private final String value; + private final boolean append; + + private Attribute(String name, String value, boolean append) { + this.name = name; + this.value = value; + this.append = append; + } + } + private static HtmlPath parseHtmlPathElements(TokenIterator tokens) { List elements = new ArrayList<>(); @@ -41,10 +50,20 @@ public class HtmlPathParser { private static HtmlPathElement parseElement(TokenIterator tokens) { List tagNames = parseTagNames(tokens); - List classNames = parseClassNames(tokens); - Map attributes = classNames.isEmpty() - ? map() - : map("class", String.join(" ", classNames)); + + List attributeList = parseAttributeOrClassNames(tokens); + Map attributes = new HashMap<>(); + for (Attribute attribute : attributeList) { + if (attribute.append && attributes.containsKey(attribute.name)) { + attributes.put( + attribute.name, + attributes.get(attribute.name) + " " + attribute.value + ); + } else { + attributes.put(attribute.name, attribute.value); + } + } + boolean isFresh = parseIsFresh(tokens); String separator = parseSeparator(tokens); return new HtmlPathElement(new HtmlTag(tagNames, attributes, !isFresh, separator)); @@ -59,18 +78,43 @@ public class HtmlPathParser { return tagNames; } - private static List parseClassNames(TokenIterator tokens) { - List classNames = new ArrayList<>(); + private static List parseAttributeOrClassNames(TokenIterator tokens) { + List attributes = new ArrayList<>(); while (true) { - Optional className = TokenParser.parseClassName(tokens); - if (className.isPresent()) { - classNames.add(className.get()); + Optional attribute = parseAttributeOrClassName(tokens); + if (attribute.isPresent()) { + attributes.add(attribute.get()); } else { - return classNames; + return attributes; } } } + private static Optional parseAttributeOrClassName(TokenIterator tokens) { + if (tokens.isNext(TokenType.SYMBOL, "[")) { + return Optional.of(parseAttribute(tokens)); + } else if (tokens.isNext(TokenType.SYMBOL, ".")) { + return Optional.of(parseClassName(tokens)); + } else { + return Optional.empty(); + } + } + + private static Attribute parseAttribute(TokenIterator tokens) { + tokens.skip(TokenType.SYMBOL, "["); + String name = parseIdentifier(tokens); + tokens.skip(TokenType.SYMBOL, "="); + String value = parseString(tokens); + tokens.skip(TokenType.SYMBOL, "]"); + return new Attribute(name, value, true); + } + + private static Attribute parseClassName(TokenIterator tokens) { + tokens.skip(TokenType.SYMBOL, "."); + String className = parseIdentifier(tokens); + return new Attribute("class", className, true); + } + private static boolean parseIsFresh(TokenIterator tokens) { return tokens.tryParse(() -> { tokens.skip(TokenType.SYMBOL, ":"); diff --git a/src/test/java/org/zwobble/mammoth/tests/styles/parsing/HtmlPathParsingTests.java b/src/test/java/org/zwobble/mammoth/tests/styles/parsing/HtmlPathParsingTests.java index 23cc730..85591ad 100644 --- a/src/test/java/org/zwobble/mammoth/tests/styles/parsing/HtmlPathParsingTests.java +++ b/src/test/java/org/zwobble/mammoth/tests/styles/parsing/HtmlPathParsingTests.java @@ -60,6 +60,20 @@ public class HtmlPathParsingTests { deepEquals(HtmlPath.collapsibleElement("p", map("class", "tip help")))); } + @Test + public void canReadAttributeOnElement() { + assertThat( + parseHtmlPath("p[lang='fr']"), + deepEquals(HtmlPath.collapsibleElement("p", map("lang", "fr")))); + } + + @Test + public void canReadMultipleAttributesOnElement() { + assertThat( + parseHtmlPath("p[lang='fr'][data-x='y']"), + deepEquals(HtmlPath.collapsibleElement("p", map("lang", "fr", "data-x", "y")))); + } + @Test public void canReadWhenElementMustBeFresh() { assertThat( -- cgit v1.2.3