Есть ли способ искать определенное место в XML, зная индекс относительно текстовых узлов в Java? - PullRequest
2 голосов
/ 03 апреля 2010

У меня есть документ XML. Я знаю индекс, где мне нужно вставить новый узел. Индекс, который у меня есть, - это позиция, учитывающая только текстовые узлы и игнорирующая теги элемента. Существует ли Java-API для поиска индекса в xml, который знает положение относительно только текстовых узлов и вставляет новый узел в эту позицию?

Ответы [ 3 ]

2 голосов
/ 04 апреля 2010

Вот таблица стилей XSLT 2.0, которая принимает два параметра: индекс, по которому вы хотите вставить (используя схему индексации XSLT / XPath, где индекс начинается с единицы, а не с нуля), и узлы, которые нужно вставить:

<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>

Вы можете использовать Saxon 9 для запуска таблиц стилей XSLT 2.0 с Java. [редактировать] Вот попытка решить эту проблему с помощью 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>

Обратите внимание, что решение XSLT 1.0 не совсем завершено, оно может привести к переполнению стека, если переданный индекс больше любого существующего текстового индекса в документе.

1 голос
/ 08 апреля 2010

Следующая таблица стилей XSLT 2.0 является попыткой расширить решение в исходной таблице стилей XSLT 2.0, чтобы получить список позиций индекса и узлов, которые должны быть вставлены одним преобразованием:

<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>

В этой таблице стилей ожидается, что файл insert-data.xml будет содержать данные в следующем формате:

<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>

Таким образом, каждый элемент «data» содержит узлы, которые нужно вставить в позицию, заданную атрибутом «index».

1 голос
/ 03 апреля 2010

Как таковой, в Java нет прямого API для достижения этой цели. Но есть библиотека XPATH и методы анализа DOM, где с помощью небольшого программирования вы можете легко достичь этого

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...