XSLT 1.0 - 如何排除不包含特定子节点的节点树

XSLT 1.0 - How to exclude node trees that do not contain a specific child

过去 2 天我一直在为此苦苦挣扎。我对 XPATH 和 XSLT 的熟悉程度不及我需要的程度,但时间并没有帮助我真正深入研究。这在 BMC 的 TrueSight Orchestration 应用程序中使用,因此仅限于使用 XPATH/XSLT 1.0.

一些我不介意被重定向到资源以进行解释的附加问题:

  1. 我不熟悉“身份模板”,我在好几篇文章中都提到过它。
  2. 我不熟悉关于如何更好地select处理元素、节点和节点集的模板。
    • 根据经验,我是一名 shell 脚本程序员,因此自上而下的方法对我来说有点陌生,因为它与 XML 文档的处理方式有关。
  3. 关于 XPATH,我仍在学习很多术语、轴、关系路径与绝对路径等。我不害怕通过“谷歌搜索”寻找答案,但有时拥有可用资源比尝试猜测哪些关键字可能提供可能导致答案的结果集更好。
    • 有没有更推荐的首选资源? (我知道这是一个有偏见的问题,但我预计会有一些资源比其他资源更值得推荐。)

我有以下输入文档:

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

我正在尝试 select 从根元素向下,仅包括那些匹配 @name 属性或文本值的服务节点。

这是我通过几个小时的搜索和反复试验拼凑而成的 XSL。我唯一无法弄清楚的部分是如何在找不到匹配服务时防止打印服务器节点及其后代。

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

这是我使用 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>

这是我想要的输出:

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

在某些情况下,一台服务器可能有多个匹配服务。例如,如果我使用“engine”作为关键字来匹配服务,我将在 SERVP0001 中有 1 个匹配项,在 SERVP0002 中有 1 个匹配项,在 SERVN0001 中有 2 个匹配项。我提供的示例输出使用关键字“logging”来匹配服务。

我知道我没有模板或 Choose-When 元素来搜索和 returns 其文本值包含我的搜索字符串的服务。 我想如果我可以通过属性搜索 return 格式正确的文档,我可以修改以适合服务文本。

提前感谢您提供的任何帮助和资源。

你的方法很接近。您成功屏蔽了不需要的 <service> 元素,但未能屏蔽不需要的 <server> 元素。

作为一个简单的介绍:身份模板将所有节点(元素、属性等)从输入流复制到输出流——除非更具体的模板 XSLT 中存在规则。

在您的 XSLT 中,您为元素 <service> 定义了更具体的模板规则 - 因此您在这方面取得了成功。但是您没有为 <server> 元素定义更具体的模板规则,因此所有这些元素都被 identity 模板.

简单地复制了

我不知道 BMC 的 TrueSight Orchestration 应用程序,所以我将匹配字符串定义为 xsl:variables(您也可以使用 xsl:params,如果必要的)。用 ${SERVICENAME} 或您的程序需要的任何内容替换单引号中的字符串。 @* 复制所有属性。

以下 XSLT 确实屏蔽了 <service> 子项中不包含“LOGGING”文本的所有 <server> 元素以及不包含 ` 的所有 <service> 元素记录”文本:

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

它的输出是:

<?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.: 我知道这个建议不会被所有人喜欢,但是我推荐W3Schools作为XML/XSLT/XPath.

的介绍站点

更简单/更短的解决方案(无需将某些内容设置为 1 以记住命中):

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

当此转换应用于提供的 XML 文档时(re-formatted 以避免水平滚动的需要):

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

产生了想要的、正确的结果:

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

如果我们将 <xsl:param name="pKeyword" select="'logging'"/> 更改为 <xsl:param name="pKeyword" select="'engine'"/> 和 运行 转换,将再次产生预期的正确结果:

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