package org.zwobble.mammoth.internal.styles.parsing; import org.zwobble.mammoth.internal.html.HtmlTag; import org.zwobble.mammoth.internal.styles.HtmlPath; import org.zwobble.mammoth.internal.styles.HtmlPathElement; import org.zwobble.mammoth.internal.styles.HtmlPathElements; import java.util.*; import static org.zwobble.mammoth.internal.styles.parsing.TokenParser.parseIdentifier; import static org.zwobble.mammoth.internal.styles.parsing.TokenParser.parseString; import static org.zwobble.mammoth.internal.util.Maps.lookup; public class HtmlPathParser { public static HtmlPath parse(TokenIterator tokens) { if (tokens.trySkip(TokenType.SYMBOL, "!")) { return HtmlPath.IGNORE; } else { return parseHtmlPathElements(tokens); } } 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<>(); if (tokens.peekTokenType() == TokenType.IDENTIFIER) { HtmlPathElement element = parseElement(tokens); elements.add(element); while (tokens.peekTokenType() == TokenType.WHITESPACE && tokens.isNext(1, TokenType.SYMBOL, ">")) { tokens.skip(TokenType.WHITESPACE); tokens.skip(TokenType.SYMBOL, ">"); tokens.skip(TokenType.WHITESPACE); elements.add(parseElement(tokens)); } } return new HtmlPathElements(elements); } private static HtmlPathElement parseElement(TokenIterator tokens) { List tagNames = parseTagNames(tokens); List attributeList = parseAttributeOrClassNames(tokens); Map attributes = new HashMap<>(); for (Attribute attribute : attributeList) { if (attribute.append && attributes.containsKey(attribute.name)) { attributes.put( attribute.name, lookup(attributes, attribute.name).get() + " " + 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)); } private static List parseTagNames(TokenIterator tokens) { List tagNames = new ArrayList<>(); tagNames.add(parseIdentifier(tokens)); while (tokens.trySkip(TokenType.SYMBOL, "|")) { tagNames.add(parseIdentifier(tokens)); } return tagNames; } private static List parseAttributeOrClassNames(TokenIterator tokens) { List attributes = new ArrayList<>(); while (true) { Optional attribute = parseAttributeOrClassName(tokens); if (attribute.isPresent()) { attributes.add(attribute.get()); } else { 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, ":"); tokens.skip(TokenType.IDENTIFIER, "fresh"); }); } private static String parseSeparator(TokenIterator tokens) { boolean isSeparator = tokens.tryParse(() -> { tokens.skip(TokenType.SYMBOL, ":"); tokens.skip(TokenType.IDENTIFIER, "separator"); }); if (isSeparator) { tokens.skip(TokenType.SYMBOL, "("); String value = TokenParser.parseString(tokens); tokens.skip(TokenType.SYMBOL, ")"); return value; } else { return ""; } } }