XPath 1.0 to find if an element's value is in a list of values
Asked Answered
T

2

43

Is there a way to construct an XPath that evaluates whether an element's value is in a predefined list of values? Something akin to this:

/Location/Addr[State='TX or AL or MA']

Which would match nodes whith State elements for Texas, Alabama, or Massachusetts? I know that I can unpack the expression:

/Location/Addr[State='TX] or  /Location/Addr[State='AL'], etc...

But this is a bit cumbersome since the xpaths are quite long, as is the list of values. My google-fu isn't turning up much on the issue...

Timecard answered 11/6, 2010 at 20:8 Comment(0)
G
65

You can check multiple conditions inside the same square brackets:

/Location/Addr[State='TX' or State='AL' or State='MA']

Or if you have a really long list, you can create a list of states and use the contains() function.

/Location/Addr[contains('TX AL MA', State)]

This will work fine for two-letter state abbreviations. If you want to make it more robust for longer strings you could add some spaces on the ends and check for _TX_, _AL_, etc. (where the underscores are spaces).

/Location/Addr[contains(' TX AL MA ', concat(' ', State, ' '))]
Gloat answered 11/6, 2010 at 20:21 Comment(0)
H
16

Just necromancing, since XPath 2.0 has come along.

With XPath 2.0, you can do:

/Location/Addr[State=('TX', 'AL', 'MA')]

Alternatively, with XPath 1.0, you can use contains in combination with string-length:

DECLARE @tXML xml = '<svg>
<g>
<path></path>
<path data-objid="0000X1">tt</path>
<path data-objid="0000X2"></path>
<path data-objid="0000X3"></path>
</g>
</svg>';

-- SELECT @tXML;

SELECT 
    c.p.value('(@data-objid)[1]', 'varchar(50)') AS objID 
FROM @tXML.nodes('//node()[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]') AS c(p)


SET @tXML.modify('delete //node()[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]'); 

SELECT @tXML AS del;

DECLARE @newRecord xml = '<path data-objid="0000X4"></path>'; 


-- SET @tXML.modify('insert sql:variable("@newRecord") as first into (/svg/g)[1]')
SET @tXML.modify('insert sql:variable("@newRecord") as last into (/svg/g)[1]')

SELECT @tXML AS ins;

See also: add block into specific position

And to update (with xml, you can only update one value at a time, and the text()selector finds nothing for an empty element, so you first need to empty the element, then insert the value, and that for each match):

SET @tXML.modify('replace value of (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]/text())[1] with "40"')
SET @tXML.modify('replace value of (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]/text())[2] with "40"')

SET @tXML.modify('replace value of (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]/text())[1] with ""')
SET @tXML.modify('insert text{"This Works"} into (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0])[1]')

SET @tXML.modify('replace value of (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]/text())[2] with ""')
SET @tXML.modify('insert text{"This Works"} into (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0])[2]')

And to insert and delete an attribute

-- insert attribute
-- SET @tXML.modify('insert attribute data-objid1 {"1"} into (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0])[1]')
-- delete attribute
-- and then, delete suddenly can remove several nodes - unlike insert or modify ... 
-- SET @tXML.modify('delete (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0])/@data-objid[1]')

-- only insert if there is no attribute "data-objid", therefore check with [not(@data-objid)]
-- (on replace, it doesn't create an attribute if it doesn't exist)
SET @tXML.modify('insert attribute data-objid {"1"} into (//path[not(@data-objid) and contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0])[1]')
SET @tXML.modify('replace value of (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]/@data-objid)[1] with "Test"')

And with variables:

DECLARE @testvar varchar(30); 
SET @testvar = 'abc'; 
SET @tXML.modify('insert attribute data-objid {sql:variable("@testvar")} into (//path[not(@data-objid)] and contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0)[1]')
SET @tXML.modify('replace value of (//path[contains("0000X1,0000X2", @data-objid) and string-length(@data-objid) != 0]/@data-objid)[1] with sql:variable("@testvar")')
SET @testvar = '0000X1,0000X2'; 
SET @tXML.modify('delete (//path[contains(sql:variable("@testvar"), @data-objid) and string-length(@data-objid) != 0])/@data-objid[1]')

Note that you should concat the attribute data-objid to ("," + objid + ","), so that modify doesn't accidentally find it if the testvar is ',0000X1a,0000X2b,' instead of ',0000X1,0000X2,' (for example)

DECLARE @testvar varchar(30); 
SET @testvar = ',0000X1a,0000X2b,'; 
SET @tXML.modify('delete (//path[contains(sql:variable("@testvar"), concat(",", @data-objid, ",")) and string-length(@data-objid) != 0])/@data-objid[1]')

SELECT @tXML 
Homophonous answered 7/7, 2017 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.