/** * Copyright (c) 2012 Cloudsmith Inc. and other contributors, as listed below. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith * */ package org.cloudsmith.xtext.dommodel.formatter.css; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.cloudsmith.xtext.dommodel.DomModelUtils; import org.cloudsmith.xtext.dommodel.IDomNode; import org.cloudsmith.xtext.dommodel.IDomNode.NodeClassifier; import org.cloudsmith.xtext.dommodel.IDomNode.NodeType; import com.google.common.base.Function; import com.google.inject.Singleton; /** * A FunctionFactory producing values that are dynamically produced when applying a style to * an {@link IDomNode}. * */ @Singleton public class FunctionFactory implements IFunctionFactory { private static class LiteralString implements Function<IDomNode, String> { private String value; public LiteralString(String value) { this.value = value; } @Override public String apply(IDomNode node) { return value; } } private static class LiteralStringSet implements Function<IDomNode, Set<String>> { private Set<String> value; public LiteralStringSet(Set<String> value) { this.value = value; } @Override public Set<String> apply(IDomNode node) { return value; } } public static class Not implements Function<IDomNode, Boolean> { Function<IDomNode, Boolean> function; public Not(Function<IDomNode, Boolean> function) { this.function = function; } @Override public Boolean apply(IDomNode node) { return !function.apply(node); } } private static final Function<IDomNode, String> textOfNodeFunc = new Function<IDomNode, String>() { @Override public String apply(IDomNode node) { return node.getText(); } }; private static final Pattern NON_WORD_CHAR_PATTERN = Pattern.compile("\\W+"); private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); private static final Function<IDomNode, Integer> firstNonWordFunc = new Function<IDomNode, Integer>() { @Override public Integer apply(IDomNode from) { Matcher m = NON_WORD_CHAR_PATTERN.matcher(from.getText()); if(!m.find()) return -1; return m.start(); } }; private static final Function<IDomNode, Integer> lastNonWordFunc = new Function<IDomNode, Integer>() { @Override public Integer apply(IDomNode from) { // reverses the string, and searches from the end // returns the index to the first char in the last location // i.e. xxx...yy returns 3 String s = from.getText(); String r = new StringBuilder(s).reverse().toString(); Matcher m = NON_WORD_CHAR_PATTERN.matcher(r); if(!m.find()) return -1; return s.length() - m.end(); } }; private static Spacing oneSpace = new Spacing(1, 1, 1); private static Spacing noSpace = new Spacing(0, 0, 0); private static LineBreaks oneLine = new LineBreaks(1, 1, 1); private static LineBreaks noLineUnlessPresent = new LineBreaks(0, 0, 1); private static final Function<IDomNode, Spacing> oneSpaceUnlessPredecessorIsWhitespaceTerminated = new Function<IDomNode, Spacing>() { @Override public Spacing apply(IDomNode from) { IDomNode n = DomModelUtils.previousLeaf(from); if(n == null) return oneSpace; String text = n.getText(); if(text == null || text.length() == 0 || n.getNodeType() != NodeType.COMMENT || !WHITESPACE_PATTERN.matcher(text.subSequence(text.length() - 1, text.length())).matches()) return oneSpace; return noSpace; } }; private static final Function<IDomNode, LineBreaks> oneLineBreakUnlessPredecessorIsLinebreakingComment = new Function<IDomNode, LineBreaks>() { @Override public LineBreaks apply(IDomNode from) { IDomNode n = DomModelUtils.previousLeaf(from); if(n == null || n.getNodeType() != NodeType.COMMENT || !n.getStyleClassifiers().contains(NodeClassifier.LINESEPARATOR_TERMINATED)) return oneLine; return noLineUnlessPresent; } }; private static final Function<IDomNode, LineBreaks> oneLineBreakUnlessNextIsLinebreakingComment = new Function<IDomNode, LineBreaks>() { @Override public LineBreaks apply(IDomNode from) { IDomNode n = DomModelUtils.nextLeaf(from); if(n == null || n.getNodeType() != NodeType.COMMENT || !n.getStyleClassifiers().contains(NodeClassifier.LINESEPARATOR_TERMINATED)) return oneLine; return noLineUnlessPresent; } }; private static final Function<IDomNode, Spacing> noSpaceUnlessNextIsLinebreakingComment = new Function<IDomNode, Spacing>() { @Override public Spacing apply(IDomNode from) { IDomNode n = DomModelUtils.nextLeaf(from); if(n == null || n.getNodeType() != NodeType.COMMENT || !n.getStyleClassifiers().contains(NodeClassifier.LINESEPARATOR_TERMINATED)) return noSpace; return oneSpace; } }; @Override public Function<IDomNode, Integer> firstNonWordChar() { return firstNonWordFunc; } @Override public Function<IDomNode, Integer> lastNonWordChar() { return lastNonWordFunc; } @Override public Function<IDomNode, String> literalString(String s) { return new LiteralString(s); } @Override public Function<IDomNode, Set<String>> literalStringSet(Set<String> set) { return new LiteralStringSet(set); } @Override public Function<IDomNode, Spacing> noSpaceUnlessNextIsLinebreakingComment() { return noSpaceUnlessNextIsLinebreakingComment; } @Override public Function<IDomNode, Boolean> not(Function<IDomNode, Boolean> f) { return new Not(f); } @Override public Function<IDomNode, LineBreaks> oneLineBreakUnlessNextIsLinebreakingComment() { return oneLineBreakUnlessNextIsLinebreakingComment; } @Override public Function<IDomNode, LineBreaks> oneLineBreakUnlessPredecessorIsLinebreakingComment() { return oneLineBreakUnlessPredecessorIsLinebreakingComment; } @Override public Function<IDomNode, Spacing> oneSpaceUnlessPredecessorIsWhitespaceTerminated() { return oneSpaceUnlessPredecessorIsWhitespaceTerminated; } @Override public Function<IDomNode, String> textOfNode() { return textOfNodeFunc; } }