XML XSL разбор / сокращение структуры до необходимых элементов
Я снова У меня новая проблема.
Мне нравится раздвигать / уменьшать структуру XML только для необходимых элементов.
Чтобы объяснить проблему, я построил простую случайную структуру.
<ROOT>
<DATA>
<ALLOC>
<TYPE>Test</TYPE>
<NAME>something text</NAME>
<VALUE>4711</VALUE>
</ALLOC>
<ALLOC>
<TYPE>Test</TYPE>
<NAME>something text</NAME>
<VALUE>4712</VALUE>
</ALLOC>
<ALLOC>
<TYPE>Test</TYPE>
<NAME>something text</NAME>
<VALUE>4713</VALUE>
</ALLOC>
</DATA>
<SOURCE>
<CONNECTION>
<TYPE>SQL</TYPE>
<VALUE>jdbc</VALUE>
<CSTRING>jdbc string</CSTRING>
</CONNECTION>
<CONNECTION>
<TYPE>CSV</TYPE>
<VALUE>CSV</VALUE>
<CSTRING></CSTRING>
</CONNECTION>
</SOURCE>
</ROOT>
Требуемые элементы, например:
/ROOT[1]/DATA[1]/ALLOC[2]/VALUE[1]
/ROOT[1]/SOURCE[1]/CONNECTION[1]/CSTRING[1]
Требуемые утверждения элементов приходят из Java с xmlassert.equal> xmldiff
Теперь мне нужно разделить структуру xml до требуемых элементов, но сохранить структуру xml (xpath) элементов.
Желаемый результат:
<ROOT>
<DATA>
<ALLOC>
<VALUE>4712</VALUE>
</ALLOC>
</DATA>
<SOURCE>
<CONNECTION>
<CSTRING>jdbc string</CSTRING>
</CONNECTION>
</SOURCE>
</ROOT>
Реальная структура огромна (минимум 6x страниц формата А4, если вы ее напечатаете), сложна и имеет многоуровневую структуру. Запрашиваемые элементы также динамически.
Я провел последние часы с чтением потоков во многих форматах, попытками с большим количеством различных xslt и чтением большего количества потоков.
Как я могу это сделать?
Огромное спасибо заранее.
2 ответа
Как я могу это сделать?
Это короткое и простое общее решение XSLT 1.0:
<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:param name="pExpressions">
<e>/ROOT[1]/DATA[1]/ALLOC[2]/VALUE[1]</e>
<e>/ROOT[1]/SOURCE[1]/CONNECTION[1]/CSTRING[1]</e>
</xsl:param>
<xsl:variable name="vExpressions"
select="document('')/*/xsl:param[@name='pExpressions']/*"/>
<xsl:template match="*">
<xsl:variable name="vPath">
<xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
</xsl:variable>
<xsl:copy-of select="self::*[$vExpressions[.=$vPath]]"/>
<xsl:apply-templates select=
"self::*[$vExpressions[not(.=$vPath) and starts-with(.,$vPath)]]" mode="process"/>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:value-of select="concat('/',name())"/>
<xsl:variable name="vnumPrecSiblings" select=
"count(preceding-sibling::*[name()=name(current())])"/>
<xsl:value-of select="concat('[', $vnumPrecSiblings +1, ']')"/>
</xsl:template>
<xsl:template match="*" mode="process">
<xsl:copy>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется к предоставленному документу XML:
<ROOT>
<DATA>
<ALLOC>
<TYPE>Test</TYPE>
<NAME>something text</NAME>
<VALUE>4711</VALUE>
</ALLOC>
<ALLOC>
<TYPE>Test</TYPE>
<NAME>something text</NAME>
<VALUE>4712</VALUE>
</ALLOC>
<ALLOC>
<TYPE>Test</TYPE>
<NAME>something text</NAME>
<VALUE>4713</VALUE>
</ALLOC>
</DATA>
<SOURCE>
<CONNECTION>
<TYPE>SQL</TYPE>
<VALUE>jdbc</VALUE>
<CSTRING>jdbc string</CSTRING>
</CONNECTION>
<CONNECTION>
<TYPE>CSV</TYPE>
<VALUE>CSV</VALUE>
<CSTRING></CSTRING>
</CONNECTION>
</SOURCE>
</ROOT>
желаемый, правильный результат получается:
<ROOT>
<DATA>
<ALLOC>
<VALUE>4712</VALUE>
</ALLOC>
</DATA>
<SOURCE>
<CONNECTION>
<CSTRING>jdbc string</CSTRING>
</CONNECTION>
</SOURCE>
</ROOT>
Пояснение:
Для каждого элемента в документе XML создается его выражение XPath (в стиле, указанном в вопросе). Этот элемент:
- полностью копируется, если его выражение XPath равно одному из переданных в качестве параметров выражений XPath.
- мелкое копирование, если его выражение XPath является строковым префиксом одного или нескольких переданных в качестве параметров выражений XPath
- игнорируется (удаляется) в противном случае
Универсальность решения:
Входные выражения XPath могут быть переданы как <xsl:param>
на вызов преобразования или может быть в файле XML, чей URI передается в качестве параметра преобразования.
Примечание:
Я провел последние часы с чтением потоков во многих форматах, попытками с большим количеством различных xslt и чтением большего количества потоков.
Для более сложного и элегантного способа создания выражения XPath для каждого типа узла см. Этот ответ.
Насколько я понимаю, вам нужен XSLT, который будет принимать последовательность выражений XPath, а затем сокращать входной XML только до тех элементов, которые соответствуют выражениям XPath и их предкам.
Вы не указываете, какую версию XSLT вы хотите использовать, или какой процессор вы будете использовать, поэтому трудно привести хороший пример кода. Вместо этого я опишу несколько вариантов, которые, я думаю, вы можете выбрать:
- Сгенерируйте некоторый XSLT (используя XSLT?), Например, в ответе @michael.hor257k, используя в качестве входных данных операторы XPath, и запустите этот XSLT на своем входе. Это, вероятно, будет хорошо масштабироваться, но требует приличного объема начальных инвестиций и будет более сложным, чем другие варианты.
- Используйте функции xsl:key и key() для определения элементов, которые вы хотите сохранить. Помните, что вы хотите сохранить всех предков.
- Используйте функции, параметры или шаблоны вызовов, чтобы оценить, имеет ли исследуемый элемент адрес XPath, соответствующий любому из ваших списков XPath или их предкам. Вы, вероятно, можете использовать параметры, чтобы сэкономить кучу времени на обработку.
- Что-то, включающее saxon:parse() или какую-то другую пользовательскую функцию, которая может или не может быть доступна в вашей среде.
TMTOWTDI. Какой бы метод вы ни выбрали, вы, вероятно, захотите использовать XSLT 2, чтобы вы могли рассматривать свой список адресов XPath как последовательность строк; Вы, вероятно, также захотите расширить эту последовательность, чтобы включить всех предков - "/ROOT[1]/DATA[1]/ALLOC[2]"
становится ("/ROOT[1]/DATA[1]/ALLOC[2]", "/ROOT[1]/DATA[1]", "/ROOT[1]")
- чтобы упростить вещи.
Черт, мне стало скучно, и вы сделали реализацию XSLT 2:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:local="http://example.com/local"
exclude-result-prefixes="xs local"
version="2.0">
<xsl:output indent="yes"/>
<xsl:param name="XPath" select="('/ROOT[1]/DATA[1]/ALLOC[2]/VALUE[1]', '/ROOT[1]/SOURCE[1]/CONNECTION[1]/CSTRING[1]')" as="xs:string+"/>
<xsl:variable name="XPe" as="xs:string+">
<xsl:for-each select="$XPath">
<xsl:sequence select="local:ancestorize(.)"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="XPd" as="xs:string+">
<xsl:sequence select="distinct-values($XPe)"/>
</xsl:variable>
<xsl:template match="@*|*">
<xsl:param name="parentXP" as="xs:string?"/>
<xsl:variable name="selfXP" as="xs:string">
<xsl:variable name="seq">
<xsl:value-of select="$parentXP"/>
<xsl:text>/</xsl:text>
<xsl:if test=". is ../@*">
<!-- this test is a bit untested: you may need a better test to tell if you're looking at an attribute; I leave it as an exercise for you! -->
<xsl:text>@</xsl:text>
</xsl:if>
<!-- I'm assuming no namespaces: if you have namespaces you'll have to build in your prefix here -->
<xsl:value-of select="local-name()"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="1 + count(preceding-sibling::*[name() eq current()/name()])"/>
<xsl:text>]</xsl:text>
</xsl:variable>
<xsl:value-of select="xs:string($seq)"/>
</xsl:variable>
<xsl:if test="$selfXP = $XPd">
<xsl:copy>
<xsl:apply-templates select="@* | node()">
<xsl:with-param name="parentXP" select="$selfXP"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="text()">
<xsl:param name="parentXP"/>
<xsl:if test="$parentXP = $XPd and normalize-space(.) ne ''">
<xsl:copy/>
</xsl:if>
</xsl:template>
<xsl:function name="local:ancestorize" as="xs:string+">
<xsl:param name="XPath" as="xs:string"/>
<xsl:sequence select="$XPath"/>
<xsl:if test="count(tokenize($XPath, '/')) gt 1">
<xsl:sequence select="local:ancestorize(string-join((tokenize($XPath, '/'))[not(position() eq last())], '/'))"/>
</xsl:if>
</xsl:function>
</xsl:stylesheet>