XPATH Examples

From CoolSolutionsWiki

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.

Contents

Reference Information

XPATH 1.0 http://www.w3.org/TR/xpath

XPATH 2.0 http://www.w3.org/TR/xpath20
Note that xpath 2.0 is not supported by Novell Identity Manager as of Version 3.5, nor as of 3.6 nor 3.6.1 nor 4.0

XPATH 2.0 Warning applies to these links as well:

http://www.w3schools.com/Xpath/

http://www.zvon.org/xxl/XPathTutorial/General_ger/examples.html

Geoff Carman's series on XPATH


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

Father Ramon Tutorials

  • . in XPath 1.0 is shorthand for self::node() the nodeset containing only the context node
  • [ ] in XPath 1.0 is a predicate, e.g. a test that is applied to each node in the set defined by the expression it follows - a sort of a filter if you will, very similar the the WHERE clause in a SQL expression, though I usually read it out-load as SUCH THAT.
  • When used in a predicate . is a way of individually referring to each node you want to test.
[. = not(.)]
  • I don't think it is particularly useful. It essentially is the equivalant of [. = 'false'] since applying not() to a nodeset force a conversion to boolean, which is true if the nodeset is non-empty and . is never empty. Applying not() to true always gives false, and comparing a nodeset to a boolean forces a conversion of the boolean to a string and gives 'false'.
$v[. != 'x' and . != y]
  • The nodes in variable v whose string values are neither equal to 'x' nor equal to 'y'.

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[]"/>
    <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[]"/>
    <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 String

This 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 Number

When 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()"/>

Min and max value in a nodeset

Author: Lothar Haeger

For more details visit:

http://www.novell.com/communities/node/6431/again-power-xpath-or-missing-max-function
<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:

  1. The expression value selects the node that contains a modify of the Surname attribute
  2. The before value ensures that the remove values element is above the add in the document and processed first. Note that it is specified in the context of the current node which is now the values selected by the XPATH expression.

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 element

From: 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 Messages

Detect a Warning Error

This 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 Exist

When 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:

  • Identity Vault Values
    • Today
    • Tomorrow
  • Destination System Values
    • Yesterday
<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:

    • Today
    • Tomorrow
    • Yesterday

Furthermore, removing a value that only exists in the vault, will simply remove that value in the destination.