How to use variables in XPath?

20,740

Solution 1

This XPATH will select all the elements within the prop element that follows the OBJECT_TYPE with the text SiteData:

//OBJECT_TYPE[text() = 'SiteData']/following-sibling::prop[1]/*

To change the OBJECT_TYPE being selected just construct the XPATH in the code:

String xpath = "//OBJECT_TYPE[text() = '" + getObjType() + "']/following-sibling::prop[1]/*"

Which results in code like this:

XPath xPath =  XPathFactory.newInstance().newXPath();
NodeList nodeList = (NodeList)xPath.compile("//OBJECT_TYPE[text() = '" + getObjType() + "']/following-sibling::prop[1]/*").evaluate(document, XPathConstants.NODESET);

for (int i = 0; i < nodeList.getLength(); i++)
{
  System.out.println(nodeList.item(i).getNodeName() + " = " + nodeList.item(i).getTextContent());
}

That given the XML from the question and when getObjType() returns SiteData prints:

DESCRIPTION = Site parameters
PARENT = NULL
VIRTUAL = 0
VISIBLE = 1
PICTURE = NULL
HELP = 10008
MIN_NO = 1
MAX_NO = 1
NAME_FORMAT = NULL

Solution 2

If you want to extract the prop belonging to a specific OBJECT_TYPE you can do that with

/type/OBJECT_TYPE[. = 'some type']/following-sibling::prop[1]

In Java you could build up this XPath expression dynamically using string concatenation but it would be much safer to use an XPath variable if the library you're using can support that (you don't say in the question what library you're using). For example with javax.xml.xpath

XPath xp = XPathFactory.newInstance().newXPath();
final Map<String, Object> vars = new HashMap<String, Object>();
xp.setXPathVariableResolver(new XPathVariableResolver() {
  public Object resolveVariable(QName name) {
    return vars.get(name.getLocalPart());
  }
});

XPathExpression expr = xp.compile("/type/OBJECT_TYPE[. = $type]/following-sibling::prop[1]");

vars.put("type", "Data");
Node dataProps = (Node)expr.evaluate(doc, XPathConstants.NODE);

vars.put("type", "SiteData");
Node siteProps = (Node)expr.evaluate(doc, XPathConstants.NODE);

// taking the value from a variable
vars.put("type", obj.getObjectType());
Node objectProps = (Node)expr.evaluate(doc, XPathConstants.NODE);
Share:
20,740
Sembrano
Author by

Sembrano

There are no stupid questions. Just unclear questions.

Updated on January 22, 2020

Comments

  • Sembrano
    Sembrano over 4 years

    I am using Xpath and Java.

    The XML got plenty of OBJECT_TYPES and every object type has properties and parameters. And each property and parameter got elements.

    How do I do the following from my XML file. I wanna know how to select with the XPATH string expression all property elements depending on whats the name of the OBJECT_TYPE string. The object type string name depends on what name the user selects from the list.

    How can I do that?

    Should be something like :

    String expression = "/getObjType()/prop/*"; 
    

    But the getObjectType is a method so I cant use it in a string expression.

    XML looks something like this:

    <type>
      <OBJECT_TYPE>SiteData</OBJECT_TYPE> 
      <prop>
        <DESCRIPTION>Site parameters</DESCRIPTION> 
        <PARENT>NULL</PARENT> 
        <VIRTUAL>0</VIRTUAL> 
        <VISIBLE>1</VISIBLE> 
        <PICTURE>NULL</PICTURE> 
        <HELP>10008</HELP> 
        <MIN_NO>1</MIN_NO> 
        <MAX_NO>1</MAX_NO> 
        <NAME_FORMAT>NULL</NAME_FORMAT> 
      </prop>
      <param>
        <PARAMETER>blabla</PARAMETER> 
        <DATA_TYPE>INTEGER</DATA_TYPE> 
        <DESCRIPTION>blaba</DESCRIPTION> 
        <MIN_NO>1</MIN_NO> 
        <MAX_NO>1</MAX_NO> 
        <ORDER1>1</ORDER1> 
        <NESTED>0</NESTED> 
        <DEFAULT1>NULL</DEFAULT1> 
        <FORMAT>0:16382</FORMAT> 
      </param>
      <OBJECT_TYPE>Data</OBJECT_TYPE> 
      <prop>
        <DESCRIPTION>Site parameters</DESCRIPTION> 
        <PARENT>NULL</PARENT> 
        <VIRTUAL>0</VIRTUAL> 
        <VISIBLE>1</VISIBLE> 
        <PICTURE>NULL</PICTURE> 
        <HELP>10008</HELP> 
        <MIN_NO>1</MIN_NO> 
        <MAX_NO>1</MAX_NO> 
        <NAME_FORMAT>NULL</NAME_FORMAT> 
      </prop>
      <param>
        <PARAMETER>gmgm</PARAMETER> 
        <DATA_TYPE>INTEGER</DATA_TYPE> 
        <DESCRIPTION>babla</DESCRIPTION> 
        <MIN_NO>1</MIN_NO> 
        <MAX_NO>1</MAX_NO> 
        <ORDER1>1</ORDER1> 
        <NESTED>0</NESTED> 
        <DEFAULT1>NULL</DEFAULT1> 
        <FORMAT>0:16382</FORMAT> 
      </param>
    </type>
    

    So depending on whats the name of the Object_type I wanna get thoose properties and I have list 122 object types so I have to use a varible to pick which one the user selects.

     public class PropXMLParsing {
    
        static PropXMLParsing instance = null;
    
        private List<String> list = new ArrayList<String>();
        ObjType obj = new ObjType();
    
        public static PropXMLParsing getInstance() {
    
            if (instance == null) {
    
                instance = new PropXMLParsing();
                try {
                    instance.ParserForObjectTypes();
                } catch (SAXException e) {
    
                    e.printStackTrace();
                } catch (IOException e) {
    
                    e.printStackTrace();
                } catch (ParserConfigurationException e) {
    
                    e.printStackTrace();
                }
    
            }
    
            return instance;
    
        }
    
        public void ParserForObjectTypes() throws SAXException, IOException,
                ParserConfigurationException {
    
            try {
                FileInputStream file = new FileInputStream(new File(
                        "xmlFiles/CoreDatamodel.xml"));
    
                DocumentBuilderFactory builderFactory = DocumentBuilderFactory
                        .newInstance();
    
                builderFactory.setNamespaceAware(true);
                DocumentBuilder builder = builderFactory.newDocumentBuilder();
    
                Document xmlDocument = builder.parse(file);
    
                XPath xp = XPathFactory.newInstance().newXPath();
                final Map<String, Object> vars = new HashMap<String, Object>();
                xp.setXPathVariableResolver(new XPathVariableResolver() {
                    public Object resolveVariable(QName name) {
                        return vars.get(name.getLocalPart());
                    }
                });
    
                XPathExpression expr = xp
                        .compile("/type/OBJECT_TYPE[. = $type]/following-sibling::prop[1]");
    
                vars.put("type", obj.getObjectType());
                NodeList objectProps = (NodeList) expr.evaluate(xmlDocument,
                        XPathConstants.NODESET);
                System.out.println(objectProps);
    
                for (int i = 0; i < objectProps.getLength(); i++) {
    
                    System.out.println(objectProps.item(i).getFirstChild()
                            .getNodeValue());
                    list.add(objectProps.item(i).getFirstChild().getNodeValue());
    
                }
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (SAXException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ParserConfigurationException e) {
                e.printStackTrace();
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }
    
        public String convertListToString() {
    
            StringBuilder sb = new StringBuilder();
            if (list.size() > 0) {
                sb.append(list.get(0));
                for (int i = 1; i < list.size(); i++) {
                    sb.append(list.get(i));
                }
            }
            return sb.toString();
        }
    
    }
    

    Second solution I have tried that aint working neither not printing out anything in the console.

    public void ParserForObjectTypes() throws SAXException, IOException,
                ParserConfigurationException {
    
            try {
                FileInputStream file = new FileInputStream(new File(
                        "xmlFiles/CoreDatamodel.xml"));
    
                DocumentBuilderFactory builderFactory = DocumentBuilderFactory
                        .newInstance();
    
                DocumentBuilder builder = builderFactory.newDocumentBuilder();
    
                Document xmlDocument = builder.parse(file);
    
                XPath xPath = XPathFactory.newInstance().newXPath();
                NodeList nodeList = (NodeList) xPath.compile(
                        "//OBJECT_TYPE[text() = '" + obj.getObjectType()
                                + "']/following-sibling::prop[1]/*").evaluate(
                        xmlDocument, XPathConstants.NODESET);
    
                for (int i = 0; i < nodeList.getLength(); i++) {
                    System.out.println(nodeList.item(i).getNodeName() + " = "
                            + nodeList.item(i).getTextContent());
                }
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (SAXException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ParserConfigurationException e) {
                e.printStackTrace();
            } catch (XPathExpressionException e) {
                e.printStackTrace();
            }
        }
    
  • Nick Holt
    Nick Holt over 10 years
    What's with the downvote - that XPath works and solves the question.
  • Sembrano
    Sembrano over 10 years
    Yes but in my case we dont know the name is SiteData unless the user clicks on it in the gui therefore all is stored in a variable and that I have getters and setters for. Ive done something like this but havent tested it yet : String expression = "/" + obj.getObjectType() + "/prop/*";
  • Sembrano
    Sembrano over 10 years
    Yes but in my case we dont know the name is SiteData unless the user clicks on it in the gui therefore all is stored in a variable and that I have getters and setters for and also there are like 122 different object types. Ive done something like this but havent tested it yet : String expression = "/" + obj.getObjectType() + "/prop/*";
  • Ian Roberts
    Ian Roberts over 10 years
    @Sembrano you can provide anything you like to the vars.put("type", ...) call. I would always advise you not to build up your XPath expressions by concatenating together user-supplied strings, it's the inviting XPath equivalent of an SQL-injection attack.
  • Nick Holt
    Nick Holt over 10 years
    That won't work, the string concatenation in the example in my answer will where getObjType() returns SiteData or one of the other valid values will. If the XPATH is run before the user has selected an object type, then a default value must be set else the XPATH may be invalid.
  • Nick Holt
    Nick Holt over 10 years
    Is there such a thing as an XPath-injection attack? Suppose you could return nodes you're not meant to see - is that what you mean?
  • Ian Roberts
    Ian Roberts over 10 years
    @NickHolt maybe "attack" is a bit strong but essentially if you allow user-provided input to be part of your expression then you have no guarantee it will even compile - the classic example for XPath is that there's no way to include both single and double quotes within one string literal (double quoted strings can contain single quotes and vice versa, but there's no escaping mechanism to allow one string to contain both types). With a variable resolver the actual XPath expression is a compile time constant - while it may match nothing at least you know it won't throw an exception.
  • Nick Holt
    Nick Holt over 10 years
    I see, interesting, though the question does mention selecting from a list, rather than the user typing the 'type' explicitly.
  • Sembrano
    Sembrano over 10 years
    This code prints out null : dataProps : null siteProps : null objectProps : null
  • Ian Roberts
    Ian Roberts over 10 years
    @Sembrano in order to use XPath you have to parse your document with a namespace-aware parser - call builderFactory.setNamespaceAware(true); before you do builderFactory.newDocumentBuilder(). Apart from that it should work, assuming the XML you gave in the question is a true representation of the real structure.
  • Ian Roberts
    Ian Roberts over 10 years
    @Sembrano the code you posted does not call setNamespaceAware(true)
  • Sembrano
    Sembrano over 10 years
    I added it now and updated the code stil not working. It should print 9 contents from the xml but it print only a object : com.sun.org.apache.xml.internal.dtm.ref.DTMNodeList@1431340
  • Ian Roberts
    Ian Roberts over 10 years
    @Sembrano right, that looks like you've got a node list there, so you're now out of XPath and back into the Java DOM APIs to iterate over that list and extract whatever you need from it. The XPath expression I have given you will find the right props element (so I suspect that node set contains one node), if you add /* to the end of it you'll get the elements inside the props instead, either way you'll need to use DOM APIs from this point on.
  • Sembrano
    Sembrano over 10 years
    yeh i want to save it in a string[] actually but didnt get it working so I put it in a list since I got one working. Anyhow I added /* at the end of my expression but stil the same : com.sun.org.apache.xml.internal.dtm.ref.DTMNodeList@1d8957f
  • Sembrano
    Sembrano over 10 years
    maybe I should just try dom parsing instead for my project seems more easy. In the end I need to be able to save the new edited sml data anyway.
  • Ian Roberts
    Ian Roberts over 10 years
    @Sembrano XPath or DOM on its own can't give you a String[], but it can give you a NodeList which is essentially like an array of nodes - for each item in the node list, .getLocalName() will give you the element name (DESCRIPTION, PARENT, etc.) and .getTextContent() will give you the corresponding value ("Site parameters", "NULL", etc.)
  • Sembrano
    Sembrano over 10 years
    Ive tried your solution but nothing is being written in the console.
  • Sembrano
    Sembrano over 10 years
    hm, I do have a <root> and <info> aswell in the xml file but cant post the <info> and root since its company stuffs in there. so I njust noticed that <type> seem to be a child to root. since the code aint working I mean
  • Ian Roberts
    Ian Roberts over 10 years
    @Sembrano that's what I meant by "assuming the XML you gave in the question is a true representation of the real structure" - if it isn't then you'll obviously have to adjust the XPath expression to match, there are many XPath tutorials on the web that can help you work out the right expression for your specific case.
  • Nick Holt
    Nick Holt over 10 years
    How are you creating the document. I created a String from the XML above, turned it into a byte[] and called Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().pa‌​rse(new ByteArrayInputStream(xmlBytes)).
  • Sembrano
    Sembrano over 10 years
    Yes but if I do //Object_type then it shouldnt matter since then it get the object_type no matter where its located.
  • Sembrano
    Sembrano over 10 years
    Im just parsing my xmlfile in my document : Document xmlDocument = builder.parse(file);