aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2024-02-17 14:18:13 +0000
committerMichael Williamson <mike@zwobble.org>2024-02-17 14:18:13 +0000
commit9bb9e645808bb8adcd3179bb688c3d70755a62fd (patch)
tree14b93431e6f41d75d0edcbf6c1f8eb8e9da137e9
parentaf2f17fafbfd632ebd107ecb544b6ec7dea2a665 (diff)
Support attributes in HTML paths in style mappings
-rw-r--r--NEWS4
-rw-r--r--README.md6
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/styles/parsing/HtmlPathParser.java74
-rw-r--r--src/test/java/org/zwobble/mammoth/tests/styles/parsing/HtmlPathParsingTests.java14
4 files changed, 83 insertions, 15 deletions
diff --git a/NEWS b/NEWS
index 3c24378..9977f85 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,7 @@
+# 1.7.0
+
+* Support attributes in HTML paths in style mappings.
+
# 1.6.0
* Support merged paragraphs when revisions are tracked.
diff --git a/README.md b/README.md
index 7097a1b..05d8c0e 100644
--- a/README.md
+++ b/README.md
@@ -487,6 +487,12 @@ append a dot followed by the name of the class:
h1.section-title
```
+To add an attribute, use square brackets similarly to a CSS attribute selector:
+
+```
+p[lang='fr']
+```
+
To require that an element is fresh, use `:fresh`:
```
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<TokenType> 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<TokenType> tokens) {
List<HtmlPathElement> elements = new ArrayList<>();
@@ -41,10 +50,20 @@ public class HtmlPathParser {
private static HtmlPathElement parseElement(TokenIterator<TokenType> tokens) {
List<String> tagNames = parseTagNames(tokens);
- List<String> classNames = parseClassNames(tokens);
- Map<String, String> attributes = classNames.isEmpty()
- ? map()
- : map("class", String.join(" ", classNames));
+
+ List<Attribute> attributeList = parseAttributeOrClassNames(tokens);
+ Map<String, String> 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<String> parseClassNames(TokenIterator<TokenType> tokens) {
- List<String> classNames = new ArrayList<>();
+ private static List<Attribute> parseAttributeOrClassNames(TokenIterator<TokenType> tokens) {
+ List<Attribute> attributes = new ArrayList<>();
while (true) {
- Optional<String> className = TokenParser.parseClassName(tokens);
- if (className.isPresent()) {
- classNames.add(className.get());
+ Optional<Attribute> attribute = parseAttributeOrClassName(tokens);
+ if (attribute.isPresent()) {
+ attributes.add(attribute.get());
} else {
- return classNames;
+ return attributes;
}
}
}
+ private static Optional<Attribute> parseAttributeOrClassName(TokenIterator<TokenType> 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<TokenType> 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<TokenType> tokens) {
+ tokens.skip(TokenType.SYMBOL, ".");
+ String className = parseIdentifier(tokens);
+ return new Attribute("class", className, true);
+ }
+
private static boolean parseIsFresh(TokenIterator<TokenType> 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
@@ -61,6 +61,20 @@ public class HtmlPathParsingTests {
}
@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(
parseHtmlPath("p:fresh"),