Question

I'm using xmlstarlet to extract changeSet nodes from a liquibase XML changelog where the viewName ends with "v".

However, xmlstarlet is complaining that the ends-with XPATH function does not exist:

$ xmlstarlet sel -N x="http://www.liquibase.org/xml/ns/dbchangelog" -t -m \
"/x:databaseChangeLog/x:changeSet[x:createView[ends-with(@viewName, 'v')]]" \
-c . public.db.changelog.xml

xmlXPathCompOpEval: function ends-with not found
Unregistered function
Stack usage errror
xmlXPathCompiledEval: 3 objects left on the stack.
runtime error: element for-each
Failed to evaluate the 'select' expression.
None of the XPaths matched; to match a node in the default namespace
use '_' as the prefix (see section 5.1 in the manual).
For instance, use /_:node instead of /node

The XML looks a bit like this:

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
  <changeSet id="1391529990457-3">
    <createView viewName="myviewnamev"><!-- view definition here --></createView>
  </changeSet>
  <changeSet id="1391529990457-4">
    <createView viewName="anotherviewname"><!-- view definition here --></createView>
  </changeSet>
</databaseChangeLog>

I do know that the XPATH expression is otherwise correct, because if I change the selection criteria to x:createView[@viewName="myviewnamev"] then it correctly selects only that changeLog entry.

How do I get xmlstarlet to correctly use ends-with? Or, is there an alternative way to accomplish what I want to do?

Was it helpful?

Solution

xmlstarlet only supports XPath 1.0, which does not offer an ends-with($string, $token) function. You need to use substring, string-length and and string comparison to construct your own using this pattern:

substring($string, string-length($string) - string-length($token) + 1) = $token]

Applied to your query, it should look like this (I "precomputed" the string length):

/x:databaseChangeLog/x:changeSet[x:createView[
  substring(@viewName, string-length(@viewName)) = 'v']
]

Alternatively, you might want to look for a more powerful XPath 2.0/XQuery engine.

OTHER TIPS

$ xml sel -t -c //_:changeSet[_:createView[str:split(@viewName,'')[last()]='v']] -n file.xml

$ xml sel -t -c //_:changeSet[_:createView[str:tokenize(@viewName,'')[last()]='v']] -n file.xml

$ xml sel -t -c //_:changeSet[_:createView[substring(@viewName,string-length(@viewName),1)='v']] -n file.xml

Building on @jens-erat's answer ...

[substring(@viewName, string-length(@viewName)) = 'v']

can be made more DRY by putting the predicate on the @viewName attribute:

[@viewName[substring(., string-length(.)) = 'v']]

resulting in:

$ xmlstarlet sel -N x="http://www.liquibase.org/xml/ns/dbchangelog" -t -m \
"/x:databaseChangeLog/x:changeSet[x:createView
   [@viewName[substring(., string-length(.)) = 'v']]
]" -c . public.db.changelog.xml
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top