【问题标题】:How to use Java String variables inside XPath query如何在 XPath 查询中使用 Java 字符串变量
【发布时间】:2011-04-14 12:33:02
【问题描述】:

我有 books.xml 文件,其中包含作者姓名和书名。我使用下面的代码sn-p查询books.xml

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression expr 
    = xpath.compile("//book[author= 'Larry Niven']/title/text()");

现在,如果我想在程序作为字符串变量运行时传递它,而不是直接将名称放入查询中,该怎么做。只放字符串变量名是不行的!

【问题讨论】:

  • 为什么不能动态构建 xpath 表达式,我的意思是像 "//book[author= '" + authorName + "']/title/text()"?
  • 是的,这是一个很好的开始。我也试试。谢谢。
  • 如何将变量注册到 XPath 评估上下文中完全取决于 XPath 引擎 API。

标签: java xml xpath


【解决方案1】:

这里的问题是当你有一个像臭名昭著的Larry "Basher" O'Niven这样的作者时。

在这种情况下,您需要转义变量,就像在这个简单的实现中一样:

  public static String escape(String s) {
    Matcher matcher = Pattern.compile("['\"]")
        .matcher(s);
    StringBuilder buffer = new StringBuilder("concat(");
    int start = 0;
    while (matcher.find()) {
      buffer.append("'")
          .append(s.substring(start, matcher.start()))
          .append("',");
      buffer.append("'".equals(matcher.group()) ? "\"'\"," : "'\"',");
      start = matcher.end();
    }
    if (start == 0) {
      return "'" + s + "'";
    }
    return buffer.append("'")
        .append(s.substring(start))
        .append("'")
        .append(")")
        .toString();
  }

这可以用这段代码来演示:

String xml =
    "<xml><foo bar=\"Larry &quot;Basher&quot; O'Niven\">Ringworm</foo></xml>";
String query =
    String.format("//foo[@bar=%s]", escape("Larry \"Basher\" O'Niven"));
System.out.println(query);
String book = XPathFactory.newInstance()
    .newXPath()
    .evaluate(query, new InputSource(new StringReader(xml)));
System.out.println(query + " > " + book);

【讨论】:

    【解决方案2】:

    实际上,您可以在 XPath 中同时使用自定义函数和变量——但是对于许多用途来说,快速破解可能会更有效率。

    以下是我开发的一些代码,作为我们学生的学习工具。它可以让你这样做:

    // create some variable we want to use in the xpath
    xPathVariableAndFunctionResolver.newVariable("myNamespace", "id", "xs:string", "l2"); // myNamespace is declared in the namespace context with prefix 'my'
    
    // create an XPath expression
    String expression = "//did:Component[@id=$my:id]"; // variable $namespace:name
    XPathExpression findComponents = xPathFunctionAndVariableOperator.compile(expression);
    
    // execute the XPath expression against the document
    NodeList statements = (NodeList)findComponents.evaluate(document, XPathConstants.NODESET);
    

    XPath 函数也是如此。代码,首先是普通 XPath 评估的包装器:

    public class XPathOperator {
    
        protected XPath xPath;
        protected XPathFactory xPathFactory;
    
        private Hashtable<String, XPathExpression> compiled = new Hashtable<String, XPathExpression>();
    
        protected void initFactory() throws XPathFactoryConfigurationException {
            xPathFactory = XPathFactory.newInstance(XPathConstants.DOM_OBJECT_MODEL);
        }
    
        protected void initXPath(NamespaceContext context) {
            xPath = xPathFactory.newXPath();
            xPath.setNamespaceContext(context);
        }
    
        public XPathOperator(NamespaceContext context) throws XPathFactoryConfigurationException {
            initFactory();
            initXPath(context);
        }
    
        public Object evaluate(Document document, String expression, QName value) throws XPathExpressionException {
    
            // create an XPath expression - http://www.zvon.org/xxl/XPathTutorial/General/examples.html
            XPathExpression findStatements = compile(expression);
    
            // execute the XPath expression against the document
            return (NodeList)findStatements.evaluate(document, value);
        }
    
        public XPathExpression compile(String expression) throws XPathExpressionException {
            if(compiled.containsKey(expression)) {
                return (XPathExpression) compiled.get(expression);
            }
    
            XPathExpression xpath = xPath.compile(expression);
    
            System.out.println("Compiled XPath " + expression);
    
            compiled.put(expression, xpath);
    
            return xpath;
        }
    }
    

    然后我们添加自定义变量和函数的概念,当然还有命名空间:

    public class XPathFunctionAndVariableOperator extends XPathOperator {
    
            public XPathFunctionAndVariableOperator(NamespaceContext context, XPathVariableResolver xPathVariableResolver, XPathFunctionResolver xPathFunctionResolver) throws XPathFactoryConfigurationException {
    
        super(context);
    
            xPath.setXPathVariableResolver(xPathVariableResolver);
            xPath.setXPathFunctionResolver(xPathFunctionResolver);
        }
    }
    

    如果没有变量和函数解析器,这不会很有趣:

    public class XPathVariableAndFunctionResolver implements XPathVariableResolver, XPathFunctionResolver {
    
        private Hashtable functions = new Hashtable();
        private Hashtable variables = new Hashtable();
    
        private SchemaDVFactory factory = SchemaDVFactory.getInstance();
    
        public XPathFunction resolveFunction(QName functionName, int arity) {
            Hashtable table = (Hashtable)functions.get(functionName.getNamespaceURI());
            if(table != null) {
                XPathFunction function = (XPathFunction)table.get(functionName.getLocalPart());
                if(function == null) {
                    throw new RuntimeException("Function " + functionName.getLocalPart() + " does not exist in namespace " + functionName.getNamespaceURI() + "!");
                }
                System.out.println("Resolved function " + functionName + " with " + arity + " argument(s)");
                return function;
            }
            throw new RuntimeException("Function namespace " + functionName.getNamespaceURI() + " does not exist!");
        }
    
        /**
         *
         * Adds a variable using namespace and name, primitive type and default value
         *
         * @param namespace
         * @param name
         * @param datatype      one of the built-in XML datatypes
         * @param value
         * @throws InvalidDatatypeValueException    if value is not of correct datatype
         */
    
        @SuppressWarnings("unchecked")
        public void newVariable(String namespace, String name, String datatype, String value) throws InvalidDatatypeValueException {
    
            int index = datatype.indexOf(":");
            if(index != -1) {
                datatype = datatype.substring(index+1);
            }
            XSSimpleType builtInType = factory.getBuiltInType(datatype);
    
            if(builtInType == null) {
                throw new RuntimeException("Null type for " + datatype);
            }
    
            ValidationState validationState = new ValidationState();
            ValidatedInfo validatedInfo = new ValidatedInfo();
    
            builtInType.validate(value, validationState, validatedInfo);
    
            System.out.println("Defined variable " + name + " as " + datatype + " with value " + value);
    
            Hashtable table;
            if(!variables.containsKey(namespace)) {
                table = new Hashtable();
                variables.put(namespace, table);
            } else {
                table = (Hashtable)variables.get(namespace);
            }
    
            table.put(name, new Object[]{validatedInfo, builtInType});
        }
    
        public void newVariableValue(String namespace, String name, String value) throws InvalidDatatypeValueException {
            ValidationState validationState = new ValidationState();
    
            Hashtable table;
            if(!variables.containsKey(namespace)) {
                throw new RuntimeException("Unknown variable namespace " + namespace);
            } else {
                table = (Hashtable)variables.get(namespace);
            }
    
            Object[] bundle = (Object[])table.get(name);
            ValidatedInfo validatedInfo = (ValidatedInfo)bundle[0];
            XSSimpleType builtInType =  (XSSimpleType)bundle[1];
            builtInType.validate(value, validationState, validatedInfo); // direct reference transfer of value
    
            System.out.println("Assigned value " + validatedInfo.normalizedValue + " to variable " + name);
        }
    
        public Object resolveVariable(QName variableName) {
    
            Hashtable table;
            if(!variables.containsKey(variableName.getNamespaceURI())) {
                throw new RuntimeException("Unknown variable namespace " + variableName.getNamespaceURI());
            } else {
                table = (Hashtable)variables.get(variableName.getNamespaceURI());
            }
    
            Object[] bundle = (Object[])table.get(variableName.getLocalPart());
            if(bundle != null) {
                ValidatedInfo var = (ValidatedInfo)bundle[0];
    
                if(var != null) {
                    switch(var.actualValueType) { // some types omitted, customize your own
                    case XSConstants.INTEGER_DT:
                    case XSConstants.DECIMAL_DT:
                    case XSConstants.INT_DT:
                    case XSConstants.LONG_DT:
                    case XSConstants.SHORT_DT:
                    case XSConstants.BYTE_DT:
                    case XSConstants.UNSIGNEDBYTE_DT:
                    case XSConstants.UNSIGNEDINT_DT:
                    case XSConstants.UNSIGNEDLONG_DT:
                    case XSConstants.UNSIGNEDSHORT_DT:
                        return new Integer(var.normalizedValue);
                    case XSConstants.DATE_DT:
                    case XSConstants.DATETIME_DT:
                    case XSConstants.GDAY_DT:
                    case XSConstants.GMONTH_DT:
                    case XSConstants.GMONTHDAY_DT:
                    case XSConstants.GYEAR_DT:
                    case XSConstants.GYEARMONTH_DT:
                    case XSConstants.DURATION_DT:
                    case XSConstants.TIME_DT:
                        return new Date(var.normalizedValue);
                    case XSConstants.FLOAT_DT:
                        return new Float(Float.parseFloat(var.normalizedValue));
                    case XSConstants.DOUBLE_DT:
                        return new Double(Double.parseDouble(var.normalizedValue));
                    case XSConstants.STRING_DT:
                    case XSConstants.QNAME_DT:
                        return var.normalizedValue;
                    default:
                        throw new RuntimeException("Unknown datatype " + var.actualValueType + " for variable " + variableName + " in namespace " + variableName.getNamespaceURI());
                    }
                }
            }
            throw new RuntimeException("Could not resolve value " + variableName + " in namespace " + variableName.getNamespaceURI());
        }
    
        public void addFunction(String namespace, String name, XPathFunction function) {
            Hashtable table;
            if(!functions.containsKey(namespace)) {
                table = new Hashtable();
                functions.put(namespace, table);
            } else {
                table = (Hashtable)functions.get(namespace);
            }
            table.put(name, function);
        }
    
    }
    

    这些函数显然不能包含在上面,因为通常运行自定义代码(即重点是您编写自己的类),所以使用类似

    public abstract class XPathFunctionImpl implements XPathFunction {
    
        /**
         * This function is called by the XPath expression as it implements the interface XPathFunction
         */
    
        protected int numberArguments;
    
        public Object evaluate(List args) throws XPathFunctionException {
            if(args.size() == numberArguments) {
                return evaluateImpl(args);
            }
            throw new RuntimeException("Illegal number of arguments for " + this);
        }
    
        public abstract Object evaluateImpl(List args) throws XPathFunctionException;
    
    }
    

    然后以某种方式在 evaluateImpl(..) 中实现/子类您自己的逻辑。

    这确实使附加的字符串看起来很……有吸引力;)注意:这段代码已经有几年的历史了,可能存在更好的方法来完成这一切。

    【讨论】:

      【解决方案3】:

      如果你想要一个现成的实现,你可以使用支持变量声明的 commons JXPathhttp://commons.apache.org/jxpath/users-guide.html#Variables

      【讨论】:

        【解决方案4】:
        String rawXPath = "//book[author= '" + larrysName + "']/title/text()";
        

        String rawXPath = String.format("//book[author= '%s']/title/text()", larrysName);
        

        其中larrysName 是来自某个地方的String 类型的变量。

        【讨论】:

        • -1 不幸的是,这对于像 O'Hara 之类的名称或任何其他包含引号的文本来说并不完美。
        • -1。出于同样的原因,它也容易受到 XPath 注入的影响——实际上可能不如 SQL 注入那么糟糕,但基于相同的原理。
        猜你喜欢
        • 2013-01-19
        • 1970-01-01
        • 2011-06-01
        • 1970-01-01
        • 2013-01-18
        • 1970-01-01
        • 2021-06-17
        • 2018-05-01
        • 1970-01-01
        相关资源
        最近更新 更多