在现有文档的特定位置插入XML节点

时间:2009-05-14 12:05:50

标签: xml xslt insert xsd

我有一个带有一些可选节点的现有XML文档,我想插入一个新节点,但是在某个位置。

该文件看起来像这样:

<root>
  <a>...</a>
  ...
  <r>...</r>
  <t>...</t>
  ...
  <z>...</z>
</root>

应在节点<s>...</s><r>之间插入新节点(<t>),从而产生:

<root>
  <a>...</a>
  ...
  <r>...</r>
  <s>new node</s>
  <t>...</t>
  ...
  <z>...</z>
</root>

问题是,现有节点是可选的。因此,我无法使用XPath查找节点<r>并在其后插入新节点。

我想避免使用“强力方法”:从<r>搜索到<a>以查找存在的节点。

我还想保留顺序,因为XML文档必须符合XML模式。

可以使用XSLT以及普通的XML库,但由于我只使用Saxon-B,因此不能选择模式感知的XSLT处理。

有没有人知道如何插入这样的节点?

thx,MyKey _

3 个答案:

答案 0 :(得分:19)

[取代我的上一个答案。现在我更了解你需要什么。]

这是一个XSLT 2.0解决方案:

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="/root">
    <xsl:variable name="elements-after" select="t|u|v|w|x|y|z"/>
    <xsl:copy>
      <xsl:copy-of select="* except $elements-after"/>
      <s>new node</s>
      <xsl:copy-of select="$elements-after"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

您必须明确列出之后的元素或之前的元素。 (你不必同时列出两者。)我倾向于选择两个列表中较短的一个(因此在上例中为“t” - “z”,而不是“a” - “r”)。

可选增强:

这可以完成工作,但是现在需要在两个不同的位置(在XSLT和模式中)维护元素名称列表。如果它变化很大,那么它们可能会失去同步。如果向模式添加新元素但忘记将其添加到XSLT,则不会复制它。如果您对此感到担心,可以实现自己的架构感知。假设您的架构如下所示:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="a" type="xs:string"/>
        <xs:element name="r" type="xs:string"/>
        <xs:element name="s" type="xs:string"/>
        <xs:element name="t" type="xs:string"/>
        <xs:element name="z" type="xs:string"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>

现在您需要做的就是更改$ elements-after变量的定义:

  <xsl:variable name="elements-after" as="element()*">
    <xsl:variable name="root-decl" select="document('root.xsd')/*/xs:element[@name eq 'root']"/>
    <xsl:variable name="child-decls" select="$root-decl/xs:complexType/xs:sequence/xs:element"/>
    <xsl:variable name="decls-after" select="$child-decls[preceding-sibling::xs:element[@name eq 's']]"/>
    <xsl:sequence select="*[local-name() = $decls-after/@name]"/>
  </xsl:variable>

这显然更复杂,但现在您不必在代码中列出任何元素(“s”除外)。每当您更改架构时,脚本的行为都会自动更新(特别是,如果您要添加新元素)。这是否过度取决于您的项目。我只是将其作为可选附件提供。 : - )

答案 1 :(得分:0)

您必须使用暴力搜索,因为您没有静态路径来查找插入位置。我的方法是使用SAX解析器并读取文档。所有节点都未经修改地复制到输出中。

你需要一个标志sWasWritten,这就是为什么你不能使用普通的XSLT工具;你需要一个可以修改变量的地方。

只要我看到一个节点&gt; rtu,...,z)或根节点的结束标记,我会写s节点,除非sWasWrittentrue并设置了标记sWasWritten

答案 2 :(得分:0)

XPath解决方案:

/root/(.|a|r)[position()=last()]

您必须明确包含所需的所有节点,这样您就需要为要插入的每个节点提供不同的XPath表达式。例如,要在<t>之后立即放置它(如果存在):

/root/(.|a|r|t)[position()=last()]

请注意当前任何节点都不存在时的特殊情况:它返回<root>(“。”)。您需要检查这一点,并将新节点作为root的第一个子节点插入,而不是在它之后(通常情况下)。这并不是那么糟糕:无论如何,你必须以某种方式处理这种特殊情况。另一种处理这种特殊情况的方法是:如果没有前面的节点,则返回0个节点。

/root/(.|a|r|t)[position()=last() and position()!=1]

挑战:你能找到更好的方法来处理这种特殊情况吗?