3
votes

I'm trying to search something in multiple attributes in multiple different nodes

This is how I find it in one attribute

//*[contains(@name,'KEYA')]

Sample XML:

<cars>

<car model="2000" name="Awesome KEYA Car" name2="somethine else">Brand1</car>
<car model="2005" name="Awesome Car" name2="KEYA something else">Brand 2</car>
<car name="Awesome Car" name2="somethine else">Brand1</car>
<car dontmatch="KEYA" name2="somethine else">Brand3333</car>

</cars>

What I really want is something like this for more than 10 attributes (it needs to match only white-listed attributes),

//*[contains((@name or @name2 or @name3),'KEYA')]

Using XPATH 1.0. Any ideas about how to do this? Tried several ways other than repeating contains but didn't work.

3
Good question, +1. See my answer for a complete, short and easy one-liner XPath expression solution, that gives you the flexibility to specify easily and in most compact and natural form the names of the attributes to be searched. Explanation is also provided.Dimitre Novatchev

3 Answers

5
votes

This worked for me

/cars/car/@*[contains(.,'KEYA')]/parent::*

enter image description here

ok, after the edit, then I suggest

/cars/car/@*[name()!='dontmatch'][contains(.,'KEYA')]/parent::*

enter image description here

If you want to exclude more than just one attribute, then you need to specify additional predicates (the things inside the square brackets). Xpath doesn't provide a way to exclude sets of attr names, unless you can constrain the set with a startswith() or contains() or something similar.

There are handy ways to query XML in other libraries, like, for example LINQ-to-XML if you are using .NET. That would allow you to specify those kinds of queries more succintly. But it's a different API altogether.

3
votes

Use:

/*/*[@*
        [contains('|name|name1|name2|name3|name4|',
                  concat('|',name(),'|')
                  )
       and
         contains(., 'KEYA')
        ]
       ]

Here is the selection, as produced by the XPath Visualizer:

enter image description here

Explanation:

You can specify in a string the names of all attributes you want searched. The "pipe delimiter (|) used guarantees that even if a name is the start of another, it one will only be included in the search if there is a separate, pipe-delimited substring for this name.

1
votes

How about:

//*[contains(@name,'KEYA') or contains(@name2,'KEYA')]

You could take Cheeso's answer and do something like this:

/cars/car/@*[contains(.,'KEYA') and local-name() != 'dontmatch']/parent::*

One way or another you're going to have to construct the XPath statement to either select from a whitelist of attributes (my example), or exclude attributes in a blacklist (the modified Cheeso example).

Building again off of Cheeso's work this might simplify building the blacklist:

//@*[contains(.,'KEYA') and not(contains('dontmatch,dontmatch2', local-name()))]/parent::*

or as a whitelist:

//@*[contains(.,'KEYA') and contains('name,name2', local-name())]/parent::**