Получение XML-информации из другой XML-структуры через xslt - PullRequest
3 голосов
/ 30 октября 2011

Я использую XSLT 1.0 и пытаюсь сделать следующее:

У меня есть файл 1.xml:

<root>
    <elem1>value1</elem1>
    <elem2>
        <elem3>
            <param1>value2</param1>
            <param2>value3</param2>
        </elem3>
    </elem2>
    <elem4>
        <param3>value4</param3>
    </elem4>
</root>

Теперь клиент передает мне еще один XML-файл, в котором сообщается, какие элементы он хочет, чтобы я ему вернул (может меняться между клиентами), т.е.

<root>
  <RequiredElements>
    <elementName>elem1</elementName>
    <elementName>elem2/elem3/param1</elementName>
  </RequiredElements>
</root>

Что означает, что в этом случае я должен создать еще один XML-файл с такой структурой:

<root>
  <elem1>value1</elem1>
  <elem2>
    <elem3>
      <param1>value2</param1>
    </elem3>
  </elem2>
</root>

Я пытался придумать что-то в xslt (без языков программирования, кроме этого), чтобы получить структуру, которая мне нужна, но не смог сделать это.

Есть идеи или указания, что мне делать?

Спасибо за помощь.

Ответы [ 2 ]

3 голосов
/ 30 октября 2011

Это простое преобразование (менее 30 строк, если параметр-документ не встроен):

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

 <my:paramDoc>
        <root>
          <RequiredElements>
            <elementName>elem1</elementName>
            <elementName>elem2/elem3/param1</elementName>
          </RequiredElements>
        </root>
 </my:paramDoc>

 <xsl:variable name="vPaths" select=
   "document('')/*/my:paramDoc/*/*/*"/>

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

 <xsl:template match="*/*">
  <xsl:variable name="vPath">
    <xsl:for-each select=
      "ancestor-or-self::*[not(position()=last())]">
      <xsl:value-of select="concat(name(), '/')"/>
    </xsl:for-each>
  </xsl:variable>

   <xsl:if test=
   "$vPaths[starts-with(concat(.,'/'), $vPath)]">
    <xsl:call-template name="identity"/>
   </xsl:if>
 </xsl:template>
</xsl:stylesheet>

при применении к предоставленному документу XML :

<root>
    <elem1>value1</elem1>
    <elem2>
        <elem3>
            <param1>value2</param1>
            <param2>value3</param2>
        </elem3>
    </elem2>
    <elem4>
        <param3>value4</param3>
    </elem4>
</root>

дает требуемый, правильный результат :

<root>
   <elem1>value1</elem1>
   <elem2>
      <elem3>
         <param1>value2</param1>
      </elem3>
   </elem2>
</root>

Пояснение :

  1. правило идентификации копирует каждый узел "как есть".

  2. Существует один переопределяющий шаблон который соответствует любому элементу, чьим родителем является элемент. В этом шаблоне выполняется следующее:

  3. Строка, которая является относительным выражением XPath с узлом контекста, верхним элементом документа - создается длятекущий (совпавший) узел.

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

1 голос
/ 30 октября 2011

Это лучшее, что я смог придумать:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" encoding="UTF-8" indent="yes" version="1.0"/>

    <xsl:template match="/root">
        <xsl:copy>
            <xsl:for-each select="*">
                <xsl:call-template name="check-if-allowed">
                    <xsl:with-param name="path" select="local-name(.)"/>
                </xsl:call-template>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>

    <xsl:template name="check-if-allowed">

        <xsl:param name="path"/>

        <xsl:copy>

            <xsl:if test="$path = document('filter.xml')//RequiredElements/elementName/text()">
                <xsl:attribute name="flagged-by-filter">true</xsl:attribute>
            </xsl:if>

            <xsl:choose>
                <xsl:when test="*">
                    <xsl:for-each select="*">
                        <xsl:call-template name="check-if-allowed">
                            <xsl:with-param name="path" select="concat($path, '/', local-name(.))"/>
                        </xsl:call-template>
                    </xsl:for-each>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="text()"/>
                </xsl:otherwise>
            </xsl:choose>

        </xsl:copy>

    </xsl:template>

</xsl:stylesheet>

Давайте пройдемся по этому вопросу: первый шаблон соответствует вашему /root элементу.Он создаст поверхностную копию и затем вызовет шаблон check-if-allowed для каждого дочернего элемента, передав локальное имя этого дочернего элемента в качестве параметра path.

Шаблон check-if-allowed принимает параметр с именем path.Он делает поверхностную копию своего узла, а затем проверяет, содержится ли параметр path в выделении, сделанном из документа filter.xml.Это должен быть путь к вашему второму документу, который содержит список разрешенных путей.Если тест пройден успешно (т. Е. Если параметр path отображается как текстовое содержимое elementName в filter.xml), он также добавит атрибут с именем flagged-by-filter и значением true.

После этого xsl:choose собирается сделать одну из двух вещей.Если есть какие-либо дочерние элементы для текущего, он будет вызывать один и тот же шаблон check-if-allowed для них, но каждый раз с параметром path, к которому было добавлено локальное имя этого элемента.Если дочерних элементов нет, он просто скопирует любой текст, который мог быть в текущем элементе.

Помните, что это очень неполное решение.Он игнорирует атрибуты и не будет работать для смешанного содержимого (то есть текста, смешанного с элементами).

Эта вторая таблица стилей может быть применена к результату первой, чтобы выполнить фактическую фильтрацию:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="/">
        <xsl:copy><xsl:apply-templates select="*"/></xsl:copy>
    </xsl:template>

    <xsl:template match="*[//*[@flagged-by-filter='true']]">
        <xsl:copy><xsl:apply-templates select="*"/></xsl:copy>
    </xsl:template>

    <xsl:template match="*[* and not(//*[@flagged-by-filter='true']) and @flagged-by-filter='true']">
        <xsl:copy></xsl:copy>
    </xsl:template>

    <xsl:template match="*[not(*) and @flagged-by-filter='true']">
        <xsl:copy-of select="."/>
    </xsl:template>

    <xsl:template match="*[not(*) and not(@flagged-by-filter='true')]"/>

</xsl:stylesheet>

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

Это более обычный декларативный стиль XSLT.Первый шаблон соответствует корню.Он просто копирует его, а затем применяет шаблоны к своим дочерним элементам.Второй шаблон соответствует любому элементу, который имеет потомок с атрибутом flagged-by-filter="true".Третий шаблон соответствует любому элементу, который имеет хотя бы один дочерний элемент, имеет атрибут помеченный фильтром, но не имеет потомков с указанным атрибутом.Четвертый шаблон соответствует любому элементу, который не имеет дочернего элемента, но сам помечен.Окончательный шаблон соответствует любому элементу, который не имеет дочернего элемента и не помечен сам по себе.

Хотя это не полное решение вашей проблемы, я надеюсь, что этого достаточно, чтобы помочь вам в этом.Если вы не можете применять два последовательных преобразования XSLT, вам нужно будет найти способ применить материал из первого XSLT по вашему усмотрению.Я не могу придумать, как это можно сделать, но, возможно, у кого-то есть хорошая идея.

Сказано и сделано, для такой проблемы либо не будет использоваться XSLT, либо будет просто генерироваться таблица стилей программнона основе вашего фильтра XML.Вышесказанное будет очень плохо с точки зрения производительности, поскольку мы постоянно применяем выражения XPath к дополнительному документу.Мало того, он должен быть полностью проанализирован каждый раз.У меня когда-то была похожая настройка, и я обнаружил, что производительность очень плохая.Поэтому я изменил доступ ко второму документу в функцию расширения, которая вызывала бы метод Java с использованием предварительно загруженных данных.

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

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