Skip to content
Browse files

jquery selectors impl

  • Loading branch information...
1 parent e253b02 commit 1169876e016b7449690082036e642415db6d4f32 @michelegonella committed Nov 14, 2012
View
143 zen-webservice/src/main/java/com/nominanuda/web/htmlcomposer/Clause.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2008-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nominanuda.web.htmlcomposer;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.xml.sax.Attributes;
+
+import com.nominanuda.lang.Check;
+import com.nominanuda.lang.Strings;
+
+
+public abstract class Clause {
+
+ public abstract boolean match(String tag, Attributes atts);
+
+ private static final Pattern attEqualsRex = Pattern.compile("\\[(\\w+)=\"([^\"]*)\"\\]");
+ private static final Pattern tagRex = Pattern.compile("(\\w+)");
+ public static Clause build(String bit) {
+ if(bit.startsWith(".")) {
+ return new ClassMatchClause(bit.substring(1));
+ } else if(bit.startsWith("#")) {
+ return new ByIdMatchClause(bit.substring(1));
+ } else if(bit.contains("#")) {
+ String[] tagAndId = bit.split("#");
+ Check.illegalargument.assertEquals(2, tagAndId.length);
+ CompoundClause cc = new CompoundClause();
+ cc.add(new TagMatchClause(tagAndId[0]));
+ cc.add(new ByIdMatchClause(tagAndId[1]));
+ return cc;
+ } else if(bit.contains(".")) {
+ String[] tagAndId = bit.split("\\.");
+ Check.illegalargument.assertEquals(2, tagAndId.length);
+ CompoundClause cc = new CompoundClause();
+ cc.add(new TagMatchClause(tagAndId[0]));
+ cc.add(new ClassMatchClause(tagAndId[1]));
+ return cc;
+ } else if("*".equals(bit)) {
+ return new AsteriskMatchClause();
+ }
+ Matcher m = attEqualsRex.matcher(bit);
+ if(m.find()) {
+ return new AttrEqualsClause(m.group(1), m.group(2));
+ }
+ if(tagRex.matcher(bit).matches()) {
+ return new TagMatchClause(bit);
+ }
+ throw new IllegalArgumentException("unrecognized selector expression"+bit);
+ }
+ public static boolean isAttributeClause(String bit) {
+ return bit.startsWith("[");
+ }
+
+ /////////////////////////////////////////////////////////
+ public static class AsteriskMatchClause extends Clause {
+
+ @Override
+ public boolean match(String tag, Attributes atts) {
+ return true;
+ }
+
+ }
+
+ public static class AttrEqualsClause extends Clause {
+ private final String name;
+ private final String value;
+ public AttrEqualsClause(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public boolean match(String tag, Attributes atts) {
+ String val = atts.getValue(name);
+ if(Strings.nullOrBlank(val)) {
+ return false;
+ }
+ return Strings.splitAndTrim(val, "\\s+").contains(this.value);
+ }
+
+ }
+
+ public static class ByIdMatchClause extends Clause {
+ private final String value;
+ public ByIdMatchClause(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean match(String tag, Attributes atts) {
+ String val = atts.getValue("id");
+ if(Strings.nullOrBlank(val)) {
+ return false;
+ }
+ return val.trim().equals(this.value);
+ }
+
+ }
+
+ public static class ClassMatchClause extends Clause {
+ private final String clazz;
+ public ClassMatchClause(String bit) {
+ this.clazz = bit;
+ }
+
+ @Override
+ public boolean match(String tag, Attributes atts) {
+ String val = atts.getValue("class");
+ if(Strings.nullOrBlank(val)) {
+ return false;
+ }
+ return Strings.splitAndTrim(val, "\\s+").contains(this.clazz);
+ }
+
+ }
+
+ public static class TagMatchClause extends Clause {
+ private final String tag;
+ public TagMatchClause(String bit) {
+ this.tag = bit;
+ }
+
+ @Override
+ public boolean match(String tag, Attributes atts) {
+ return this.tag.equals(tag);
+ }
+
+ }
+}
View
90 zen-webservice/src/main/java/com/nominanuda/web/htmlcomposer/CompoundClause.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2008-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nominanuda.web.htmlcomposer;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.xml.sax.Attributes;
+
+import com.nominanuda.lang.Check;
+
+public class CompoundClause extends Clause {
+ private static final int NOVAL = -1;
+ private static final int TARGET = 0;
+ private static final int PARENT = 1;
+ private static final int ANCESTOR = 2;
+ private int clauseScope = NOVAL;
+ private List<Clause> clauses = new LinkedList<Clause>();
+
+ public void add(Clause c) {
+ clauses.add(c);
+ }
+
+ public boolean isUnizializedScope() {
+ return clauseScope == NOVAL;
+ }
+ public void setAncestorScope() {
+ clauseScope = ANCESTOR;
+ }
+
+ public void setParentScope() {
+ clauseScope = PARENT;
+ }
+
+ public int match(String tag, Attributes atts, List<HtmlTag> parents,
+ int parentRecursionLevel) {
+ switch (clauseScope) {
+ case TARGET:
+ return match(tag, atts) ? parentRecursionLevel : -1;
+ case PARENT:
+ HtmlTag ht = parents.get(parentRecursionLevel);
+ return match(ht.getTagName(), ht.getAttributes())
+ ? parentRecursionLevel + 1 : -1;
+ case ANCESTOR:
+ for(int i = parentRecursionLevel; i < parents.size(); i++) {
+ HtmlTag htx = parents.get(i);
+ if(match(htx.getTagName(), htx.getAttributes())) {
+ return i + 1;
+ }
+ }
+ return -1;
+ default:
+ Check.illegalstate.fail();
+ break;
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean match(String tag, Attributes atts) {
+ for(Clause c : clauses) {
+ if(! c.match(tag, atts)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void setTargetScope() {
+ clauseScope = TARGET;
+ }
+
+ public boolean isTargetScope() {
+ return clauseScope == TARGET;
+ }
+
+}
View
36 zen-webservice/src/main/java/com/nominanuda/web/htmlcomposer/HtmlTag.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nominanuda.web.htmlcomposer;
+
+import org.xml.sax.Attributes;
+
+class HtmlTag {
+ private final String tag;
+ private final Attributes atts;
+
+ public HtmlTag(String tag, Attributes atts) {
+ this.tag = tag;
+ this.atts = atts;
+ }
+
+ public String getTagName() {
+ return tag;
+ }
+
+ public Attributes getAttributes() {
+ return atts;
+ }
+}
View
100 zen-webservice/src/main/java/com/nominanuda/web/htmlcomposer/JquerySelectorExpr.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2008-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nominanuda.web.htmlcomposer;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+
+import com.nominanuda.lang.Strings;
+
+class JquerySelectorExpr {
+ private List<Expr> l = new LinkedList<Expr>();
+ public JquerySelectorExpr(String expr) {
+ List<String> commaSep = Strings.splitAndTrim(expr, ",");
+ for(String cse : commaSep) {
+ l.add(new Expr(cse));
+ }
+ }
+
+ public boolean matches(String tag, Attributes atts, Stack<HtmlTag> parents) {
+ List<HtmlTag> _parents = new LinkedList<HtmlTag>(parents);
+ Collections.reverse(_parents);
+ for(Expr exp : l) {
+ if(exp.matches(tag, atts, _parents)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ private class Expr {
+ private List<CompoundClause> clauses = new LinkedList<CompoundClause>();
+ public Expr(String cse) {
+ cse = cse.replace("[", " [");
+ List<String> bits = Strings.splitAndTrim(cse, "\\s+");
+ Collections.reverse(bits);
+ CompoundClause pendingClause = new CompoundClause();
+ for(int i = 0; i < bits.size(); i++) {
+ String bit = bits.get(i);
+ if(Clause.isAttributeClause(bit)) {
+ pendingClause.add(Clause.build(bit));
+ } else {
+ Clause c = Clause.build(bit);
+ pendingClause.add(c);
+ if(i < bits.size() - 2 && ">".equals(bits.get(i + 1))) {
+ if(clauses.size() > 0 && pendingClause.isUnizializedScope() ) {
+ pendingClause.setAncestorScope();
+ } else if(pendingClause.isUnizializedScope() ){
+ pendingClause.setTargetScope();
+ }
+ clauses.add(pendingClause);
+ pendingClause = new CompoundClause();
+ pendingClause.setParentScope();
+ i++;
+ } else {
+ if(clauses.size() > 0 && pendingClause.isUnizializedScope() ) {
+ pendingClause.setAncestorScope();
+ } else if(pendingClause.isUnizializedScope() ){
+ pendingClause.setTargetScope();
+ }
+ clauses.add(pendingClause);
+ pendingClause = new CompoundClause();
+ }
+ }
+ }
+ }
+
+ public boolean matches(String tag, Attributes atts,
+ List<HtmlTag> parents) {
+ int parentRecursionLevel = 0;
+ int parentChainLen = parents.size();
+ for(CompoundClause c : clauses) {
+ if(parentRecursionLevel > parentChainLen - 1 && ! c.isTargetScope()) {
+ return false;
+ }
+ parentRecursionLevel = c.match(tag, atts, parents, parentRecursionLevel);
+ if(parentRecursionLevel == -1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ }
+}
View
57 zen-webservice/src/test/java/com/nominanuda/web/htmlcomposer/CssMatchTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2008-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nominanuda.web.htmlcomposer;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Stack;
+
+import org.junit.Test;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+import com.nominanuda.web.http.HttpProtocol;
+
+public class CssMatchTest {
+
+ @Test
+ public void test() {
+ Stack<HtmlTag> ancestors = new Stack<HtmlTag>();
+ ancestors.push(new HtmlTag("body", atts()));
+ ancestors.push(new HtmlTag("form", atts("class","aform")));
+ ancestors.push(new HtmlTag("div", atts("id","thediv")));
+ match("div p", "p", atts(), ancestors);
+ match("div > p", "p", atts(), ancestors);
+ match("body p", "p", atts(), ancestors);
+ match("form.aform > div#thediv > p", "p", atts(), ancestors);
+ match("#uglyman", "input", atts("id", "uglyman"), ancestors);
+ match("div > *", "p", atts(), ancestors);
+ match("*[miao=\"bau\"]", "p", atts("miao", "bau"), ancestors);
+ }
+
+ private void match(String cssExpr, String tag, Attributes atts, Stack<HtmlTag> ancestors) {
+ JquerySelectorExpr expr = new JquerySelectorExpr(cssExpr);
+ assertTrue(expr.matches(tag, atts, ancestors));
+ }
+
+ private Attributes atts(String... ss) {
+ AttributesImpl a = new AttributesImpl();
+ for(int i = 0; i < ss.length/2; i++) {
+ a.addAttribute(HttpProtocol.HTMLNS, ss[i*2], ss[i*2], "", ss[i*2+1]);
+ }
+ return a;
+ }
+}

0 comments on commit 1169876

Please sign in to comment.
Something went wrong with that request. Please try again.