/**
* Copyright 2015 Santhosh Kumar Tekuri
*
* The JLibs authors license this file to you 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 jlibs.xml.sax.dog.path;
import jlibs.xml.sax.dog.Scope;
import jlibs.xml.sax.dog.path.tests.Element;
import jlibs.xml.sax.dog.path.tests.ParentNode;
/**
* This class has various optimizations for LocationPath
*
* @author Santhosh Kumar T
*/
public class LocationPathAnalyzer{
public static LocationPath simplify(LocationPath path){
if(impossible(path))
return LocationPath.IMPOSSIBLE;
path = discardSelfNode(path);
if(path.steps.length>0){
path = compressAnywhere(path);
pnode_element(path);
}
return path;
}
private static boolean impossible(LocationPath path){
/**
* folowing xpaths always returns empty result
*
* 1. /text()
* 2. /following-sibling:anything
* 3. /following:anything
* 4. /attribute:anything
* 5. /namespace:anything
*/
if(path.scope==Scope.DOCUMENT && path.steps.length>0){
switch(path.steps[0].axis){
case Axis.CHILD:
if(path.steps[0].constraint.id==Constraint.ID_TEXT)
return true;
break;
case Axis.FOLLOWING_SIBLING:
case Axis.FOLLOWING:
case Axis.ATTRIBUTE:
case Axis.NAMESPACE:
return true;
}
}
int lastAxis = -1;
for(Step step: path.steps){
// any step having predicate as false() returns nothing
if(step.predicateSet.impossible)
return true;
// following-sibling after attribute or namespace axis always returns nothing
if(step.axis==Axis.FOLLOWING_SIBLING && (lastAxis==Axis.ATTRIBUTE || lastAxis==Axis.NAMESPACE))
return true;
lastAxis = step.axis;
}
return false;
}
// discard self::node() step if it has no predicate
private static LocationPath discardSelfNode(LocationPath path){
int noOfNulls = 0;
Step steps[] = path.steps;
for(int i=steps.length-1; i>=0; --i){
Step step = steps[i];
if(step.axis==Axis.SELF && step.constraint.id==Constraint.ID_NODE && !step.predicateSet.hasPosition){
if(step.predicateSet.getPredicate()==null){
steps[i] = null;
noOfNulls++;
}else{
if(i!=0){
steps[i] = null;
steps[i-1].setPredicate(step);
noOfNulls++;
}
}
}
}
return noOfNulls>0 ? removeNullSteps(path, noOfNulls) : path;
}
// "//book" i,e "/descendant-or-self::node()/child::book" can be simplified to
// "/descendant::book" if there are no predicates on first two steps
private static LocationPath compressAnywhere(LocationPath path){
int noOfNulls = 0;
Step steps[] = path.steps;
Step prevStep = steps[0];
for(int i=1, len=steps.length; i<len; i++){
Step curStep = steps[i];
if(!curStep.predicateSet.hasPosition && prevStep.predicateSet.getPredicate()==null){
if(prevStep.axis==Axis.DESCENDANT_OR_SELF && prevStep.constraint.id==Constraint.ID_NODE){
if(curStep.axis==Axis.CHILD){
steps[i-1] = null;
Step newStep = new Step(Axis.DESCENDANT, curStep.constraint);
newStep.setPredicate(curStep);
steps[i] = curStep = newStep;
noOfNulls++;
}
}
}
prevStep = curStep;
}
return noOfNulls>0 ? removeNullSteps(path, noOfNulls) : path;
}
private static void pnode_element(LocationPath path){
Step steps[] = path.steps;
for(int i=1, len=steps.length; i<len; i++){
Step curStep = steps[i];
switch(curStep.axis){
case Axis.NAMESPACE:
case Axis.ATTRIBUTE:
// if the step preceding attribute/namespace has no predicate and doing node() test,
// the test can be replaced with element() test
Step prevStep = steps[i-1];
if(!prevStep.predicateSet.hasPosition && prevStep.constraint.id==Constraint.ID_NODE){
steps[i-1] = new Step(prevStep.axis, Element.INSTANCE);
steps[i-1].setPredicate(prevStep);
}
break;
case Axis.CHILD:
case Axis.DESCENDANT:
// if the step preceding child/decendant has no predicate and doing node() test,
// the test can be replaced with pnode() test
prevStep = steps[i-1];
if(!prevStep.predicateSet.hasPosition && prevStep.constraint.id==Constraint.ID_NODE){
steps[i-1] = new Step(prevStep.axis, ParentNode.INSTANCE);
steps[i-1].setPredicate(prevStep);
}
break;
}
}
}
private static LocationPath removeNullSteps(LocationPath path, int noOfNulls){
LocationPath spath = new LocationPath(path.scope, path.steps.length-noOfNulls);
int i = 0;
for(Step step: path.steps){
if(step!=null){
spath.steps[i] = step;
i++;
}
}
return spath;
}
}