/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.gui.xpathchooser; import java.util.List; import java.util.regex.Pattern; /** * Utility method to parse XPath expressions. * * @author Heinrich Wendel * @author Markus Kunde */ public final class XPathParser { private static final String SEP = "/"; private static final String START = "[A-Za-z_:]"; private static final String NAME = "[A-Za-z0-9$�%\\.,;\\-_:]*"; private static final String QNAME = START + NAME; private static final String QVAL = "\"?[^<&\"]+\"?"; private static final String OPT_VAL = "(=" + QVAL + ")?"; private static final String QOPTVALNAME = QNAME + OPT_VAL; private static final String PREDICATE = "([/]?" + QOPTVALNAME + "(/" + QOPTVALNAME + ")*" + "(/@" + QNAME + OPT_VAL + ")?|(@" + QOPTVALNAME + ")?)"; /** * Private constructor for utility class. */ private XPathParser() { } /** * Splits a xpath path expression into its components. * * @param expr The xpath expression to split. * @return The parsed XPathExpr. */ public static XPathLocation parse(final String expr) { final XPathLocation xpath = new XPathLocation(); boolean inString = false; boolean inPredicate = false; XPathStep cur = null; for (int i = 0; i < expr.length(); i++) { final String c = expr.substring(i, i + 1); if (c.equals(SEP) && !inString && !(cur instanceof XPathPredicate)) { if (i == 0) { xpath.setAbsolute(true); } cur = null; } else if (c.equals("@") && !inString && !(cur instanceof XPathPredicate)) { cur = new XPathAttribute(); xpath.getSteps().add(cur); } else if (c.equals("[") && !inString && !(cur instanceof XPathPredicate)) { if (cur instanceof XPathNode) { inPredicate = true; ((XPathNode) cur).setPredicate(new XPathPredicate()); cur = ((XPathNode) cur).getPredicate(); } } else if (c.equals("]") && !inString) { cur = null; inPredicate = false; } else if (c.equals("=") && !inString && !inPredicate) { // should be the last element of the path cur = new XPathValue(); xpath.getSteps().add(cur); } else { if (cur == null) { cur = new XPathNode(); xpath.getSteps().add(cur); } cur.setValue(cur.getValue() + c); } if (c.equals("\"")) { inString = !inString; } } return xpath; } /** * Validates if we can parse the given xpath. That does not mean that it is generally not a * valid xpath, but not for our cases at least. * * @param path * The xpath path expression to validate. * @return True or false. */ public static boolean isValid(String path) { final XPathLocation loc = parse(path); final List<XPathStep> steplist = loc.getSteps(); boolean correct = true; for (final XPathStep step: steplist) { if (step instanceof XPathNode) { final XPathPredicate pred = ((XPathNode) step).getPredicate(); if (pred != null) { if (!Pattern.matches(PREDICATE, pred.getValue())) { correct = false; break; } } final String nonpred = ((XPathNode) step).getValue(); if (!Pattern.matches(QNAME, nonpred)) { correct = false; break; } } else if (step instanceof XPathAttribute) { final int index = steplist.indexOf(step); if (index < steplist.size() - 2) { // allowed on two last positions correct = false; break; } if (index == steplist.size() - 2) { final XPathStep lastStep = steplist.get(steplist.size() - 1); if (!(lastStep instanceof XPathValue) || (!Pattern.matches(NAME, step.getValue()))) { correct = false; } break; } if (!Pattern.matches(QOPTVALNAME, step.getValue())) { correct = false; break; } } else { correct = false; break; } } return correct; } /** * Returns only the base component, i.e. everything but the last step of a path. * * @param path The path to split the base from. * @return The base component. */ public static String splitBase(final String path) { final XPathLocation expr = XPathParser.parse(path); if (expr.getSteps().size() > 0) { final XPathStep step = expr.getSteps().remove(expr.getSteps().size() - 1); if (step instanceof XPathValue) { expr.getSteps().remove(expr.getSteps().size() - 1); } } return expr.toString(); } /** * Removes a part from the beginning of the path for set root, thus ignoring the predicates. * * @param org The original path. * @param remove The part to remove. * @return The path with the beginning replaced */ public static String replacePath(final String org, final String remove) { if (remove.equals("") || org.equals("")) { return ""; } final XPathLocation orgLocation = parse(org); final List<XPathStep> orgSteps = orgLocation.getSteps(); final List<XPathStep> removeSteps = parse(remove).getSteps(); for (int i = 0; i < removeSteps.size(); i ++) { if (orgSteps.get(0).getValue().equals(removeSteps.get(i).getValue())) { orgSteps.remove(0); } else { break; } } final XPathLocation retLocation = new XPathLocation(); retLocation.setAbsolute(orgLocation.isAbsolute()); retLocation.getSteps().addAll(orgSteps); final String ret = retLocation.toString(); if (ret.equals(SEP)) { return ""; } return ret; // final XPathLocation orgPath = parse(org); // for (final XPathStep step: orgPath.getSteps()) { // if (step instanceof XPathNode) { // ((XPathNode) step).setPredicate(null); // } // } // final String ret = orgPath.toString(); // return ret.replaceFirst(Pattern.quote(remove), ""); } /** * Removes a part from the beginning of the path. * * @param org The path to remove leading stuff from, including predicates. * @param remove The part to remove. * @return The path with the beginning removed. */ public static String removeLeadingPath(final String org, final String remove) { if (remove.equals("")) { return org; } final XPathLocation orgLocation = parse(org); final List<XPathStep> orgSteps = orgLocation.getSteps(); final List<XPathStep> removeSteps = parse(remove).getSteps(); for (int i = 0; i < removeSteps.size(); i ++) { if (orgSteps.get(0).toString().equals(removeSteps.get(i).toString())) { orgSteps.remove(0); } else { break; } } final XPathLocation retLocation = new XPathLocation(); retLocation.setAbsolute(orgLocation.isAbsolute()); retLocation.getSteps().addAll(orgSteps); final String ret = retLocation.toString(); if (ret.equals(SEP)) { return ""; } return ret; } /** * Helper function to get slash-less and predicate-less xpath elements. * @param xpath The xpath to separate * @return The string array with step names */ public static String[] parseValuesToStrings(final String xpath) { final XPathLocation location = parse(xpath); final List<XPathStep> steps = location.getSteps(); final String[] ret = new String[steps.size()]; for (int i = 0; i < steps.size(); i ++) { ret[i] = steps.get(i).getValue(); } return ret; } /** * Helper function to get slash-less xpath elements including predicates. * @param xpath The xpath to separate * @return The string array with step names and predicates */ public static String[] parseToStrings(final String xpath) { final XPathLocation location = parse(xpath); final List<XPathStep> steps = location.getSteps(); final String[] ret = new String[steps.size()]; for (int i = 0; i < steps.size(); i ++) { ret[i] = steps.get(i).toString(); } return ret; } /** * Helper function to remove all predicates from an xpath. * @param xpath the xpath * @return The cleaned version of the xpath. */ public static String removePredicates(final String xpath) { final XPathLocation location = parse(xpath); final List<XPathStep> steps = location.getSteps(); final StringBuilder ret = new StringBuilder(); for (int i = 0; i < steps.size(); i ++) { ret.append(SEP).append(steps.get(i).getValue()); } return ret.toString(); } }