Today I had a requirement to match an XML path where a part of the path must have a specified attribute that does not have a specified suffix. Additionally, the solution must be in SimpleXML, and so cannot use the XPath function fn:ends-with, since that is part of XPath 2.0 and hence isn’t supported. Phew!
Here is some XML that illustrates my use case. I want to select any primary key columns from tables that are not versionable; in this example, just one column would be returned.
<database>
<table name="event">
<column name="id" type="integer" required="true" primaryKey="true" />
<column name="name" type="varchar" size="50" required="true" />
<column name="description" type="varchar" size="250" />
<column name="location" type="varchar" size="250" />
</table>
<table name="event_versionable">
<column name="id" type="integer" required="true" primaryKey="true" />
<column name="name" type="varchar" size="50" required="true" />
<column name="description" type="varchar" size="250" />
<column name="location" type="varchar" size="250" />
</table>
</database>
One simple solution is thus:
$suffix = '_versionable';
$match = "contains(@name, '$suffix')";
$search = "
/database/table[not($match)]/column[@primaryKey=\"true\"]
";
$keys = $this->xml->xpath($search);
However that will also match columns in tables that have ‘_versionable’ somewhere in the name attribute other than at the end, which isn’t quite what is needed. Using this StackOverflow answer, I have come up with the following:
$suffix = '_versionable';
$match = "
substring(
@name,
string-length(@name) - string-length('$suffix') + 1
)
= '$suffix'
";
$search = "
/database/table[not($match)]/column[@primaryKey=\"true\"]
";
$keys = $this->xml->xpath($search);
Note that the answer on StackOverflow uses name(), which I think returns the tag name; I’ve used @name instead, as I want the value of the attribute called ‘name’.