1
votes

I've been struggling with this for the last 2 days. I'm nowhere as familiar with XPATH and XSLT as I need to be, but time has not been conducive to truly dig in and study. This is being used in BMC's TrueSight Orchestration application, and as such is limited to using XPATH/XSLT 1.0.

Some add-on questions that I wouldn't mind being redirected to resources for explanations:

  1. I'm not familiar with an "identity template" and I have seen it referred to in several posts.
  2. I'm not familiar with templates with regard to how to be more selective of the elements, nodes, and node sets for processing.
    • I'm a shell script programmer by experience, so a top-down approach is a bit foreign to me as it relates to how XML documents are processed.
  3. I'm still learning a lot of the terms, axes, relational paths vs. absolute paths, etc. when it comes to XPATH. I'm not afraid of "Googling" for answers, but sometimes having a go-to resource is better than trying to guess at what keywords might provide a result set that may lead to an answer.
    • Is there a more recommended go-to resource? (I know that this is a loaded and biased question, but I'm expecting that there will be a handful of resources that will be more recommended than others.)

I have the following input document:

<servers>
  <server os="WINDOWS" role="CENTRAL" account="*****" name="SERVP0001">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
      <service name="QlikSenseEngineService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Engine Service</service>
      <service name="QlikSensePrintingService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Printing Service</service>
      <service name="QlikSenseProxyService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Proxy Service</service>
      <service name="QlikSenseRepositoryDatabase" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Repository Database</service>
      <service name="QlikSenseRepositoryService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Repository Service</service>
      <service name="QlikSenseSchedulerService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Scheduler Service</service>
      <service name="QlikSenseServiceDispatcher" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Service Dispatcher</service>
    </services>
  </server>
  <server os="WINDOWS" role="SLAVE" account="*****" name="SERVP0002">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
      <service name="QlikSenseEngineService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Engine Service</service>
      <service name="QlikSensePrintingService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Printing Service</service>
      <service name="QlikSenseProxyService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Proxy Service</service>
      <service name="QlikSenseRepositoryService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Repository Service</service>
      <service name="QlikSenseSchedulerService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Scheduler Service</service>
      <service name="QlikSenseServiceDispatcher" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Service Dispatcher</service>
    </services>
  </server>
  <server os="WINDOWS" role="NPRINTING" account="*****" name="SERVN0001">
    <services>
      <service name="QlikNPrintingEngine" start_type="AUTOMATIC" state="RUNNING">Qlik NPrinting Engine</service>
      <service name="QlikNPrintingLicenseService" start_type="AUTOMATIC" state="RUNNING">Qlik NPrinting License Service</service>
      <service name="QlikNPrintingMessagingService" start_type="AUTOMATIC" state="RUNNING">QlikNPrintingMessagingService</service>
      <service name="QlikNPrintingRepoService" start_type="AUTOMATIC" state="RUNNING">QlikNPrintingRepoService</service>
      <service name="QlikNPrintingScheduler" start_type="AUTOMATIC" state="RUNNING">Qlik NPrinting Scheduler</service>
      <service name="QlikNPrintingWebEngine" start_type="AUTOMATIC" state="RUNNING">Qlik NPrinting Web Engine</service>
    </services>
  </server>
</servers>

I am trying to select from the root element down, including only those service nodes that match either the @name attribute or the text value.

This is the XSL that I've been able to cobble together through several hours of searching and trial and error. The only part I have not been able to figure out is how to prevent the printing of the server node and its descendants when no matching service is found.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output indent="no" />
  <xsl:template match="node() | @*">
    <xsl:copy>
      <xsl:apply-templates select="node() | @*" />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="service">
    <xsl:choose>
      <xsl:when test="contains( translate( normalize-space( ./@name ), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ), translate( normalize-space( &quot;${SERVICENAME}&quot; ), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) )">
        <xsl:copy-of select="." />
      </xsl:when>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

This is the current output I'm getting with my XSL:

<servers>
  <server os="WINDOWS" role="CENTRAL" account="*****" name="SERVP0001">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
    </services>
  </server>
  <server os="WINDOWS" role="SLAVE" account="*****" name="SERVP0002">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
    </services>
  </server>
  <server os="WINDOWS" role="NPRINTING" account="*****" name="SERVN0001">
    <services />
  </server>
</servers>

This is my desired output:

<servers>
  <server os="WINDOWS" role="CENTRAL" account="*****" name="SERVP0001">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
    </services>
  </server>
  <server os="WINDOWS" role="SLAVE" account="*****" name="SERVP0002">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
    </services>
  </server>
</servers>

In some cases, there may be more than one matching service for a server. For example, if I used "engine" as a keyword to match services, I would have 1 match in SERVP0001, 1 match in SERVP0002, and 2 matches in SERVN0001. My provided example output is using the keyword "logging" to match services.

I know that I do not have a template or Choose-When element that searches for and returns the services whose text values contain my search string. I figured that if I could get the attribute search to return the correctly formatted document, I could modify to suit for the service text.

Thank you in advance for whatever help and resources you can provide.

2
Could you be more precise as to which criteria you are using to filter out some service elements? For example in your first server, what determines that you only keep one service? - Sebastien
There are lots of good books available, but you've indicated that (a) you want to learn a lot about the concepts of the language, but (b) you don't have time to do so, and it's very hard to satisfy both those requirements at the same time. You're absolutely right that google searching is very unproductive until you know the terminology. - Michael Kay
@Sebastien with regard to the criteria being used - the keyword is passed into the TrueSight Orchestration workflow by the operator executing the workflow. They are prompted to provide all or a part of a service name. So, like I explained in my post, the output I'm receiving currently is from using the keyword "logging." If I were to use "engine" I would expect a total of 4 services among the 3 servers. - Greg M
@MichaelKay noted, and understood. At some point, I will have to dedicate the time to do some self-education. For the most part I was thinking of what websites like W3C would be recommended for reference. Thanks! - Greg M
To add to Dr. Kay's recommendation, there are also Pluralsight courses on XSLT and XPath (disclaimer: I wrote three of these). Just go to Pluralsight and search for "XSLT". - Dimitre Novatchev

2 Answers

2
votes

A simpler / shorter solution (no need to set something to 1 to remember there was a hit):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 
 <xsl:param name="pKeyword" select="'logging'"/>
 <xsl:variable name="vKeywordUpper" select="translate($pKeyword, $vLower, $vUpper)"/>
 
 <xsl:variable name="vUpper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
 <xsl:variable name="vLower" select="'abcdefghijklmnopqrstuvwxyz'"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="server | service">
    <xsl:if test="descendant-or-self::service
                   /@name[contains(translate(., $vLower, $vUpper), $vKeywordUpper)]">
      <xsl:element name="{name()}">
        <xsl:apply-templates select="@*|node()"/>
      </xsl:element>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document (re-formatted to avoid the need for horizontal scrolling):

<servers>
  <server os="WINDOWS" role="CENTRAL" account="*****" name="SERVP0001">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Logging Service</service>
      <service name="QlikSenseEngineService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Engine Service</service>
      <service name="QlikSensePrintingService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Printing Service</service>
      <service name="QlikSenseProxyService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Proxy Service</service>
      <service name="QlikSenseRepositoryDatabase" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Repository Database</service>
      <service name="QlikSenseRepositoryService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Repository Service</service>
      <service name="QlikSenseSchedulerService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Scheduler Service</service>
      <service name="QlikSenseServiceDispatcher" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Service Dispatcher</service>
    </services>
  </server>
  <server os="WINDOWS" role="SLAVE" account="*****" name="SERVP0002">
    <services>
      <service name="QlikLoggingService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Logging Service</service>
      <service name="QlikSenseEngineService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Engine Service</service>
      <service name="QlikSensePrintingService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Printing Service</service>
      <service name="QlikSenseProxyService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Proxy Service</service>
      <service name="QlikSenseRepositoryService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Repository Service</service>
      <service name="QlikSenseSchedulerService" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Scheduler Service</service>
      <service name="QlikSenseServiceDispatcher" start_type="AUTOMATIC"
      state="RUNNING">Qlik Sense Service Dispatcher</service>
    </services>
  </server>
  <server os="WINDOWS" role="NPRINTING" account="*****" name="SERVN0001">
    <services>
      <service name="QlikNPrintingEngine" start_type="AUTOMATIC"
      state="RUNNING">Qlik NPrinting Engine</service>
      <service name="QlikNPrintingLicenseService" start_type="AUTOMATIC"
      state="RUNNING">Qlik NPrinting License Service</service>
      <service name="QlikNPrintingMessagingService" start_type="AUTOMATIC"
      state="RUNNING">QlikNPrintingMessagingService</service>
      <service name="QlikNPrintingRepoService" start_type="AUTOMATIC"
      state="RUNNING">QlikNPrintingRepoService</service>
      <service name="QlikNPrintingScheduler" start_type="AUTOMATIC"
      state="RUNNING">Qlik NPrinting Scheduler</service>
      <service name="QlikNPrintingWebEngine" start_type="AUTOMATIC"
      state="RUNNING">Qlik NPrinting Web Engine</service>
    </services>
  </server>
</servers>

the wanted, correct result is produced:

<servers>
   <server os="WINDOWS" role="CENTRAL" account="*****" name="SERVP0001">
      <services>
         <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
      </services>
   </server>
   <server os="WINDOWS" role="SLAVE" account="*****" name="SERVP0002">
      <services>
         <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
      </services>
   </server>
</servers>

If we change <xsl:param name="pKeyword" select="'logging'"/> to <xsl:param name="pKeyword" select="'engine'"/> and run the transformation, again the expected correct result is produced:

<servers>
   <server os="WINDOWS" role="CENTRAL" account="*****" name="SERVP0001">
      <services>
         <service name="QlikSenseEngineService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Engine Service</service>
      </services>
   </server>
   <server os="WINDOWS" role="SLAVE" account="*****" name="SERVP0002">
      <services>
         <service name="QlikSenseEngineService" start_type="AUTOMATIC" state="RUNNING">Qlik Sense Engine Service</service>
      </services>
   </server>
   <server os="WINDOWS" role="NPRINTING" account="*****" name="SERVN0001">
      <services>
         <service name="QlikNPrintingEngine" start_type="AUTOMATIC" state="RUNNING">Qlik NPrinting Engine</service>
         <service name="QlikNPrintingWebEngine" start_type="AUTOMATIC" state="RUNNING">Qlik NPrinting Web Engine</service>
      </services>
   </server>
</servers>
1
votes

Your approach was close. You successfully masked out the <service> elements you don't want, but failed to mask out the <server> element you don't want.

Just as a simple introduction: the identity template copies all nodes(elements, attributes, ...) from the input stream to the output stream - unless a more specific template rule is present in the XSLT.

In your XSLT you defined a more specific template rule for the element <service> - hence your success in that regard. But you did not define a more specific template rule for the <server> element, therefore all of these elements are simply copied by the identity template.

I do not know about the BMC's TrueSight Orchestration application, so I defined the matching strings as xsl:variables (You could also use xsl:params if necessary). Replace the strings in the single quotes with ${SERVICENAME} or whatever your program needs. The @* copies all attributes.

The following XSLT does mask out all <server> elements that do not contain the "LOGGING" text in a <service> child and also all <service> elements that do not contain the `LOGGING" text:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output indent="no" />
  <xsl:strip-space elements="*" />
  
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="server|service">
    <xsl:variable name="matching">
      <xsl:for-each select="descendant-or-self::service/@name">
        <xsl:if test="contains(translate(normalize-space(.), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),'LOGGING')">1</xsl:if>
      </xsl:for-each>
    </xsl:variable>
    <xsl:if test="contains($matching,'1')">
      <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
      </xsl:copy>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Its output is:

<?xml version="1.0"?>
<servers>
    <server os="WINDOWS" role="CENTRAL" account="*****" name="SERVP0001">
        <services>
            <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
        </services>
    </server>
    <server os="WINDOWS" role="SLAVE" account="*****" name="SERVP0002">
        <services>
            <service name="QlikLoggingService" start_type="AUTOMATIC" state="RUNNING">Qlik Logging Service</service>
        </services>
    </server>
</servers>

P.S.: I know that this suggestion will not be liked by everyone, but I recommend W3Schools as an introductory site to XML/XSLT/XPath.