/**
* Copyright (C) 2000 - 2009 Silverpeas
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* Open Source Software ("FLOSS") applications as described in Silverpeas's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* "http://repository.silverpeas.com/legal/licensing"
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.silverpeas.xml.xpath;
import org.jdom.Element;
import org.silverpeas.xml.XmlTreeHandler;
/**
* Titre : Application Builder Description : Represents a simplified XPath as specified below.<br/>
* W3C XPath 1.0 Recommendation (S=Simplified)<br/>
* (S)[1] LocationPath ::= '/' RelativeLocationPath? | RelativeLocationPath<br/>
* (S)[3] RelativeLocationPath ::= Step ('/' RelativeLocationPath)?<br/>
* (S)[4] Step ::= '.' | '..' | '@' NCName | NCName Predicate?<br/>
* (S)[8] Predicate ::= '[' PredicateExpr ']'<br/>
* (S)[9] PredicateExpr ::= '@'? NCName '=' PrimaryExpr<br/>
* (S)[15] PrimaryExpr ::= Literal | Number<br/>
* Restriction : when a predicate is used, only the first matching element is selected.<br/>
*/
public class XPath {
// ######### Constructors ##########
public XPath() {
}
public XPath(String xpath) {
this(null, xpath, XmlTreeHandler.MODE_SELECT);
}
public XPath(Element startingElement, String xpath) {
this(startingElement, xpath, XmlTreeHandler.MODE_SELECT);
}
/**
* @param Element startingElement the base element where XPath starts
* @param String xpath the XPath string
* @param char mode a static XmlTreeHandler constant
*/
public XPath(Element startingElement, String xpath, char mode) {
setMode(mode);
setStartingElement(startingElement);
setXPath(xpath);
}
// ######### getters and setters ##########
public void setStartingElement(Element e) {
getTreeHandler().setStartingElement(e);
getTokenizer().reinitRead();
eraseAll();
}
public Element getStartingElement() {
return getTreeHandler().getStartingElement();
}
public void setXPath(String xpath) {
getTokenizer().setXPath(xpath);
getTreeHandler().returnToStartingElement();
eraseAll();
}
public String getXPath() {
return getTokenizer().getXPath();
}
/**
* Use the constants defined in XmlTreeHandler
* @see com.silverpeas.xml.XmlTreeHandler
*/
public void setMode(char mode) {
getTreeHandler().setMode(mode);
getTreeHandler().returnToStartingElement();
getTokenizer().reinitRead();
eraseAll();
}
/**
* @return a constant defined in XmlTreeHandler
* @see com.silverpeas.xml.XmlTreeHandler
*/
public char getMode() {
return getTreeHandler().getMode();
}
// ######### XPathTokenizer ##########
private XPathTokenizer getTokenizer() {
if (_tokenizer == null) {
_tokenizer = new XPathTokenizer();
}
return _tokenizer;
}
private XPathTokenizer _tokenizer = null;
// ######### XmlTreeHandler ##########
private XmlTreeHandler getTreeHandler() {
if (_xmlTreeHandler == null) {
_xmlTreeHandler = new XmlTreeHandler();
}
return _xmlTreeHandler;
}
private XmlTreeHandler _xmlTreeHandler = null;
// ######### Does parsing do sth on XML ##########
private void setXMLActionsToBePerformed(boolean doPerform) {
_doPerformActions = doPerform;
}
private boolean areXMLActionsPerformed() {
return _doPerformActions;
}
private boolean _doPerformActions;
// ######### Syntactical analyser ##########
public void parse() throws XPathParseException {
// First, validate the syntax
getTokenizer().reinitRead();
setXMLActionsToBePerformed(false);
analyse();
// Then perform actions
if (getTreeHandler().getStartingElement() != null) {
getTokenizer().reinitRead();
getTreeHandler().returnToStartingElement();
setXMLActionsToBePerformed(true);
analyse();
}
}
/**
* The main parse function
*/
private void analyse() throws XPathParseException {
getTokenizer().readNextToken();
LocationPath();
if (getTokenizer().getCurrentTokenType() != XPathTokenizer.END_OF_XPATH) {
throw new XPathParseException("end of XPath expected", getXPath(),
getTokenizer().getCurrentTokenPosition());
}
if (areXMLActionsPerformed()) {
setExists(getTreeHandler().currentNodeExists());
}
}
/**
* W3C XPath 1.0 Recommendation (S=Simplified) (S)[1] LocationPath ::= '/' RelativeLocationPath? |
* RelativeLocationPath
*/
private void LocationPath() throws XPathParseException {
if (getTokenizer().getCurrentTokenType() == XPathTokenizer.STEP_SEPARATOR) {
if (areXMLActionsPerformed() && getTreeHandler().currentNodeExists()) {
getTreeHandler().gotoRoot();
}
setIsAbsolute(true);
if (getTokenizer().readNextToken() != XPathTokenizer.END_OF_XPATH) {
RelativeLocationPath();
}
} else {
if (areXMLActionsPerformed() && getTreeHandler().currentNodeExists()) {
getTreeHandler().setCurrentElementAsCousinsAncestor();
}
setIsAbsolute(false);
RelativeLocationPath();
}
}
/**
* W3C XPath 1.0 Recommendation (S=Simplified) (S)[3] RelativeLocationPath ::= Step ('/'
* RelativeLocationPath)?
*/
private void RelativeLocationPath() throws XPathParseException {
Step();
if (getTokenizer().getCurrentTokenType() == XPathTokenizer.STEP_SEPARATOR) {
getTokenizer().readNextToken();
RelativeLocationPath();
}
}
/**
* W3C XPath 1.0 Recommendation (S=Simplified) (S)[4] Step ::= '.' | '..' | '@' NCName | NCName
* Predicate?
*/
private void Step() throws XPathParseException {
switch (getTokenizer().getCurrentTokenType()) {
case XPathTokenizer.DOT:
// the current element
getTokenizer().readNextToken();
break;
case XPathTokenizer.PARENT_STEP:
// the parent element
if (areXMLActionsPerformed() && getTreeHandler().currentNodeExists()) {
getTreeHandler().gotoParent();
getTreeHandler().setNameFromCurrentElement();
}
getTokenizer().readNextToken();
break;
case XPathTokenizer.ABREV_ATTRIBAXIS:
// attribute name expected
if (getTokenizer().readNextToken() != XPathTokenizer.NAME) {
throw new XPathParseException("XML Name expected", getXPath(),
getTokenizer().getCurrentTokenPosition());
}
setIsAttribute(true);
setIsElement(false);
// now we search for the named attribute
if (areXMLActionsPerformed() && getTreeHandler().currentNodeExists()) {
getTreeHandler().gotoFirstNephewNode(XmlTreeHandler.TYPE_ATTRIBUTE,
getTokenizer().getCurrentToken());
}
// ready for next analyse step
getTokenizer().readNextToken();
break;
case XPathTokenizer.NAME:
setIsElement(true);
setIsAttribute(false);
String elementName = getTokenizer().getCurrentToken();
if (getTokenizer().readNextToken() == XPathTokenizer.PREDICATE_OPEN) {
Predicate(elementName);
} else {
if (areXMLActionsPerformed() && getTreeHandler().currentNodeExists()) {
getTreeHandler().gotoFirstNephewNode(XmlTreeHandler.TYPE_ELEMENT,
elementName);
}
}
break;
default:
throw new XPathParseException("'.', '..', '@' or XML Name expected",
getXPath(), getTokenizer().getCurrentTokenPosition());
}
}
/**
* W3C XPath 1.0 Recommendation (S=Simplified) (S)[8] Predicate ::= '[' PredicateExpr ']'
*/
private void Predicate(String elementName) throws XPathParseException {
if (getTokenizer().getCurrentTokenType() != XPathTokenizer.PREDICATE_OPEN) {
throw new XPathParseException("'[' expected", getXPath(), getTokenizer()
.getCurrentTokenPosition());
}
getTokenizer().readNextToken();
PredicateExpr(elementName);
if (areXMLActionsPerformed() && getTreeHandler().currentNodeExists()) {
getTreeHandler().setCurrentElementAsCousinsAncestor();
}
if (getTokenizer().getCurrentTokenType() != XPathTokenizer.PREDICATE_CLOSE) {
throw new XPathParseException("']' expected", getXPath(), getTokenizer()
.getCurrentTokenPosition());
}
getTokenizer().readNextToken();
}
/**
* W3C XPath 1.0 Recommendation (S=Simplified) (S)[9] PredicateExpr ::= '@'? NCName '='
* PrimaryExpr
*/
private void PredicateExpr(String elementName) throws XPathParseException {
char nodeType = 'X';
String nodeName = null;
switch (getTokenizer().getCurrentTokenType()) {
case XPathTokenizer.ABREV_ATTRIBAXIS:
nodeType = XmlTreeHandler.TYPE_ATTRIBUTE;
// attribute name expected
if (getTokenizer().readNextToken() != XPathTokenizer.NAME) {
throw new XPathParseException("XML Name expected", getXPath(),
getTokenizer().getCurrentTokenPosition());
}
nodeName = getTokenizer().getCurrentToken();
break;
case XPathTokenizer.NAME:
nodeType = XmlTreeHandler.TYPE_ELEMENT;
nodeName = getTokenizer().getCurrentToken();
break;
default:
throw new XPathParseException("'@' or XML Name expected", getXPath(),
getTokenizer().getCurrentTokenPosition());
}
if (getTokenizer().readNextToken() != XPathTokenizer.EQUALITY) {
throw new XPathParseException("'=' expected", getXPath(), getTokenizer()
.getCurrentTokenPosition());
}
getTokenizer().readNextToken();
String value2find = PrimaryExpr();
// search
if (areXMLActionsPerformed() && getTreeHandler().currentNodeExists()) {
getTreeHandler().gotoFirstNephewParentOf(elementName, nodeType, nodeName,
value2find);
}
}
/**
* W3C XPath 1.0 Recommendation (S=Simplified) (S)[15] PrimaryExpr ::= Literal | Number
*/
private String PrimaryExpr() throws XPathParseException {
String value = null;
switch (getTokenizer().getCurrentTokenType()) {
case XPathTokenizer.INTEGER:
case XPathTokenizer.REAL:
case XPathTokenizer.LITERAL:
value = getTokenizer().getCurrentToken();
break;
default:
throw new XPathParseException("Number or Literal expected", getXPath(),
getTokenizer().getCurrentTokenPosition());
}
getTokenizer().readNextToken();
return value;
}
// ######### Observers & result getters ##########
// ######### is it an attribute ##########
/**
* @return true if the XPath points to an attribute
*/
public Boolean isAttribute() {
return _isAttribute;
}
private void setIsAttribute(boolean isAttribute) {
_isAttribute = new Boolean(isAttribute);
}
private void eraseIsAttribute() {
_isAttribute = null;
}
private Boolean _isAttribute = null;
// ######### is it an element ##########
/**
* @return true if the XPath points to an element
*/
public Boolean isElement() {
return _isElement;
}
private void setIsElement(boolean isElement) {
_isElement = new Boolean(isElement);
}
private void eraseIsElement() {
_isElement = null;
}
private Boolean _isElement = null;
// ######### does it exist in the XML document ##########
/**
* @return null if not parsed or if no starting element is available. True if the XPath points to
* an existing node
*/
public Boolean exists() {
return _exists;
}
private void setExists(boolean doesExist) {
_exists = new Boolean(doesExist);
}
private void eraseExists() {
_exists = null;
}
private Boolean _exists = null;
// ######### Absolute path indicator ##########
/**
* sets the absolute XPath indicator
*/
private void setIsAbsolute(boolean isAbsolute) {
_isAbsolute = new Boolean(isAbsolute);
}
private void eraseIsAbsolute() {
_isAbsolute = null;
}
/**
* @return true if the XPath is absolute
*/
public Boolean isAbsolute() {
return _isAbsolute;
}
private Boolean _isAbsolute;
// ######### init all indicators ##########
private void eraseAll() {
eraseExists();
eraseIsAbsolute();
eraseIsAttribute();
eraseIsElement();
}
// ######### Value of the denoted node ##########
public void setValue(String value) {
getTreeHandler().setCurrentNodeValue(value);
}
/**
* @return null if not parsed or if no starting element is available.
*/
public String getValue() {
return getTreeHandler().getCurrentNodeValue();
}
/**
* @return null if not parsed or if no starting element is available.
*/
public Object getNode() {
return getTreeHandler().getCurrentNode();
}
public void setNodeAsStart() {
getTreeHandler().setCurrentElementAsStartingElement();
}
}