XSLT — Как сохранить набор элементов, удаленных из исходного документа, для повторного использования

#xml #xslt #saxon

#xml #xslt #saxon

Вопрос:

Учитывая следующий XML:

 <root>
  <group>
    <e1>001</e1>
    <e2>beep</e2>
    <e2>bop</e2>
    <e2>ork</e2>
    <e2>ah</e2>
    <e2>ah</e2>
  </group>
  <group>
    <e1>002</e1>
    <e2>beep</e2>
    <e2>bop</e2>
    <e2>ork</e2>
    <e2>ah</e2>
    <e2>ah</e2>
  </group>
  <group>
    <e1>003</e1>
    <e2>beep</e2>
    <e2>bop</e2>
    <e2>ork</e2>
    <e2>ah</e2>
    <e2>ah</e2>
  </group>
  <group>
    <e1>004</e1>
    <e2>beep</e2>
    <e2>bop</e2>
    <e2>ork</e2>
    <e2>ah</e2>
    <e2>ah</e2>
  </group>
</root>
  

Обратите внимание, что элементы ‘e2’ в каждом элементе ‘group’ одинаковы, и это гарантируется в исходном документе.

Я пытаюсь использовать XSLT для выполнения следующих шагов:

  1. сохраните копию набора элементов ‘e2’,
  2. удалите все элементы ‘group’,
  3. создайте набор элементов группы по умолчанию со вставленным в него набором e2s

Желаемый результат будет выглядеть следующим образом:

 <root>
  <group>
    <e1>default1</e1>
    <e2>beep</e2>
    <e2>bop</e2>
    <e2>ork</e2>
    <e2>ah</e2>
    <e2>ah</e2>
  </group>
  <group>
    <e1>default2</e1>
    <e2>beep</e2>
    <e2>bop</e2>
    <e2>ork</e2>
    <e2>ah</e2>
    <e2>ah</e2>
  </group>
</root>
  

Значения ‘e1’ в исходном документе не имеют значения, а значения ‘e2’ в выходном документе известны заранее и статичны. Динамическими являются только значения ‘e2’, и мне нужно убедиться, что все они есть.

Я уже использовал шаблон, подобный этому, ранее, когда заменял все элементы некоторыми жестко запрограммированными значениями:

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

<!-- Empty Template eliminates all but first 'group' element. -->
<xsl:template match="//group[preceding::group]"></xsl:template>

<xsl:template match="//group">
  <xsl:element name="group">
    <e1>default1</e1>           
    <!-- e2 elements inserted here somehow? -->
  </xsl:element>
  <xsl:element name="group">
    <e1>default2</e1>           
    <!-- e2 elements inserted here somehow? -->
  </xsl:element>
</xsl:template>
  

Я попытался сохранить эти элементы в переменной, но в выходной html ничего не было вставлено:

 <xsl:variable name="e2Elements" select="//group[1]/e2"></xsl:variable>
<xsl:template match="//group">
  <xsl:element name="group">
    <e1>default1</e1>           
    <xsl:copy-of select="$e2Elements" />
  </xsl:element>
</xsl:template>
  

Но я не уверен, как вставить элементы e2 в значения. Я использую SaxonHE9.8N и имею доступ к пространству имен exslt и xslt2.0

Комментарии:

1. Вы упомянули элементы e3, это опечатка?

2. Да, извините. Редактирование выполнено.

Ответ №1:

Ваше решение фактически создает ненужную копию элементов. Вы можете сделать это, не копируя их, вот так:

 <xsl:variable name="e3Elements" select="//group[1]/e2" />
  

Другая неэффективность заключается в том, что <xsl:template match="group[preceding::group]"/> использование предыдущей оси всегда дорого, но особенно в шаблоне. Очевидным улучшением является замена его на previous-sibling (поиск по оси previous-sibling выполняется намного быстрее, чем поиск по предыдущей оси). Но на самом деле вы можете сделать лучше: сделайте это правилом по умолчанию для групп ( <xsl:template match="group"/> ), а другое правило — соответствующим только первой группе ( <xsl:template match="group[1]">... ).

Но на самом деле, также нет необходимости сопоставлять первый элемент group, потому что вы не используете какие-либо из его данных.

Со стилистической точки зрения, <group> предпочтительнее, чем <xsl:element name="group"> просто потому, что это гораздо более читабельно.

Это было бы моим решением на XSLT 3.0:

 <xsl:transform version="3.0" .... expand-text="yes">

  <xsl:template match="/">
    <xsl:variable name="e3Elements" select="//group[1]/e2"/>
    <xsl:for-each select="'default1', 'default2'">
      <group>
        <e1>{.}</e1>
        <xsl:copy-of select="$e3Elements"/>
      </group>
    </xsl:for-each>
  </xsl:template>

</xsl:transform>
  

Комментарии:

1. В качестве примечания я использую xsl:element, потому что это позволило мне указать пространство имен, чтобы сгенерированные элементы не имели атрибутов xmlns: xxx. Фактический код, с которым я работаю, имеет очень большое пространство имен.

Ответ №2:

Оказалось, что мне нужно было сделать мою переменную копией элемента, используя copy-of. Ниже приведено мое решение:

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

<xsl:variable name="e3Elements">
    <xsl:copy-of select="//group[1]/e2" />
</xsl:variable>

<xsl:template match="group[preceding::group]"></xsl:template>

<xsl:template match="group">
    <xsl:element name="group">
        <xsl:element name="e1">default1</xsl:element>
        <xsl:copy-of select="$e3Elements"></xsl:copy-of>
    </xsl:element>
    <xsl:element name="group">
        <xsl:element name="e1">default2</xsl:element>
        <xsl:copy-of select="$e3Elements"></xsl:copy-of>
    </xsl:element>
</xsl:template>
  

Комментарии:

1. Вам не нужно использовать xsl:copy-of в вашей переменной. На самом деле вы можете объявить это как <xsl:variable name="e3Elements" select="//group[1]/e2" /> .