package org.ops4j.pax.construct.util; /* * Copyright 2007 Stuart McCulloch, Alin Dreghiciu * * 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. */ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.codehaus.plexus.util.xml.Xpp3Dom; /** * Provide a very simple XPATH query implementation for XML pull-parser (Xpp) documents */ public class XppPathQuery { /** * Node is a word */ private static final String NODE = "\\w+"; /** * Parent is a node followed by zero or more slash-separated nodes */ private static final String PARENT = NODE + "(?:/" + NODE + ")*"; /** * Test compares dot (ie. current node) or a node to a quoted string */ private static final String TEST = "(.|" + NODE + ")='(.*)'"; /** * Binary operator can be and / or */ private static final String BIN_OP = "(?:and|or)"; /** * Predicate is a test combined with zero or more tests using binary operators */ private static final String PREDICATE = TEST + "(?:\\s+" + BIN_OP + "\\s+" + TEST + ")*"; /** * XPATH is a parent followed by a pivot node and a predicate */ private static final String XPATH = "/?(" + PARENT + ")/(" + NODE + ")\\[\\s*(" + PREDICATE + ")\\s*\\]"; /** * Compiled XPATH expression matcher */ private final Matcher m_xpathParser; /** * Create a new XPATH query object from a given string * * @param xpath simple XPATH query * @throws IllegalArgumentException */ public XppPathQuery( String xpath ) throws IllegalArgumentException { m_xpathParser = Pattern.compile( XPATH ).matcher( xpath ); if( !m_xpathParser.matches() ) { throw new IllegalArgumentException( "Unsupported XPATH syntax: " + xpath ); } } /** * Find the parent node for this XPATH query * * @param dom document root * @return the parent node */ public Xpp3Dom queryParent( Xpp3Dom dom ) { String[] nodes = m_xpathParser.group( 1 ).split( "/" ); Xpp3Dom parent = dom; for( int i = 0; parent != null && i < nodes.length; i++ ) { parent = parent.getChild( nodes[i] ); } return parent; } /** * Find all children matching the XPATH predicate * * @param parent the parent node * @return array of child indices */ public int[] queryChildren( Xpp3Dom parent ) { String pivotNode = m_xpathParser.group( 2 ); // split into tests and binary operators Pattern testPattern = Pattern.compile( TEST ); String[] testClauses = m_xpathParser.group( 3 ).split( "\\s+" ); List children = new ArrayList( Arrays.asList( parent.getChildren() ) ); Set results = new HashSet(); for( int i = -1; i < testClauses.length; i += 2 ) { // parse test clause (at every even index) Matcher matcher = testPattern.matcher( testClauses[i + 1] ); matcher.matches(); Set selection = filter( children, pivotNode, matcher.group( 1 ), matcher.group( 2 ) ); if( i > 0 && "and".equals( testClauses[i] ) ) { // and == intersect results.retainAll( selection ); } else { // or == union results.addAll( selection ); } } int[] indices = new int[results.size()]; int n = 0; for( Iterator i = results.iterator(); i.hasNext(); ) { indices[n++] = children.indexOf( i.next() ); } return indices; } /** * @param children complete list of child nodes * @param pivotNode pivot node * @param testNode test node * @param testValue test value * @return matching child nodes */ private Set filter( List children, String pivotNode, String testNode, String testValue ) { Set results = new HashSet(); for( Iterator i = children.iterator(); i.hasNext(); ) { Xpp3Dom node = (Xpp3Dom) i.next(); Xpp3Dom test = node; if( !testNode.startsWith( "." ) ) { test = node.getChild( testNode ); } if( pivotNode.equals( node.getName() ) && test != null && testValue.equals( test.getValue() ) ) { results.add( node ); } } return results; } }