开发者

Is there a way to seek a specific location in an xml by knowing the index relative to the text nodes in java?

开发者 https://www.devze.com 2022-12-25 19:17 出处:网络
I have a xml document. I know the index where i need to insert the new node. The index that i have is the position considering only the text nodes and ignoring the element tags. Is there a java api to

I have a xml document. I know the index where i need to insert the new node. The index that i have is the position considering only the text nodes and ignoring the element tags. Is there a java api to seek the index in a xml knowing the position relative to the text nod开发者_如何学Pythones alone and insert a new node in that position?


Here is an XSLT 2.0 stylesheet that takes two parameters, the index at which you want to insert (using XSLT/XPath indexing scheme where the index starts with one, not with zero), and the node(s) to insert:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xsd"
  version="2.0">

  <!-- XPath/XSLT index starts with 1 -->
  <xsl:param name="index" as="xsd:integer" select="11"/>
  <xsl:param name="new" as="node()+"><e/></xsl:param>

  <xsl:variable name="text-to-split" as="text()?"
     select="descendant::text()[sum((preceding::text(), .)/string-length(.)) ge $index][1]"/>

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

  <xsl:template match="text()[. is $text-to-split]">
    <xsl:variable name="split-index" as="xsd:integer"
      select="$index - sum(preceding::text()/string-length(.))"/>
    <xsl:value-of select="substring(., 1, $split-index - 1)"/>
    <xsl:copy-of select="$new"/>
    <xsl:value-of select="substring(., $split-index)"/>
  </xsl:template>

</xsl:stylesheet>

You can use Saxon 9 to run XSLT 2.0 stylesheets with Java. [edit] Here is an attempt to solve this with XSLT 1.0:

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

  <!-- XPath/XSLT index starts with 1 -->
  <xsl:param name="index" select="11"/>
  <xsl:param name="new"><e/></xsl:param>

  <xsl:template name="find-text-to-split">
    <xsl:param name="text-nodes"/>
    <xsl:variable name="sum">
      <xsl:call-template name="make-sum">
        <xsl:with-param name="nodes" select="$text-nodes[1]/preceding::text() | $text-nodes[1]"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$sum &gt;= $index">
        <xsl:value-of select="generate-id($text-nodes[1])"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="find-text-to-split">
          <xsl:with-param name="text-nodes" select="$text-nodes[position() &gt; 1]"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="make-sum">
    <xsl:param name="nodes"/>
    <xsl:param name="length" select="0"/>
    <xsl:choose>
      <xsl:when test="not($nodes)">
        <xsl:value-of select="$length"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="make-sum">
          <xsl:with-param name="nodes" select="$nodes[position() &gt; 1]"/>
          <xsl:with-param name="length" select="$length + string-length($nodes[1])"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:variable name="text-to-split-id">
    <xsl:call-template name="find-text-to-split">
      <xsl:with-param name="text-nodes" select="descendant::text()"/>
    </xsl:call-template>
  </xsl:variable>

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

  <xsl:template match="@* | comment() | processing-instruction()">
    <xsl:copy/>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:choose>
      <xsl:when test="generate-id() = $text-to-split-id">
        <xsl:variable name="sum">
          <xsl:call-template name="make-sum">
            <xsl:with-param name="nodes" select="preceding::text()"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="split-index"
          select="$index - $sum"/>
        <xsl:value-of select="substring(., 1, $split-index - 1)"/>
        <xsl:copy-of select="$new"/>
        <xsl:value-of select="substring(., $split-index)"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:copy/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

Note that the XSLT 1.0 solution is not quite complete, it might recurse to a stack overflow if the index passed in is greater than any existing text index in the document.


The following XSLT 2.0 stylesheet is an attempt to extend the solution in the original XSLT 2.0 stylesheet to receive a list of index positions and nodes to be inserted with one transformation:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xsd"
  version="2.0">

  <xsl:param name="insert-file" as="xsd:string" select="'insert-data.xml'"/>

  <xsl:variable name="main-root" as="document-node()" select="/"/>

  <xsl:variable name="insert-data" as="element(data)*">
    <xsl:for-each-group select="doc($insert-file)/insert-data/data" group-by="xsd:integer(@index)">
      <xsl:sort select="current-grouping-key()"/>
      <data index="{current-grouping-key()}" text-id="{generate-id($main-root/descendant::text()[sum((preceding::text(), .)/string-length(.)) ge current-grouping-key()][1])}">
        <xsl:copy-of select="current-group()/node()"/>
      </data>
    </xsl:for-each-group>
  </xsl:variable>

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

  <xsl:template match="text()[generate-id() = $insert-data/@text-id]">
    <xsl:variable name="preceding-text" as="xsd:integer" select="sum(preceding::text()/string-length(.))"/>
    <xsl:variable name="this" as="text()" select="."/>
    <xsl:variable name="insert-here" as="element(data)+">
      <xsl:for-each select="$insert-data[@text-id = generate-id(current())]">
        <data split-index="{@index - $preceding-text}">
          <xsl:copy-of select="node()"/>
        </data>
      </xsl:for-each>
    </xsl:variable>

    <xsl:for-each select="$insert-here">
      <xsl:variable name="pos" as="xsd:integer" select="position()"/>
      <xsl:value-of select="substring(
        $this, 
        if ($pos eq 1) then 1 else xsd:integer($insert-here[$pos - 1]/@split-index), 
        if ($pos ne 1) then xsd:integer(@split-index) - xsd:integer($insert-here[$pos - 1]/@split-index) else xsd:integer(@split-index) - 1)"/>
      <xsl:copy-of select="node()"/>
    </xsl:for-each>

    <xsl:value-of select="substring($this, $insert-here[last()]/@split-index)"/>  
  </xsl:template>

</xsl:stylesheet>

This stylesheet expects a file insert-data.xml to contain the data in the following format:

<insert-data>
  <data index="2"><e/></data>
  <data index="4"><f/></data>
  <data index="6"><g/></data>
  <data index="6"><h/></data>
  <data index="18"><i/></data>
</insert-data>

So each 'data' element contains the nodes to be inserted at the position given by the 'index' attribute.


As such there is no direct API in Java to achieve this. But there is XPATH library and DOM parsing techniques where by using little programing you can achieve this easily

0

精彩评论

暂无评论...
验证码 换一张
取 消