IBM Support

Frequently used XPATHs in B2B BusinessProcesses

Technical Blog Post


Abstract

Frequently used XPATHs in B2B BusinessProcesses

Body

Business Process (BP) is core functionality of B2B and SFG suite. BPs are designed using BPML (resembles XML) and which often depends on XPATH expressions. I would like to capture some of commonly used XPATH functions/APIs here. That might help as a guide to users while developing BPs.

Here is production documentation that introduces Business Processes and XPATH - Click here

XPATH Tutorial at w3schools - https://www.w3schools.com/xml/xpath_intro.asp

 

Most of the XPATH APIs are self explanatory. So I am not providing description unless it is required.

 

ProcessData is root tag of XML document available for Business Process workflow (i..e, at the time of BP execution).

Let us consider sample ProcessData below

<?xml version="1.0" encoding="UTF-8"?>
<ProcessData>

  <filename>MyFile.pdf</filename>

  <file>MyFile<file>

  <ext>pdf<ext>

  <ProducerName>customProdUser</ProducerName>

  <executionNode>node2</executionNode>

  <Doc attr="READ">document</Doc>

  <Doc attr="WRITE">document2</Doc>

  <Doc>document3</Doc>

</ProcessData>

Please note XPATH using relative paths (not started with forward slash /) always assume parent tag as "ProcessData" with matching case.
(1) Extract Text node from ProcessData and assign to new node. - string
It can be written in multiple ways.

                  <assign to="Name1" from="/ProcessData/filename/text()"/>
                  <assign to="Name1" from="string(/ProcessData/filename)"/>
                  <assign to="Name1" from="string(filename)"/>
                  <assign to="Name1" from="string(//filename)"/>

                 <rule name="running_on_node2?">
                         <condition>string(/ProcessData/executionNode) = "node2"</condition>
                 </rule>
(2) look for a string (or character) in Text node. Returns Boolean result - contains or if

                   <assign to="IsTextFile" from="contains(string(/ProcessData/filename),'.txt')"/>
                   <assign to="ContainDollarHashSymbols"  from="if(contains(string(filename),'$') and                  
                                                                                        contains(string(filename),'#'),'true','false')"/>

                  <rule name="Filename_Has_Dollar?">
                              <condition>contains(string(filename),'$')="true"</condition>
                 </rule>
                 <rule name="IsTextFile?">
                         <condition>contains(string(ext),'txt')</condition>
                </rule>

 

(3) Join strings and/or text nodes into single. - concat

 

                <assign to="Param1" from="concat(string(file),'.',string(ext))"/>

 

                <assign to="Param1" from="concat(file/text(),'.',ext/text())"/>

                <assign to="Param1" from="concat(file/text(),'.txt')"/>

 

(4) Fetch partial string from Text node - substring, substring-after, substring-before

 

                 <assign to="producer" from="substring(string(/ProcessData/ProducerName),7)"/>

                 Returns the substring from the 7th position to the end. Index of the first character in the string is 1 not 0.

 

                <assign to="producer" from="substring(string(/ProcessData/ProducerName),7,4)"/>

                Returns 4 characters starting from the 7th position.

 

              <assign to="file1" from="substring-before(string(/ProcessData/filename),'.')"/>

 

(5) Convert case of string - translate

 

                <assign to="upperFilename" from="translate(string(filename),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>

 

(6) Remove trailing and leading spaces from string - normalize-space

Note : "normalize-space" API Removes leading and trailing spaces from the specified string, and replaces all internal sequences of white space with one and returns the result.

 

                        <assign to="MyUserId" from="string(normalize-space(sci-get-property('my_custom_properties','userID'))"/>
 

"sci-get-property" is not standard XPATH API. It is something B2B product offers. It is usually used for reading custom properties. In this example, this API is reading userID property from my_custom_properties.properties file.

normalize-space is best useful when reading custom properties to trim off any extra spaces in string/property.

 

(7) Counting identical nodes - count
 

               <assign to="DocCount" from="count(/ProcessData/Doc)"/>

 

                 <rule name="Docs_With_READ_Attr?>

                            <condition>count(/ProcessData/Doc[@attr='READ']) &gt; 0</condition>

                </rule>

 

 (8) Verify availability of a node - boolean, node(), not

 

                 <rule name="nodeExists">
                        <condition>boolean(filename/node())</condition>
                </rule>


               <rule name="nodeNotExists">
                    <condition>not(/ProcessData/filename)</condition>
              </rule>

 

---Sample BPs with xpath usage--

(1) Obtain filename from absolute file-location

Assume below is process data

<ProcessData>

  <filename> MyFile.pdf </fic:\mydir\test1\test2\sample.xml</filename>

</ProcessData>


<process name="KK.Fetch.FilenameFromDir">
  <rule name="containsSlash?">
    <condition>contains(//filename/text(),'\')</condition>
  </rule>

  <sequence>
 
    <choice name="SplitFilename">
        <select>
            <case ref="containsSlash?" activity="TruncateDir"/>
        </select>

        <sequence name="TruncateDir">
            <assign to="filename" from="substring-after(filename/text(),'\')"/>
            <repeat ref="SplitFilename"/>
        </sequence>
    </choice>
    
    <assign to="endFileName" from="filename/text()"/>

        
  </sequence>
</process>

 

(2) Extract messages from mailbox and triggers a sub-bp (one for each message) in loop. This bpml takes multiple message IDs from Routing Rule evaluation.

 

<process name="KK_RR_BP">
  <rule name="usingMailboxRouting">
    <condition>count(//RoutingRequest/MessageId) &gt; 0</condition>
  </rule>

  <rule name="moreMessageIDs">
    <condition>number(currentMessageIDIndex/text()) &lt;= number(messageIDCount/text())</condition>
  </rule>

  <sequence>
    <choice name="usingMailbox">
      <select>
        <case ref="usingMailboxRouting" activity="StartMailboxMessageIDLoop"/>
      </select>

      <sequence name="StartMailboxMessageIDLoop">
        <assign to="messageIDCount" from="count(//RoutingRequest/MessageId)"></assign>
        <assign to="currentMessageIDIndex" from="&apos;1&apos;"></assign>
        <choice name="MessageIDLoop">
          <select>
            <case ref="moreMessageIDs" activity="UseMessageID"/>
          </select>

          <sequence name="UseMessageID">
            <operation name="Mailbox ExtractBegin Service">
              <participant name="MailboxExtractBegin"/>
              <output message="ExtractBeginRequest">
                <assign to="MandatoryNode">node1</assign>
                <assign to="MessageId" from="//RoutingRequest/MessageId[position()=//currentMessageIDIndex/text()]/text()"></assign>
                <assign to="CommitNow" from="&apos;Yes&apos;"></assign>
                <assign to="MailoxPath" from="&apos;/Test&apos;"></assign>
                <assign to="." from="*"></assign>
              </output>
              <input message="inmsg">
                <assign to="ExtractBeginResponse" from="*"></assign>
                <assign to="." from="PrimaryDocument"></assign>
                <assign to="." from="MailboxPath"></assign>
              </input>
            </operation>

        <operation name="InvokeSubProcessService">
          <participant name="InvokeSubProcessService"/>
          <output message="InvokeSubProcessServiceTypeInputMessage">
        <assign to="." from="*"></assign>
            <assign to="INVOKE_MODE">ASYNC</assign>
        <assign to="WFD_NAME">Repeat</assign>
          </output>
          <input message="inmsg">
        <assign to="." from="*"></assign>
          </input>
    </operation>

        <operation name="Mailbox Delete Service">
              <participant name="MailboxDelete"/>
              <output message="MailboxDeleteServiceTypeInputMessage">
                <assign to="MandatoryNode">node1</assign>
                <assign to="MessageId" from="//RoutingRequest/MessageId[position()=//currentMessageIDIndex/text()]/text()"></assign>
                <assign to="." from="*"></assign>
              </output>
              <input message="inmsg">
                <assign to="." from="*"></assign>
              </input>
            </operation>

            <assign to="currentMessageIDIndex" from="currentMessageIDIndex + 1"></assign>
            <repeat ref="MessageIDLoop"/>
      </sequence>
        </choice>
      </sequence>
    </choice>
  </sequence>
</process>

 

You may post your questions and/or feedback below

[{"Business Unit":{"code":"BU059","label":"IBM Software w\/o TPS"},"Product":{"code":"SS3JSW","label":"IBM Sterling B2B Integrator"},"Component":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"","Edition":"","Line of Business":{"code":"LOB59","label":"Sustainability Software"}}]

UID

ibm11120641