Преобразование XML с помощью XSLT

У меня есть следующий образец XML:

<finding>
    <title>Found something</title>
    <heading>Severity:</heading>
    <text>Really low.</text>
    <heading>URL:</heading>
    <text>https://www.something.com:443</text>
    <heading>Description:</heading>
    <text>We have found an issue</text>
    <heading>Impact:</heading>
    <text>This is bad.</text>
    <heading>Recommendations:</heading>
    <text>Fix it!.</text>
</finding>

Легко ли это сделать с помощью XSLT? Я использую Python с lxml, если это помогает. Я хотел бы иметь XSLT, который даст мне следующее:

<finding>
    <title>Found something</title>
    <severity>Really low.</severity>
    <url>https://www.something.com:443</url>
    <description>We have found an issue</description>
    <impact>This is bad.</impact>
    <recommendations>Fix it!</recommendations>
</finding>

Спасибо!


person user884725    schedule 08.08.2011    source источник
comment
Да, я думаю, что XSLT — хороший выбор!   -  person Martin Vejmelka    schedule 09.08.2011
comment
Хороший вопрос, +1. См. мой ответ для решения, которое правильно обрабатывает даже такие элементы <heading>, которые содержат произвольные небуквенно-цифровые символы.   -  person Dimitre Novatchev    schedule 09.08.2011


Ответы (4)


XSLT был бы довольно приличным способом сделать это. Селектор для узлов text немного сложен, но шаблон в следующих строках должен сделать это (просто добавьте его модифицированные версии для других заголовков).

<xsl:template match="text[preceding-sibling::heading[1][text()='Description:']]">
    <description><xsl:value-of select="." /></description>
</xsl:template>

Обратите внимание, что таблица стилей идентификации, вероятно, является разумной отправной точкой для добавления ваших шаблонов.

person Greg Beech    schedule 08.08.2011

С XSLT 2.0:

<!-- Identity Template / Copy by default -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="heading">
    <xsl:element name="{lower-case(replace(., ':', ''))}">
        <xsl:value-of select="following-sibling::text[1]"/>
    </xsl:element>
</xsl:template>

<xsl:template match="text"/>

Give you the exact output.

person fbdcw    schedule 08.08.2011
comment
См. решение empo для использования substring-before вместо replace. В этом случае намного лучше для производительности и совместимости с XSLT 1.0. - person fbdcw; 09.08.2011

Вы можете использовать три шаблона:

Во-первых, шаблон преобразование идентичности для копирования всех узлов как есть

Затем два правила переопределяют обязательные элементы следующим образом:

 <xsl:template match="finding/heading">
  <xsl:element name="{lower-case(substring-before(.,':'))}">
    <xsl:value-of select="following-sibling::text[1]"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="finding/*[not(self::heading or self::title)]"/> 

где lower-case() — функция XPath 2.0. Для обработки XPath 1.0 нижний регистр вы можете увидеть ответы в этом вопрос.

person Emiliano Poggi    schedule 08.08.2011

Это преобразование XSLT 1.0 корректно обрабатывает даже документы, в которых <heading> содержит произвольные небуквенно-цифровые символы:

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

 <xsl:variable name="vUpper" select=
  "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>

 <xsl:variable name="vLower" select=
  "'abcdefghijklmnopqrstuvwxyz'"/>

 <xsl:variable name="vDigits" select=
  "'0123456789'"/>

 <xsl:variable name="vAlhpaNum" select=
  "concat($vUpper, $vLower, $vDigits)"/>

 <xsl:template match=
  "node()[not(self::heading or self::text)]|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="heading">
  <xsl:element name=
  "{translate(translate(.,translate(.,$vAlhpaNum,''),''),
              $vUpper,
              $vLower
             )
   }">
   <xsl:value-of select="following-sibling::text[1]"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="text"/>
</xsl:stylesheet>

Применительно к следующему XML-документу (на основе предоставленного, но более сложного):

<finding>
    <title>Found something</title>
    <heading>Severity...</heading>
    <text>Really low.</text>
    <heading>URL?</heading>
    <text>https://www.something.com:443</text>
    <heading>Description:</heading>
    <text>We have found an issue</text>
    <heading>Impact!:</heading>
    <text>This is bad.</text>
    <heading>Recommendations!!!</heading>
    <text>Fix it!.</text>
</finding>

получен желаемый правильный результат:

<finding>
   <title>Found something</title>
   <severity>Really low.</severity>
   <url>https://www.something.com:443</url>
   <description>We have found an issue</description>
   <impact>This is bad.</impact>
   <recommendations>Fix it!.</recommendations>
</finding>
person Dimitre Novatchev    schedule 09.08.2011