package org.openxdm.xcap.common.uri;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
public class Parser {
public static ResourceSelector parseResourceSelector(String xcapRoot, String resourceSelector,String queryComponent) throws ParseException {
if (resourceSelector == null) {
throw new ParseException(null);
}
// undecode uri
try {
resourceSelector = URLDecoder.decode(resourceSelector,"UTF-8");
} catch (UnsupportedEncodingException e1) {
// MUST NOT COME HERE
}
String documentSelector = null;
String nodeSelector = null;
Map<String,String> namespaces = null;
try {
if (xcapRoot != null && resourceSelector.substring(0,xcapRoot.length()).equals(xcapRoot)) {
// xcap root is set and found, remove it
resourceSelector = resourceSelector.substring(xcapRoot.length());
}
// break on nodeSelectorSeparator, that is, ~~
int nodeSelectorSeparator = resourceSelector.indexOf("~~");
if (nodeSelectorSeparator != -1) {
// get document selector, but not the last char if its '/'
if (resourceSelector.charAt(nodeSelectorSeparator-1) == '/') {
documentSelector = resourceSelector.substring(0,nodeSelectorSeparator-1);
} else {
documentSelector = resourceSelector.substring(0,nodeSelectorSeparator);
}
// get node selector
nodeSelector = resourceSelector.substring(nodeSelectorSeparator+2);
// check for query component
if (queryComponent != null) {
namespaces = parseQueryComponent(queryComponent,new ParseException(documentSelector));
} else {
namespaces = new HashMap<String,String>();
}
} else {
if (queryComponent == null) {
// node selector & query component does not exists
// get document selector (but not the last char if its '/')
if (resourceSelector.charAt(resourceSelector.length()-1) == '/') {
documentSelector = resourceSelector.substring(0,resourceSelector.length()-1);
} else {
documentSelector = resourceSelector;
}
}
else {
// node selector does not exists, query component must not exist too
throw new ParseException(documentSelector);
}
}
}
catch (IndexOutOfBoundsException e) {
throw new ParseException(documentSelector,e.getMessage());
}
return new ResourceSelector(documentSelector,nodeSelector,namespaces);
}
public static Map<String,String> parseQueryComponent(String queryComponent, ParseException exceptionToThrow) throws ParseException {
Map<String,String> namespaces = new HashMap<String,String>();
// xpointer scheme = ( scheme data ), but scheme data can contain '(' and ')' chars,
// even unbalanced, but in this case those chars need a '^' escape
// any scheme data '^' must be escaped with another '^' char
// we will discard any scheme not xmlns since those are not used in xcap
// 1) we need to split each scheme, so we find the first ( and then we look for the close ),
// then, if its xmlns scheme we process its data and continue till xpointer ends
int schemeOpenParIndex;
int afterLastSchemeCloseParIndex = 0;
while((schemeOpenParIndex = queryComponent.indexOf('(',afterLastSchemeCloseParIndex)) >= 0) {
// get scheme name
String scheme = queryComponent.substring(afterLastSchemeCloseParIndex,schemeOpenParIndex);
if (scheme.equals("xmlns")) {
// in the xmlns scheme the next close par is the scheme close par
int schemeCloseParIndex = queryComponent.indexOf(')',schemeOpenParIndex);
if (schemeCloseParIndex < 0) {
// close par does not exist, mal formed xpointer
throw exceptionToThrow;
} else {
// get scheme data
String schemeData = queryComponent.substring(schemeOpenParIndex+1,schemeCloseParIndex);
// find the '='
int keyToNamespaceSeparator = schemeData.indexOf('=');
if (keyToNamespaceSeparator < 0) {
// does not exist, mal formed xpointer
throw exceptionToThrow;
} else {
// 1st part is the key, 2nd is the namespace
String key = schemeData.substring(0,keyToNamespaceSeparator);
String namespace = schemeData.substring(keyToNamespaceSeparator+1);
// add (key->namespace) to namespaces
namespaces.put(key,namespace);
// update 'after' last scheme close par pointer
afterLastSchemeCloseParIndex = schemeCloseParIndex+1;
}
}
} else {
// not xmlns pointer, we need to skip it,
// but we need to validade the whole xpointer
if (scheme.indexOf(')') >= 0) {
// its has close pars in scheme name, mal formed xpointer
throw exceptionToThrow;
}
// count openPars
int openPars = 1;
// we need to find the scheme close par
for(int i=schemeOpenParIndex+1;i<queryComponent.length();i++) {
if (queryComponent.charAt(i) == '(') {
// open par
openPars++;
} else if (queryComponent.charAt(i) == ')'){
// close par
openPars--;
if (openPars == 0) {
// found the scheme close par
// update 'after' last scheme close par pointer
afterLastSchemeCloseParIndex = i+1;
break;
}
}
else if (queryComponent.charAt(i) == '^'){
// escape char, we don't need to look at the next char
i++;
continue;
}
}
if (openPars != 0) {
// we didn't found the scheme close par, mal formed
throw exceptionToThrow;
}
}
}
return namespaces;
}
public static DocumentSelector parseDocumentSelector(String documentSelector) throws ParseException {
try {
// get documentName & documentParent
int documentNameSeparator = documentSelector.lastIndexOf("/");
if (documentNameSeparator != -1) {
String documentParent = documentSelector.substring(0,documentNameSeparator);
String documentName = documentSelector.substring(documentNameSeparator+1);
// check there is a leading '/'
if (documentParent.charAt(0) != '/') {
throw new ParseException(null);
} else {
int auidSeparator = documentParent.indexOf('/',1);
if (auidSeparator > 1) {
String auid = documentParent.substring(1,auidSeparator);
return new DocumentSelector(auid,documentParent.substring(auidSeparator+1),documentName);
}
else {
throw new ParseException(null);
}
}
} else {
throw new ParseException(null);
}
}
catch (IndexOutOfBoundsException e) {
throw new ParseException(null);
}
}
public static NodeSelector parseNodeSelector(String nodeSelector) throws ParseException {
String terminalSelector = null;
String elementSelector = null;
int elementToTerminalSelectorSeparator = nodeSelector.lastIndexOf('/');
if (elementToTerminalSelectorSeparator != -1
&& elementToTerminalSelectorSeparator < (nodeSelector.length()-1)) {
// it can be breaked, check its last part is a just a element path or a terminal selector
String elementSelectorCandidate = nodeSelector.substring(0,elementToTerminalSelectorSeparator);
String terminalSelectorCandidate = nodeSelector.substring(elementToTerminalSelectorSeparator+1);
//FIXME what about extension selector??
if (terminalSelectorCandidate.charAt(0) == '@' || terminalSelectorCandidate.equals("namespace::*")) {
elementSelector = elementSelectorCandidate;
terminalSelector = terminalSelectorCandidate;
}
else {
elementSelector = nodeSelector;
}
}
else {
throw new ParseException(null);
}
return new NodeSelector(elementSelector,terminalSelector);
}
public static ElementSelector parseElementSelector(String elementSelector) throws ParseException {
LinkedList<ElementSelectorStep> elementSelectorSteps = new LinkedList<ElementSelectorStep>();
// remove head '/'
if (elementSelector.charAt(0) == '/') {
elementSelector = elementSelector.substring(1);
} else {
throw new ParseException(null);
}
int nextSlashPos = -1;
String stepString = null;
do {
// get next '/' position
nextSlashPos = elementSelector.indexOf('/');
// get the next step string and remove that part
// from element selector
if (nextSlashPos != -1) {
// next slash exists
if (elementSelector.length()-1 > nextSlashPos) {
stepString = elementSelector.substring(0,nextSlashPos);
// advance element selector
elementSelector = elementSelector.substring(nextSlashPos+1);
}
else {
// we need to have more than the slash
throw new ParseException(null);
}
} else {
// no more slashes, its the last step
stepString = elementSelector;
elementSelector = null;
}
// parse and add a step
elementSelectorSteps.addLast(parseElementSelectorStep(stepString));
}
while (elementSelector != null);
return new ElementSelector(elementSelectorSteps);
}
public static ElementSelectorStep parseLastElementSelectorStep(String elementSelector) throws ParseException {
// break the elementSelector on the last '/'
int lastStepSeparatorIndex = elementSelector.lastIndexOf('/');
if (lastStepSeparatorIndex > -1 && lastStepSeparatorIndex < elementSelector.length()-1) {
return parseElementSelectorStep(elementSelector.substring(lastStepSeparatorIndex+1));
} else {
throw new ParseException(null);
}
}
public static ElementSelectorStep parseElementSelectorStep(String step) throws ParseException {
try {
// start breaking the step for '['
String[] elementParts = step.split("\\[");
if (elementParts.length == 1) {
// no '[' found, its the simplest step
return new ElementSelectorStep(step);
}
else if (elementParts.length == 2) {
// one '[' found
// the first part is the element name
if (elementParts[1].charAt(0) != '@') {
// 2nd part is the element position
if (elementParts[1].charAt(elementParts[1].length()-1) == ']') {
return new ElementSelectorStepByPos(elementParts[0],Integer.parseInt(elementParts[1].substring(0,elementParts[1].length()-1)));
}
} else {
// 2nd part is the element id attribute
if (elementParts[1].charAt(elementParts[1].length()-1) == ']') {
String[] elementPartParts = elementParts[1].substring(0,elementParts[1].length()-1).split("=");
if (elementPartParts.length == 2 && ((elementPartParts[1].charAt(0) == '"' && elementPartParts[1].charAt(elementPartParts[1].length()-1) == '"') || (elementPartParts[1].charAt(0) == '\'' && elementPartParts[1].charAt(elementPartParts[1].length()-1) == '\''))) {
return new ElementSelectorStepByAttr(elementParts[0],elementPartParts[0].substring(1),elementPartParts[1].substring(1,elementPartParts[1].length()-1));
}
}
}
}
else if (elementParts.length == 3) {
// two '[' found
// the first part is the element name
// 2nd part is the element position
int elementPosition = -1;
if (elementParts[1].charAt(elementParts[1].length()-1) == ']') {
elementPosition = Integer.parseInt(elementParts[1].substring(0,elementParts[1].length()-1));
}
// 3rd part is the element id attribute
String[] elementPartParts = elementParts[2].substring(0,elementParts[2].length()-1).split("=");
if (elementPartParts.length == 2 && ((elementPartParts[1].charAt(0) == '"' && elementPartParts[1].charAt(elementPartParts[1].length()-1) == '"') || (elementPartParts[1].charAt(0) == '\'' && elementPartParts[1].charAt(elementPartParts[1].length()-1) == '\''))) {
return new ElementSelectorStepByPosAttr(elementParts[0],elementPosition,elementPartParts[0].substring(1),elementPartParts[1].substring(1,elementPartParts[1].length()-1));
}
}
}
catch (IndexOutOfBoundsException e) {
// change it to parse exception
throw new ParseException(e.getMessage());
}
// if we get here than throw parse exception
throw new ParseException(null);
}
public static TerminalSelector parseTerminalSelector(String terminalSelector) throws ParseException {
if (terminalSelector.charAt(0) == '@') {
if (terminalSelector.length() > 1) {
return new AttributeSelector(terminalSelector.substring(1));
}
else {
throw new ParseException(null);
}
}
else if (terminalSelector.equals("namespace::*")) {
return new NamespaceSelector();
}
else {
//FIXME what about extension selector
throw new ParseException(null);
}
}
}