package org.zwobble.mammoth.styles; import com.google.common.collect.ImmutableList; import org.parboiled.BaseParser; import org.parboiled.Parboiled; import org.parboiled.Rule; import org.parboiled.parserunners.ReportingParseRunner; import org.parboiled.support.ParsingResult; import org.parboiled.support.Var; import java.util.List; import static org.zwobble.mammoth.util.MammothLists.list; import static org.zwobble.mammoth.util.MammothMaps.map; public class StyleMapParser extends BaseParser { public static HtmlPath parseHtmlPath(String value) { StyleMapParser parser = Parboiled.createParser(StyleMapParser.class); ReportingParseRunner runner = new ReportingParseRunner<>(parser.HtmlPath()); ParsingResult result = runner.run(value); if (result.hasErrors()) { // TODO: wrap in result throw new RuntimeException("Parse error"); } else { return result.valueStack.peek(); } } Rule HtmlPath() { ListVar elements = new ListVar<>(); return Sequence(Optional(HtmlPathElements(elements)), EOI, push(new HtmlPath(elements.build()))); } Rule HtmlPathElements(ListVar elements) { Var element = new Var<>(); return Sequence( HtmlPathElement(element), elements.append(element), ZeroOrMore(Sequence(Whitespace(), Ch('>'), Whitespace(), HtmlPathElement(element), elements.append(element)))); } Rule HtmlPathElement(Var element) { ListVar tagNames = new ListVar<>(); ListVar classNames = new ListVar<>(); Var 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 tagNames) { return Sequence( Identifier(), tagNames.append(match()), ZeroOrMore(Sequence("|", Identifier(), tagNames.append(match())))); } Rule ClassNames(ListVar classNames) { Var className = new Var<>(); return ZeroOrMore(Sequence(ClassName(className), classNames.append(className))); } Rule ClassName(Var className) { return Sequence(".", Identifier(), className.set(match())); } Rule Fresh(Var fresh) { return Sequence(fresh.set(false), Optional(":fresh", fresh.set(true))); } 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")); } class ListVar extends Var> { boolean append(Var element) { return append(element.get()); } boolean append(T element) { if (get() == null) { set(ImmutableList.builder()); } return set(get().add(element)); } public List build() { if (get() == null) { return list(); } else { return get().build(); } } public boolean isEmpty() { return get() == null; } } }