Cybersecurity
DevOps Cloud
IT Operations Cloud
This page is intent to help demystify XPATH expressions by showing example expressions with explanations as to what elements they match. Example traces are encouraged.
Reference InformationXPath 1.0 http://www.w3.org/TR/xpath
XPath 2.0 http://www.w3.org/TR/xpath20
Note that XPath 2.0 or higher is not supported by Identity Manager
XPATH 2.0 Warning also applies to the following links:
http://www.w3schools.com/Xpath/
http://www.zvon.org/xxl/XPathTutorial/General_ger/examples.html
Geoff Carman's series on XPathhas written extensively regarding Xpath and Identity Manager, here are the most essential articles:
http://www.novell.com/communities/node/4833/some-thoughts-xpath-novell-identity-manager
http://www.novell.com/communities/node/6175/xpath-and-context-node
http://www.novell.com/communities/node/6109/xpath-and-math
http://www.novell.com/communities/node/6179/using-string-compares-xpath-statements
http://www.novell.com/communities/node/6910/another-attempt-explaining-xpath-context-node
http://www.novell.com/communities/node/5845/using-xpath-examine-association-values
http://www.novell.com/communities/node/5686/cool-tricks-using-xpath-nodesets
http://www.novell.com/communities/node/4825/using-global-configuration-values-xpath
http://www.novell.com/communities/node/6276/using-xpath-get-position-node-node-set
http://www.novell.com/communities/node/5818/different-attribute-options-identity-manager
http://www.novell.com/communities/node/9214/example-walk-through-using-xpath-identity-manager
Others have also contributed valuable articles regarding XPath, here are some key articles:
https://www.netiq.com/communities/cool-solutions/manipulating-node-sets-idm-via-set-operations/
Examples Strip Values Strip Empty Values [Add]This rule removes elements that have either an empty value [value="] or do not have a value defined [not(*)]
<rule> <description>Strip Empty Values [Add]</description> <conditions> <and> <if-operation mode="case" op="equal">add</if-operation> </and> </conditions> <actions> <do-strip-xpath expression="add-attr[value="]"/> <do-strip-xpath expression="add-attr[not(*)]"/> </actions> </rule>
When the above rule is applied to this input document:
<input> <add class-name="User" qualified-src-dn="o=dirXML Test\ou=Users\cn=User1" src-dn="o=dirXML Test\ou=Users\cn=User1"> <association>o=dirXML Test\ou=Users\cn=User1</association> <add-attr attr-name="cn"> <value>User1</value> </add-attr> <add-attr attr-name="Surname"> <value></value> </add-attr> <add-attr attr-name="Given Name"/> </add> </input>
The result is:
<input> <add class-name="User" qualified-src-dn="o=dirXML Test\ou=Users\cn=User1" src-dn="o=dirXML Test\ou=Users\cn=User1"> <association>o=dirXML Test\ou=Users\cn=User1</association> <add-attr attr-name="cn"> <value>User1</value> </add-attr> </add> </input>
The expression value=" matches the <value></value> and the expression not(*) matches the Given Name example above
Strip Empty Values [Modify]This is similar to the above rule, but looks for elements that are modify values as opposed to add values.
<rule> <description>Strip Empty Values [Modify]</description> <conditions> <or> <if-operation mode="case" op="equal">modify</if-operation> <if-operation mode="case" op="equal">sync</if-operation> </or> </conditions> <actions> <do-strip-xpath expression="modify-attr/add-value[value="]"/> <do-strip-xpath expression="modify-attr[not(*)]"/> </actions> </rule>
Example Document that would have the value stripped:
<modify-attr attr-name="Surname"> <add-value> <value></value> </add-value> </modify-attr>
OR
<modify-attr attr-name="Surname"/>Strip 'Remove All Values'
This removes the instance of <remove-all-values/> from an attribute named Surname
<do-strip-xpath expression="*[@attr-name='Surname']/remove-all-values"/>
If the above rule was applied to this document:
<modify-attr attr-name="Surname"> <remove-all-values/> <add-value> <value>Last Name1</value> </add-value> </modify-attr>
The result would be:
<modify-attr attr-name="Surname"> <add-value> <value>Last Name1</value> </add-value> </modify-attr>Strip a Specific Value of a Specific Attribute
<do-strip-xpath expression="*[@attr-name='CN']/add-value[value='Common Name']"/>
Removes this Value of Common Name:
<add-attr attr-name="cn"> <value>Common Name</value> </add-attr>
Which Returns:
<add-attr attr-name="cn"/>
You should then follow with the strip empty values rules above to clean this up.
After using the Policy Simulator in Designer I found that the following XPath expressions may work better
<do-strip-xpath expression="*[@attr-name='cn'][value='Common Name']/value"/>
Removes this Value of Common Name:
<input> <add class-name="User"> <add-attr attr-name="cn"> <value type="string">Common Name</value> </add-attr> </add> </input>
Which Returns:
<input> <add class-name="User"> <add-attr attr-name="cn"/> </add> </input>
And to clean it all up in one step...
<do-strip-xpath expression="*[@attr-name='cn'][value='Common Name']"/>
Removes the entire CN add-attr:
<input> <add class-name="User"> <add-attr attr-name="cn"> <value type="string">Common Name</value> </add-attr> </add> </input>
Which Returns:
<input> <add class-name="User"> </input>Strip All Empty Nodes (unscoped)
Author: Lothar Haeger
Strip empty nodes of all kinds of operations that usually pass event/command policies (not scoped to add or modify)
<rule> <description>Strip Empty Nodes</description> <conditions/> <actions> <do-strip-xpath expression='self::instance/attr/value[not(*)][not(text()) or text()=""]'/> <do-strip-xpath expression='self::instance/attr[not(*)]'/> <do-strip-xpath expression='self::add/add-attr/value[not(*)][not(text()) or text()=""]'/> <do-strip-xpath expression='self::add/add-attr[not(*)]'/> <do-strip-xpath expression='self::modify/modify-attr/remove-value/value[not(*)][not(text()) or text()=""]'/> <do-strip-xpath expression='self::modify/modify-attr/remove-value[not(*)]'/> <do-strip-xpath expression='self::modify/modify-attr/add-value/value[not(*)][not(text()) or text()=""]'/> <do-strip-xpath expression='self::modify/modify-attr/add-value[not(*)]'/> <do-strip-xpath expression='self::modify/modify-attr[not(*)]'/> </actions> </rule>
No need to set conditions here, they're kind of included in the xpath statements as they won't do anything if they do not match.
Or even shorter in a new 2014 version:
<rule> <description>Strip Empty Nodes</description> <conditions> <or> <if-operation mode="regex" op="equal">add|modify|instance</if-operation> </or> </conditions> <actions> <do-strip-xpath expression='.//value[not(*) and (not(text()) or text()="")]'/> <do-strip-xpath expression='self::modify/modify-attr/remove-value[not(*)]'/> <do-strip-xpath expression='self::modify/modify-attr/add-value[not(*)]'/> <do-strip-xpath expression='*[@attr-name and not(*)]'/> </actions> </rule>
Note: in this new version you must keep the condition block, otherwise the last do-strip-xpath might do funny things during driver startup...
String Operations Retrieve a Portion of a StringThis example grabs just the last portion of a string delimited by a hyphen
<actions> <do-set-local-variable name="vSSN" scope="policy"> <arg-node-set> <token-split delimiter="-"> <token-local-variable name="current-node"/> </token-split> </arg-node-set> </do-set-local-variable> <do-set-dest-attr-value name="vLAST4"> <arg-value> <token-xpath expression="$vSSN[3]"/> </arg-value> </do-set-dest-attr-value> </actions>
When applied to the value 333-22-4444 this would return just 4444
Remove Leading Zeros from a NumberWhen presented with a value that is padded with one or more zeros such as 0003456 or 001 you want to have the actual number passed through as a string for a result of 3456 or 1
<actions> <do-reformat-op-attr name="jobCode"> <arg-value type="string"> <token-xpath expression="number($current-value)"/> </arg-value> </do-reformat-op-attr> </actions>Working with nodesets Get Value From a Nodeset
A query noun token returns its values in type nodeset. One way to get a specific value is to set a local variable using the query token and then use an xpath expression to return the desired value.
<token-xpath expression="$MyLocalVar//value[1]/text()"/>
The [1] indicates the first value node inside your variable. See below if your order is unpredictable and you might need a specific value.
Get Text of Specific Value From a NodesetYou want to test for the presence of one or more of several values. If found you want the found value returned.
For Example:
A user is assigned to three buildings. If one of those buildings is in a specific list of buildings you want to know which of those specific ones. building: Houston building: Paris building: New York Determine if a user has a building value of Athens, Paris, Sydney, or London
First, set a nodeset local variable equal to the attribute value:
<do-set-local-variable name="lv-buildings" scope="policy"> <arg-node-set> <token-attr name="buildings"/> </arg-node-set> </do-set-local-variable>
Next, the xpath that will return the desired value:
<token-xpath expression="$lv-buildings[text()='Athens' or text()='Paris' or text()='Sydney' or text()='London']/text()"/>
This expression returns the string 'Paris'
Note: this expression works when you expect only a single value to return. If the user has more than one value that matches you end up with a concatenated string. How do we fix that? If you set another nodeset variable equal to the xpath expression and remove the trailing text() element then you will get the nodes that match and can iterate through them or handle them as you please. You might also consider using a strip XPATH expression. There are several approaches to handle that.
Min and max value in a nodesetAuthor: Lothar Haeger
For more details visit:
<do-set-local-variable name="minValue" scope="policy"><arg-string> <token-xpath expression='self::*//value[ancestor::*/@attr-name="attrNameGoesHere"][not(. > preceding-sibling::value or . > following-sibling::value)][1]'/> </arg-string> </do-set-local-variable> <do-set-local-variable name="maxValue" scope="policy"> <arg-string> <token-xpath expression='self::*//value[ancestor::*/@attr-name="attrNameGoesHere"][not(. < preceding-sibling::value or . < following-sibling::value)][1]'/> </arg-string> </do-set-local-variable>Add an Element to a Nodeset
This example inserts a remove-all-values element into a modify of an existing attribute. This would be used to ensure that a SET destination attribute action always takes place instead of an add destination attribute.
<do-append-xml-element before="add-value" expression="modify-attr[@attr-name='Surname']" name="remove-all-values"/>
The important items to note here are as follows:
Here is an example input document:
<input> <modify class-name="User" qualified-src-dn="o=dirXML Test\ou=Users\cn=User1"> <association>o=dirXML Test\ou=Users\cn=User1</association> <modify-attr attr-name="Surname"> <add-value> <value type="string">Last Name1</value> </add-value> </modify-attr> </modify> </input>
The following rule would test to ensure that Surname is present and that it doesn't already contain a remove all values element:
<rule> <description>Single Valued Attribute Handler</description> <conditions> <and> <if-class-name mode="nocase" op="equal">User</if-class-name> </and> </conditions> <actions> <do-if> <arg-conditions> <and> <if-xpath op="not-true">modify-attr[@attr-name='Surname']/remove-all-values</if-xpath> <if-op-attr name="Surname" op="available"/> </and> </arg-conditions> <arg-actions> <do-append-xml-element before="add-value" expression="modify-attr[@attr-name='Surname']" name="remove-all-values"/> </arg-actions> <arg-actions/> </do-if> </actions> </rule>
This would be the resultant document:
<input> <modify class-name="User" qualified-src-dn="o=dirXML Test\ou=Users\cn=User1"> <association>o=dirXML Test\ou=Users\cn=User1</association> <modify-attr attr-name="Surname"> <remove-all-values/> <add-value> <value type="string">Last Name1</value> </add-value> </modify-attr> </modify> </input>Working with Nodeset Attributes Remove an attribute of an element
From: Father Ramon \ Forums
This expression will strip an attribute of a value.
To remove the "type" attribute of the value below:
<attr attr-name="memberOf"> <value naming="true" type="dn">CN=dept,OU=groups,DC=domain,DC=com</value> </attr>
Apply this expression:
<do-strip-xpath expression="*[@attr-name="memberOf"]//value/@type"/>
Which would return this:
<attr attr-name="memberOf"> <value naming="true">CN=dept,OU=groups,DC=domain,DC=com</value> </attr>
This could also be accomplished without XPATH as below:
<do-reformat-op-attr name="memberOf"> <arg-value type="string"> <token-local-variable name="current-value"/> </arg-value> </do-reformat-op-attr>
From Father Ramon:
Reformat actually replaces all the existing values for the attributes with new ones. In that sense it is much more broad than the XPath approach because it actually throws away the original value elements and replaces it with a newly fabricated one that will only have on it exactly what you specify to put on it.
Parse a CN from an attribute of an elementFrom: Father Ramon \ Forums
This rule will parse the CN from an attribute of old-src-dn in a rename operation.
Input Document:
<input> <rename class-name="User" old-src-dn="\TREE\organization\myolduid" src-dn="\TREE\organization\mynewuid"> <association state="associated">\TREE\organization\mynewuid</association> <new-name>mynewuid</new-name> </rename> </input>
Policy:
<do-set-local-variable name="old-cn"> <arg-string> <token-parse-dn start="-1" length="1" src-dn-format="slash" dest-dn-format="slash"> <token-xpath expression="@old-src-dn"/> </token-parse-dn> </arg-string> </do-set-local-variable>
Structured Attributes Component Value of a Structured Attribute (By Name)
This rule will grab the value of a single component of a structured attribute using the component's name. This can be used with attributes like postal address.
<token-xpath expression="$current-node/component[@name='COMPONENT1']"/>
When applied to this example:
<modify-attr attr-name="Postal Address"> <remove-all-values/> <add-value> <value type="structured"> <component name="COMPONENT1">Address Name</component> <component name="COMPONENT2">1234 Street Address</component> <component name="COMPONENT3">Additional Street Address</component> <component name="COMPONENT4">City</component> <component name="COMPONENT5">State</component> <component name="COMPONENT6">84000</component> </value> </add-value> </modify-attr>
The value Address Name would be returned.
Component Value of a Structured Attribute (By Position)This rule will grab the value of a single component of a structured attribute by it's position in the attribute. This can be used with attributes like postal address, where the components are not named.
<token-xpath expression="$current-node/component[2]"/>
When applied to this example:
<modify-attr attr-name="homePostalAddress"> <remove-all-values/> <add-value> <value> <component type="string">Herman Munster</component> <component type="string">1313 Mockingbird Lane</component> <component type="string">" "</component> <component type="string">Mockingbird Heights</component> <component type="string">MN</component> <component type="string">12345</component> </value> </add-value> </modify-attr>
The value "1313 Mockingbird Lane" will be returned.
Working With Event and Status MessagesThis expression detects a warning returned on the driver.
<if-xpath op="true">self::status[@level = 'warning']</if-xpath>
For example:
<input> <status event-id="777" level="warning">Operation vetoed by unassociated object.</status> </input>
The subsequent rule:
<token-xpath expression="self::status"/>
Would return "Operation vetoed by unassociated object."
Detect a Merge Event<conditions> <and> <if-operation op="equal">modify</if-operation> <if-xpath op="true">@from-merge = 'true'</if-xpath> </and> </conditions>Remove Un-Associated Group Memberships
You want to remove values of an attribute that do not contain a particular XML attribute
Input Document
<input> <modify class-name="User" qualified-src-dn="o=dirXML Test\ou=Users\cn=User1"> <association>o=dirXML Test\ou=Users\cn=User1</association> <modify-attr attr-name="Surname"> <remove-all-values/> <add-value> <value>Last Name1</value> </add-value> </modify-attr> <modify-attr attr-name="Given Name"> <remove-all-values/> <add-value> <value>First Name1</value> </add-value> </modify-attr> <modify-attr attr-name="Group Membership"> <add-value> <value association-ref="cn=groupname,o=myorg" type="dn">\Tree1\myorg\groupname</value> <value type="dn">\Tree1\myorg\anothergroup</value> <value association-ref="cn=yetonemore" type="dn">\Tree1\myorg\yetonemore</value> </add-value> </modify-attr> </modify> </input>
Policy
<rule> <description>Strip Unassociated Memberships</description> <conditions> <and> <if-op-attr name="Group Membership" op="available"/> </and> </conditions> <actions> <do-strip-xpath expression="*[@attr-name='Group Membership']/add-value/value[not(@association-ref)]"/> </actions> </rule>
Output Result
<input> <modify class-name="User" qualified-src-dn="o=dirXML Test\ou=Users\cn=User1"> <association>o=dirXML Test\ou=Users\cn=User1</association> <modify-attr attr-name="Surname"> <remove-all-values/> <add-value> <value>Last Name1</value> </add-value> </modify-attr> <modify-attr attr-name="Given Name"> <remove-all-values/> <add-value> <value>First Name1</value> </add-value> </modify-attr> <modify-attr attr-name="Group Membership"> <add-value> <value association-ref="cn=groupname,o=myorg" type="dn">\Tree1\myorg\groupname</value> <value association-ref="cn=yetonemore" type="dn">\Tree1\myorg\yetonemore</value> </add-value> </modify-attr> </modify> </input>
Query Vault for DN using Email address as a matching attribute
<rule> <description>Query Manager DN</description> <conditions> <and> <if-attr name="directSupervisorEmail" op="available"/> </and> </conditions> <actions> <do-set-local-variable name="DEST-DN" scope="policy"> <arg-string> <token-text xml:space="preserve">rackspace\users\active</token-text> </arg-string> </do-set-local-variable> <do-set-local-variable name="SEARCH-VALUE" scope="policy"> <arg-string> <token-attr name="directSupervisorEmail"/> </arg-string> </do-set-local-variable> <do-set-local-variable name="ManagerDN" scope="policy"> <arg-node-set> <token-xpath expression="query:search($destQueryProcessor, 'subtree',,$DEST-DN,,'Internet Email Address',$SEARCH-VALUE,)"/> </arg-node-set> </do-set-local-variable> <do-set-local-variable name="SRC-DN" scope="policy"> <arg-string> <token-xpath expression="$ManagerDN/@src-dn"/> </arg-string> </do-set-local-variable> </actions> </rule> <rule> <description>Display Direct Supervisor Query Result</description> <conditions> <and> <if-local-variable name="SRC-DN" op="available"/> </and> </conditions> <actions> <do-set-local-variable name="lvarManagerDN" scope="policy"> <arg-string> <token-parse-dn dest-dn-format="src-dn" start="0"> <token-local-variable name="SRC-DN"/> </token-parse-dn> </arg-string> </do-set-local-variable> <do-set-dest-attr-value name="manager"> <arg-value> <token-local-variable name="lvarManagerDN"/> </arg-value> </do-set-dest-attr-value> </actions> </rule>Run external code on the IDM engine server
Windows only
<rule> <description>Start Notepad</description> <conditions/> <actions> <do-set-local-variable name="Runtime"> <arg-object> <token-xpath expression="java.lang.Runtime:getRuntime()"/> </arg-object> </do-set-local-variable> <do-set-local-variable name="Notepad"> <arg-object> <token-xpath expression="java.lang.Runtime:exec($Runtime, 'notepad.exe c:\NewDoc.txt')"/> </arg-object> </do-set-local-variable> <do-set-local-variable name="Returncode"> <arg-string> <token-xpath expression="java.lang.Process:waitFor($Notepad)"/> </arg-string> </do-set-local-variable> <do-set-local-variable name="Returncode"> <arg-string> <token-xpath expression="java.lang.Process:exitValue($Notepad)"/> </arg-string> </do-set-local-variable> </actions> </rule>
(with IDM versions earlier than 3.5 you need to define namespace for the java classes)
Allow Unsynchronized Values to ExistWhen an attribute is synchronized, you usually designate one system as authoritative. However, their could be instances where you want both to be authoritative. For example:
<rule> <description>Merge Description Values</description> <comment xml:space="preserve">This rule will take any description values being sync'd and convert them to add value events instead of synchronization to preserve destination values.</comment> <conditions> <and> <if-op-attr name="Description" op="available"/> </and> </conditions> <actions> <do-set-local-variable name="lvDescription" scope="policy"> <arg-node-set> <token-op-attr name="Description"/> </arg-node-set> </do-set-local-variable> <do-set-local-variable name="lvDescriptionRemove" scope="policy"> <arg-node-set> <token-xpath expression="*[@attr-name='Description']/remove-value/value"/> </arg-node-set> </do-set-local-variable> <do-strip-op-attr name="Description"/> <do-for-each> <arg-node-set> <token-local-variable name="lvDescriptionRemove"/> </arg-node-set> <arg-actions> <do-remove-dest-attr-value name="Description"> <arg-value> <token-local-variable name="current-node"/> </arg-value> </do-remove-dest-attr-value> </arg-actions> </do-for-each> <do-for-each> <arg-node-set> <token-local-variable name="lvDescription"/> </arg-node-set> <arg-actions> <do-add-dest-attr-value name="Description"> <arg-value> <token-local-variable name="current-node"/> </arg-value> </do-add-dest-attr-value> </arg-actions> </do-for-each> </actions> </rule>
Resultant Values:
Furthermore, removing a value that only exists in the vault, will simply remove that value in the destination.