如何将 XSLT 模板应用于具有相同结构的多个不同元素?

How can I apply a XSLT template to several different elements with the same structure?

我已经搜索了 Google 和 Stack Overflow,但一直找不到答案。如果这是重复的,我深表歉意。

我有一个 XML 文档,其中包含一些嵌套类型;但是,整个文档中出现了几个重复的元素,它们的名称和父元素都不同。对比XSD,发现这些重复的元素其实是同一类对象,只是通篇引用而已。大约有 50 种不同的类型引用了这个重复元素。我如何编写一个模板,然后可以从我需要编写的 50 个模板中的每一个模板中调用,这样我就不必为每个父类型复制转换?

例如:

<Model1>
  <Description>desc1</Description>
  <Code>code1</Code>
  <type>VMO</type>
  <Model2>
    <Description>desc1</Description>
    <Code>code1</Code>
    <type>VMO</type>
  </Model2>
</Model1>
<Model3>
  <Description>desc1</Description>
  <Code>code1</Code>
  <type>VMO</type>
  <Model4>
    <Model5>
      <Description>desc1</Description>
      <Code>code1</Code>
      <type>VMO</type>
      <Model6>
        <Description>desc1</Description>
        <Code>code1</Code>
        <type>VMO</type>
      </Model6>
    </Model5>
    <Code>code1</Code>
    <type>VMO</type>
  </Model4>
</Model3>
<Model7>
  <Description>desc1</Description>
  <Code>code1</Code>
  <type>VMO</type>
  <Model8>
    <Description>desc1</Description>
    <Code>code1</Code>
    <type>VMO</type>
  </Model8>
</Model7>

例如,在上面的示例中,Model2Model6Model8 是相同的结构,我需要对每个元素应用相同的转换。我想做这样的事情:

<xsl:template match="Model1">
  <xsl:copy>
    <!-- maps all other elements -->
    <xsl:apply-templates select="someSpecialFunction(Model3)" />
  </xsl:copy>
</xsl:template>
<xsl:template match="Model5">
  <xsl:copy>
    <!-- maps all other elements -->
    <xsl:apply-templates select="someSpecialFunction(Model6)" />
  </xsl:copy>
</xsl:template>
<xsl:template match="Model7">
  <xsl:copy>
    <!-- maps all other elements -->
    <xsl:apply-templates select="someSpecialFunction(Model8)" />
  </xsl:copy>
</xsl:template>

并简单地转换父元素。这可能吗?

我知道我可以做这样的事情:

<xsl:template match="Model1/Model3">
  <xsl:copy>
    <!-- Transfrom type -->
    <xsl:apply-templates select="node()|@*" />
  </xsl:copy>
</xsl:template>

<xsl:template match="Model5/Model6">
  <xsl:copy>
    <!-- Transfrom type -->
    <xsl:apply-templates select="node()|@*" />
  </xsl:copy>
</xsl:template>

<xsl:template match="Model7/Model8">
  <xsl:copy>
    <!-- Transfrom type -->
    <xsl:apply-templates select="node()|@*" />
  </xsl:copy>
</xsl:template>

但后来我重复了很多代码。有什么建议吗?

编辑:

我原来的post太笼统了,对此感到抱歉。 一个更具体的例子(在我的例子中,是我拥有的对象之一)是一个地址和与之关联的子类型。几种不同类型的元素都有一个地址。例如,我有一个 Product 元素,其中包含 Seller、Supplier、Reseller、利益相关者列表,每个利益相关者都有一个地址等。这些地址中的每一个都有不同的名称。

<Product>
  <Seller>
    <BusinessAddress>...</BusinessAddress>
    <DistributionAddress>...</DistributionAddress>
    <MainContactAddress>...</MainContactAddress>
  </Seller>
  <ReSeller>
    <BusinessAddress>...</BusinessAddress>
    <StoreManagerAddress>...</StoreManagerAddress>
    <MainContactAddress>...</MainContactAddress>
  </ReSeller>
  <Supplier>
    <BusinessAddress>...</BusinessAddress>
    <DistributionCenterAddress>...</DistributionCenterAddress>
    <MainContactAddress>...</MainContactAddress>
  </Supplier>
  <Stakeholders>
    <Entity>
      <HomeAddress>...</HomeAddress>
      <WorkAddress>...</WorkAddress>
      <SecondaryAddress>...</SecondaryAddress>
      <Street>...</Street>
      <CrossStreet>...</CrossStreet>
    </Entity>
  </Stakeholders>
</Product>
<SurveyRespondents>
  <SurveyRespondent>
    <Address>...</Address>
    <Business>...</Business>
  </SurveyRespondent>
  <SurveyRespondent>
    <Address>...</Address>
    <Business>...</Business>
  </SurveyRespondent>
</SurveyRespondents>

现在这不是所有具有地址的元素,但是如果我采用我之前做过的方法,我将不得不制作一个模板列表(就像我上面提到的那样)或者使用很长的建议的 michael.hor257k 等匹配列表。

<xsl:template match="Address | Business | SecondaryAddress | WorkAddress | Home | SecondaryAddress | BusinessAddress | ...">
  <!-- apply the same transform to all of these -->
</xsl:template>

为了解决我的问题,每个地址都由多个子类型组成:AddressCategory、District、Zone、Street、StreetType、PostDirection、PreDirection 等。这些类型中的每一个都具有相同的结构,但现在我 运行 进入我如何匹配每种类型的问题。一些子类型,如 Street,在某些地方是一个字符串(在其他元素中使用时),在其他地方是 "CodeType"。如果我进行匹配,我会得到一些非常丑陋的东西,我什至还没有完成整个文档:

<xsl:template match="Address | Business | SecondaryAddress | WorkAddress | Home | SecondaryAddress | BusinessAddress | ...">
  <!-- apply the same transform to all of these -->
</xsl:template>

<xsl:template match="Address/Street | Business/Street | SecondaryAddress/Street | WorkAddress/Street | Home/Street | SecondaryAddress/Street | BusinessAddress/Street | AddressType | District | Zone...">
  <!-- apply the same transform to all of these -->
</xsl:template>

我是否坚持难以阅读、调试和维护 xslt 是更好的方法吗?

很难回答您的问题,因为它全都与上下文有关 - 而您没有提供完整的上下文。

In the example above for instance, Model2, Model6 and Model8 are the same structure and I need to apply the same transform to each element

你为什么不这样做:

<xsl:template match="Model2 | Model6 |  Model8">
    <!-- apply the same transform to all of these -->
</xsl:template>

请注意,此模板可以从作为父节点的任何节点应用 a Model2 and/or Model6 and/or Model8(实际上,它也可以从其他上下文中应用,但这可能不是这里的重点)。

How can I write one template that can then be called from inside of each of the 50 templates I need to write

你确定要写50个模板吗?


重新编辑:

替代方案:

<xsl:template match="Address | Business | SecondaryAddress | WorkAddress | Home | SecondaryAddress | BusinessAddress | ...">
    <!-- apply the same transform to all of these -->
</xsl:template>

将是:

<xsl:template name="address">
    <xsl:param name="address-node" />
    <!-- apply the transform  -->
</xsl:template>

但是您必须从存在地址的每个位置显式调用此模板,例如:

<xsl:template match="Seller">
    <xsl:copy>
        <!-- other code -->
        <xsl:call-template name="address">
            <xsl:with-param name="address-node" select="BusinessAddress"/>
        </xsl:call-template>
        <xsl:call-template name="address">
            <xsl:with-param name="address-node" select="DistributionAddress"/>
        </xsl:call-template>
        <xsl:call-template name="address">
            <xsl:with-param name="address-node" select="MainContactAddress"/>
        </xsl:call-template>
        <!-- more code -->
    </xsl:copy>
</xsl:template>    

可能,这可以缩短为:

<xsl:template match="Seller">
    <xsl:copy>
        <!-- other code -->
        <xsl:call-template name="address">
            <xsl:with-param name="address-nodes" select="BusinessAddress | DistributionAddress | MainContactAddress"/>
        </xsl:call-template>
        <!-- more code -->
    </xsl:copy>
</xsl:template>   

即便如此,我不确定替代方案是否比最初的建议更有吸引力。

如果您可以访问模式感知 XSLT 2.0 处理器,这将变得非常简单。您可以定义一个模板规则来匹配具有给定类型 T 的所有元素,如下所示:

<xsl:template match="element(*, T)">

或者如果元素都在模式中定义为属于一个替换组,其头部是元素H,您可以写

<xsl:template match="schema-element(H)">

没有架构感知处理器,您必须定义联合模式

<xsl:template match="A|B|C|D|E|F....">

并记得在每次架构更改时更改它。