【问题标题】:Making ID attributes unique in XML使 ID 属性在 XML 中唯一
【发布时间】:2014-05-30 21:47:56
【问题描述】:

我有一个合并的 XML 文件(为简单起见),格式如下:

<bookstore>
    <books>
        <book id="1"/>
        <book id="2"/>
        <book id="2"/>
        <book id="3"/>
        <book id="10"/>
    </books>
</bookstore>

由于 XML 被合并,所以存在具有相同 ID 属性的书籍。我需要按照以下规则使 ID 唯一:如果遇到(从上到下)已被占用的 ID,则将此 ID 更改为 MAX(ID)+1.

<bookstore>
    <books>
        <book id="1"/>
        <book id="2"/>
        <book id="11"/>
        <book id="3"/>
        <book id="10"/>
    </books>
</bookstore>

执行此操作的一种直接方法是提取 ID,检查它们的出现情况,如果出现多次,则搜索 ID 的第二次出现(从上到下)并替换它。但这不是很优雅……

当我正在阅读有关 XML 处理的信息时,我希望有一个(简单的)XQuery 可以做到这一点。 如果有人有一些指针或伪代码:他们都是受欢迎的。

我的环境是Oracle(PL)SQL数据库,支持XMLTYPEXQuery

【问题讨论】:

  • 您可以使用 XSLT 转换文档,它有一个generate-id() 函数,允许您根据节点表达式生成唯一的 ID。您可以通过包含一个简单模板的非常短的 XSLT 文档来实现这一点,该模板复制每个节点并将每个 bookid 属性内容替换为由 generate-id() 函数创建的序列。

标签: sql xml oracle plsql xquery


【解决方案1】:

在您的环境中,您可以使用 XSLT 1.0 转换文档并在此过程中生成 ID。请参阅:DBMS_XSLPROCESSOR

使用 XSLT 样式表,您可以将节点从 XML 源复制到结果树,在此过程中创建唯一 ID。 ID 不是序列号,而是由generate-id() 方法生成的唯一字符串序列。你无法控制它们的外观,但你可以保证它们是独一无二的。 (如果这是您的意图,XSLT 还允许您删除重复节点(使用 key),但从您的示例中我了解到重复的 *ID*s 实际上并不意味着节点是重复的,因为您想为其生成一个新 ID。)

下面的样式表有两个模板。第二个是恒等变换:它只是将元素和属性复制到结果树。第一个模板创建一个名为 id 的属性,其中包含一个唯一 ID。

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

    <xsl:template match="book">
        <xsl:copy>
            <xsl:attribute name="id">
                <xsl:value-of select="generate-id(.)"/>
            </xsl:attribute>
            <xsl:apply-templates select="node()|@*[name() != 'id']"/>
        </xsl:copy>
    </xsl:template>

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

</xsl:stylesheet>

为所有节点和属性调用其他模板(在这种情况下只有标识模板),除了&lt;xsl:apply-templates ...&gt;id 属性。结果是原始 XML 文件的副本,其中包含为 book 元素生成的唯一 ID。

如果您有这样的 XML:

<bookstore>
    <books>
        <book id="1" other="123"/>
        <book id="2"/>
        <book id="2"/>
        <book id="3">
            <chapter number="123" id="ch1">Text</chapter>
        </book>
        <book id="10"/>
    </books>
    <magazines>
        <mag id="non-book-id"></mag>
    </magazines>
</bookstore>

上面的 XSLT 会将它转换成这个 XML:

<bookstore>
   <books>
      <book id="d2e3" other="123"/>
      <book id="d2e4"/>
      <book id="d2e5"/>
      <book id="d2e6">
         <chapter number="123" id="ch1">Text</chapter>
      </book>
      <book id="d2e9"/>
   </books>
   <magazines>
      <mag id="non-book-id"/>
   </magazines>
</bookstore>

(字符串序列是任意的,在您的实现中可能会有所不同)。

对于创建 ID/IDREF 链接,生成的字符串序列比数字更好,因为您可以在任何地方使用它们(以数字开头的数字和标识符不能始终用作 ID)。但是如果字符串序列不可接受并且您需要连续数字,则可以使用 XQuery 或 XSLT 中的 XPath 节点 position() 根据元素在整个文档中的位置生成数字 em> (这将是唯一的)。如果所有书籍都是同一个上下文中的兄弟,你可以简单地将上面样式表中的generate-id(.)替换为position()

<xsl:template match="book">
    <xsl:copy>
        <xsl:attribute name="id">
            <xsl:value-of select="position()"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*[name() != 'id']"/>
    </xsl:copy>
</xsl:template>

(如果 books 不是同级的,则需要以稍微不同的方式进行操作,使用变量)。

如果您想保留现有的 ID 并且只为重复的 ID 生成顺序 ID,这会有点复杂,但您可以使用键(或 XQuery 而不是 XSLT)来实现。在 XPath 2.0 中使用max() 函数可以获得最大的id

max(//book/@id)

XPath 1.0 中不存在该功能,但您可以通过以下方式获取最大 ID:

//book[not(@id < //book/@id)]/@id

【讨论】:

  • 您有什么理由选择 XML 转换而不是 XQuery 更新工具?据我所知,XSLT 用于转换文档(甚至结构),XQuery Update Facility 用于修改其内容。 “转换”可能被视为包含/封闭“修改”,但对于这里需要的东西来说似乎有点矫枉过正?还是我错了?
  • @davor 您可能是对的,这个问题当然值得 XQuery 回答。关于 Oracle PL/SQL 我只是一个用户。我使用 XSLT 是因为我对它更有经验,并且认为它是解决此问题的可能 解决方案。不过,可能不是最好的解决方案。如果您可以添加 XQuery 答案,那就太好了。
  • 最终我采用了程序化 PL/SQL 和 DOM 方法。 XQuery Update Facility 自 Oracle 12cR1 起才受支持,所以我没有真正深入了解它的细节。正如here 所讨论的,这种看似简单的更新对于纯 SQL 来说甚至都不是微不足道的。注意:需要唯一的整数,而不仅仅是唯一的字符串,那些没有重复的字符串可能不会被更改。在出现更好的情况之前,我会将您的答案标记为已接受。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 2015-05-10
  • 2011-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-25
相关资源
最近更新 更多