【问题标题】:XSLT, set namespace dynamically and remove name space from output xmlXSLT,动态设置名称空间并从输出 xml 中删除名称空间
【发布时间】:2016-06-26 00:42:06
【问题描述】:

我的源xml是:

<?xml version="1.0" encoding="UTF-8"?>
<PMML version="4.1" xmlns="http://www.dmg.org/PMML-4_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dmg.org/PMML-4_1 pmml-4-1.xsd">
<Header copyright="(C) Copyright IBM Corp. 1989, 2014.">
    <Application name="IBM SPSS Statistics 23.0" version="23.0.0.0"/>
</Header>
<GeneralRegressionModel algorithmName="multinomialLogistic" functionName="classification" modelType="multinomialLogistic" targetVariableName="CLASS">
    <MiningSchema>
        <MiningField missingValueTreatment="asIs" name="CLASS" usageType="predicted"/>
        <MiningField missingValueTreatment="asIs" name="ACTIVE_CUSTOMER" usageType="active"/>
        <MiningField missingValueTreatment="asIs" name="SEGMENT" usageType="active"/>
    </MiningSchema>
    <ParameterList>
        <Parameter label="Konstanter Term" name="P0000001"/>
        <Parameter label="[ACTIVE_CUSTOMER=0]" name="P0000002"/>
        <Parameter label="[ACTIVE_CUSTOMER=1]" name="P0000003"/>
        <Parameter label="[SEGMENT=0]" name="P00000004"/>
        <Parameter label="[SEGMENT=1]" name="P00000005"/>
    </ParameterList>
    <ParamMatrix>
        <PCell beta="-167.307903919999" df="1" parameterName="P0000001" targetCategory="1"/>
        <PCell beta="-0.0747629275586869" df="1" parameterName="P0000002" targetCategory="1"/>
        <PCell beta="0.409965797830495" df="1" parameterName="P0000003" targetCategory="1"/>
        <PCell beta="-1.03190717557433" df="1" parameterName="P0000004" targetCategory="1"/>
        <PCell beta="0.904157514089376" df="1" parameterName="P0000005" targetCategory="1"/>
    </ParamMatrix>
</GeneralRegressionModel>
</PMML>

我的输出 xml 是:

<?xml version="1.0" encoding="utf-8"?>
<Predictors xmlns:ns="some:ns" xmlns:rs="http://www.dmg.org/PMML-4_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Predictor coefficient="-167.307903919999" name="__INTERCEPT__" value=""/>
  <Predictor coefficient="-0.0747629275586869" name="ACTIVE_CUSTOMER" value="0"/>
  <Predictor coefficient="0.409965797830495" name="ACTIVE_CUSTOMER" value="1"/>
  <Predictor coefficient="" name="SEGMENT" value="0"/>
  <Predictor coefficient="" name="SEGMENT" value="1"/>
</Predictors>

我可以通过以下 xslt 实现这一点:

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" xmlns:ns="some:ns" xmlns:rs="http://www.dmg.org/PMML-4_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
  <xsl:output encoding="utf-8" indent="yes" method="xml"/>
  <xsl:strip-space elements="*"/>

  <xsl:key match="rs:ParamMatrix/rs:PCell" name="cell" use="@parameterName"/>
  <xsl:key match="rs:DataDictionary/rs:DataField" name="dataField" use="@name"/>

  <!-- identity transform -->
  <xsl:template match="node()|@*">
     <xsl:apply-templates select="node()|@*"/>
  </xsl:template>

  <xsl:template match="rs:GeneralRegressionModel">
    <!--MiningSchema-->
    <xsl:apply-templates select="rs:MiningSchema"/>

    <!--RegressionTable for predicted targetVariable targetCategory-->
    <Predictors>
      <xsl:apply-templates select="rs:ParameterList/rs:Parameter"/>
    </Predictors>

  </xsl:template>

    <xsl:template match="rs:Parameter[not(contains(@label, '='))][@name='P0000001']">
    <Predictor coefficient="{key('cell', @name)/@beta}" name="__INTERCEPT__" value=""/>
  </xsl:template>

  <xsl:template match="rs:Parameter[not(contains(@label, '='))][@name!='P0000001']">
    <Predictor coefficient="{key('cell', @name)/@beta}" name="{@label}" value=""/>
  </xsl:template>

  <xsl:template match="rs:Parameter[contains(@label, '=')]" name="split">
    <Predictor coefficient="{key('cell', @name)/@beta}" name="{substring-after(substring-before(@label,'='),'[')}" value="{substring-before(substring-after(@label,'='),']')}"/>
  </xsl:template>

</xsl:transform>

此 XSLT 有效。 但是,我有两个问题: 1. 源xml开头,有命名空间,如'xmlns="http://www.dmg.org/PMML-4_1"',可以是其他值。整个文档仅使用这一个命名空间。目前在我的 xslt 中,我将命名空间设置为固定值 'xmlns:rs="http://www.dmg.org/PMML-4_1" ',这是不正确的。如何在 xslt 中动态设置命名空间?

  1. 在 xslt 中设置命名空间后,它也会显示在输出 xml 中。如何从输出 xml 中删除此命名空间?

如果可以的话,能否请您直接修改我的 xslt 以显示用法?

非常感谢!!!

【问题讨论】:

  • "在源xml的开头,有命名空间,比如'xmlns="dmg.org/PMML-4_1"',可以是其他值。"你能解释一下如何真的有效吗?命名空间是 XML 模式的一部分——它不应该随意改变。你至少有一个可能的命名空间“银行”吗? -- 关于你的第二个问题:使用exclude-result-prefixes="rs"。并删除 identity transform 模板:您不会从源 XML 复制任何内容,也不想从源 XML 复制任何内容 - 否则您也会复制其命名空间。
  • 请注意,您的评论标识为identity transform 的模板不是身份转换,因为它不使用xsl:copy,而仅使用&lt;xsl:apply-templates select="node()|@*"/&gt;。至于命名空间是动态的问题,对于 XSLT 2.0 处理器,您可以使用 *:foo,例如&lt;xsl:template match="*:GeneralRegressionModel"&gt;&lt;xsl:apply-templates select="*:MiningSchema"/&gt;...,但它同意这个对任意命名空间的要求听起来很奇怪。
  • @michael.hor257k 我正在处理由统计软件生成的 XML。如果 XML 是由使用旧版本软件的人发送给我的,命名空间可能是 'xmlns="dmg.org/PMML-4_0"',如果使用新版本的软件,则名称空间可能是 'xmlns= “dmg.org/PMML-4_1”。对我的要求是,无论是哪个版本的软件,xslt 都应该可以工作。
  • @michael.hor257k 我简化了源 xml 和目标 xml,以使这里的问题更容易发布。我确实需要身份转换来复制源 xml 的某些部分。
  • @michael.hor257k @Martin Honnen 我不想忽略命名空间。我仍然使用 XSLT 1.0。我在我的程序中使用这个 xslt。如果我可以解析源 xml 文件以获取命名空间,我可以将此命名空间用作 xslt 中的输入参数吗?如果是,你认为这是一种可行的方法吗?你介意告诉我怎么做吗? ---- 非常感谢

标签: xml xslt namespaces


【解决方案1】:

在名称空间中创建一个直到运行时才知道的元素:

(a) 将任何文字结果元素(例如 &lt;Predictor/&gt;)更改为 &lt;xsl:element name="Predictor" namespace="{$ns}'/&gt;

(b) 将&lt;xsl:copy/&gt; 的任何用法更改为&lt;xsl:element name="{local-name()}" namespace="{$ns}'/&gt;

(c) 将&lt;xsl:copy-of/&gt; 的任何使用更改为使用上述&lt;xsl:element/&gt; 修改后的身份模板的递归副本。

或者,与控制此 XML 词汇表的人交谈,并询问他们为什么以这种方式滥用命名空间。

【讨论】:

  • 我相信他们描述的是一种事先不知道 source 命名空间的情况。
【解决方案2】:

如果您将以下样式表应用于您的输入 XML:

XSLT 1.0 (a)

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="*[namespace-uri()=/*/namespace::*[not(name())]]">
    <xsl:element name="{local-name()}" namespace="urn:x-my:constant-namespace">
        <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
</xsl:template>

</xsl:stylesheet>

它将(未知)默认命名空间中的所有元素移动到已知且恒定的命名空间urn:x-my:constant-namespace

<?xml version="1.0" encoding="UTF-8"?>
<PMML xmlns="urn:x-my:constant-namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="4.1" xsi:schemaLocation="http://www.dmg.org/PMML-4_1 pmml-4-1.xsd">
  <Header copyright="(C) Copyright IBM Corp. 1989, 2014.">
    <Application name="IBM SPSS Statistics 23.0" version="23.0.0.0"/>
  </Header>
  <GeneralRegressionModel algorithmName="multinomialLogistic" functionName="classification" modelType="multinomialLogistic" targetVariableName="CLASS">
    <MiningSchema>
      <MiningField missingValueTreatment="asIs" name="CLASS" usageType="predicted"/>
      <MiningField missingValueTreatment="asIs" name="ACTIVE_CUSTOMER" usageType="active"/>
      <MiningField missingValueTreatment="asIs" name="SEGMENT" usageType="active"/>
    </MiningSchema>
    <ParameterList>
      <Parameter label="Konstanter Term" name="P0000001"/>
      <Parameter label="[ACTIVE_CUSTOMER=0]" name="P0000002"/>
      <Parameter label="[ACTIVE_CUSTOMER=1]" name="P0000003"/>
      <Parameter label="[SEGMENT=0]" name="P00000004"/>
      <Parameter label="[SEGMENT=1]" name="P00000005"/>
    </ParameterList>
    <ParamMatrix>
      <PCell beta="-167.307903919999" df="1" parameterName="P0000001" targetCategory="1"/>
      <PCell beta="-0.0747629275586869" df="1" parameterName="P0000002" targetCategory="1"/>
      <PCell beta="0.409965797830495" df="1" parameterName="P0000003" targetCategory="1"/>
      <PCell beta="-1.03190717557433" df="1" parameterName="P0000004" targetCategory="1"/>
      <PCell beta="0.904157514089376" df="1" parameterName="P0000005" targetCategory="1"/>
    </ParamMatrix>
  </GeneralRegressionModel>
</PMML>

然后您可以将第二个样式表应用于结果,例如:

XSLT 1.0 (b)

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rs="urn:x-my:constant-namespace" 
exclude-result-prefixes="rs">
<xsl:output encoding="utf-8" indent="yes" method="xml"/>
<xsl:strip-space elements="*"/>

<xsl:key match="rs:PCell" name="cell" use="@parameterName"/>

<xsl:template match="/">
    <Predictors>
          <xsl:apply-templates/>
    </Predictors>
</xsl:template>

<xsl:template match="rs:Parameter[not(contains(@label, '='))][@name='P0000001']">
    <Predictor coefficient="{key('cell', @name)/@beta}" name="__INTERCEPT__" value=""/>
</xsl:template>

<xsl:template match="rs:Parameter[not(contains(@label, '='))][@name!='P0000001']">
    <Predictor coefficient="{key('cell', @name)/@beta}" name="{@label}" value=""/>
</xsl:template>

<xsl:template match="rs:Parameter[contains(@label, '=')]">
    <Predictor coefficient="{key('cell', @name)/@beta}" name="{substring-after(substring-before(@label,'='),'[')}" value="{substring-before(substring-after(@label,'='),']')}"/>
</xsl:template>

</xsl:stylesheet>

并接收:

<?xml version="1.0" encoding="utf-8"?>
<Predictors>
  <Predictor coefficient="-167.307903919999" name="__INTERCEPT__" value=""/>
  <Predictor coefficient="-0.0747629275586869" name="ACTIVE_CUSTOMER" value="0"/>
  <Predictor coefficient="0.409965797830495" name="ACTIVE_CUSTOMER" value="1"/>
  <Predictor coefficient="" name="SEGMENT" value="0"/>
  <Predictor coefficient="" name="SEGMENT" value="1"/>
</Predictors>

【讨论】:

    【解决方案3】:

    如果您希望使 xsl 命名空间不可知,以便您可以任意更改输入 xml 的命名空间,那么您需要分两个阶段运行转换。

    如果您在 xsl 中包含以下内容并从 xsl 中删除所有命名空间引用 - uri 和前缀(除了您希望在输出中看到的那些)

    <xsl:template match="@*" mode="stripNS">
        <xsl:attribute name="{local-name(.)}"><xsl:value-of select="."/></xsl:attribute>
    </xsl:template>
    <xsl:template match="node()" mode="stripNS">
        <xsl:element name="{local-name()}">
            <xsl:apply-templates select="node()|@*" mode="stripNS"/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="/">
        <xsl:variable name="nakedXML">
            <xsl:apply-templates mode="stripNS"/>
        </xsl:variable>
        <xsl:apply-templates select="$nakedXML/*" />
    </xsl:template>
    

    无论输入命名空间如何,根目录上的匹配项始终是要执行的初始模板。然后,xml 将使用 将输入 xml 的表示创建到变量 $nakedXML 中,所有命名空间都会被剥离。

    从这一点你可以将 对抗nakedXML。 注意,一些 xsl 处理器会要求您使用合适的 node-set() 函数包装 $nakeXML - 每个处理器处理它的方式不同,因此请查看文档。

    我应该补充一点,我并不完全赞同这种技术。它对性能有重大影响,并且剥离命名空间可能会在以后造成混乱。 IMO,当使用命名空间编写内容时,应始终使用该命名空间来引用它。

    【讨论】:

    • "如果您希望使 xsl 命名空间不可知,以便您可以任意更改输入 xml 的命名空间,那么您需要分两个阶段运行转换。 i>" 不,这没有必要。 -- 不过,我同意忽略命名空间是一种黑客行为,应尽可能避免。
    • @michael.hor257k - 嗯,现在你让我思考了。当然,当输入绑定到命名空间时,不会触发 NS-free 匹配表达式?我(还)可以一口气看到它是如何实现的。
    • 阅读上面 Martin Honnen 的评论。也有一个 XSLT 1.0 等价物。
    • 就我所见,xsl 1.0 等效项是匹配诸如 this-*[name()='GeneralRegressionModel'] 之类的表达式。不确定我是否喜欢这个,因为这些表达式可能会变得丑陋,但它可以一次性完成。但是感谢@michael.hor257k 提到它,否则我什至不会尝试这种技术
    • @Phil B 非常感谢您的重播。我真的不想忽略命名空间。如果我可以首先解析 xml 以找出这个特定 xml 文件的命名空间,我可以使用这个命名空间作为输入参数吗?怎么样?
    【解决方案4】:

    根据您在我之前的回答中的评论:

    当您需要获取当前 节点的命名空间时,您应该遍历命名空间轴。如果我们假设 all 您的文档命名空间在根节点中声明,您可以使用 xpath "/*/namespace::*" 来获取所有命名空间的节点集。

    因此,对于您的示例输入,类似这样的内容...

      <xsl:for-each select="/*/namespace::*">
           <namespace prefix="{name()}" uri="{.}"/>
      </xsl:for-each>
    

    会给你

    <namespace prefix="" uri="http://www.dmg.org/PMML-4_1"/>
    <namespace prefix="xsi" uri="http://www.w3.org/2001/XMLSchema-instance"/>
    <namespace prefix="xml" uri="http://www.w3.org/XML/1998/namespace"/>
    

    如果你只想要根节点的默认命名空间 URI:

    <xsl:value-of select="/*/namespace::*[not(name())]"/>
    

    【讨论】:

    • 感谢您的帮助。不过,在迈克尔发布上面的脚本之前,我真的在语法上苦苦挣扎。 ´match="*[namespace-uri()=/*/namespace::*[not(name())]]" ´,难道只有我觉得难以理解吗?我会继续努力的!
    最近更新 更多