Дан XML-файл с неопределенным количеством атрибутов «Поле». Можно ли "нормализовать" его в пары ключ-значение с помощью XSL 1.0.
Может ли XML-файл быть таким:
<Table>
<Record RecordNo = "1" Field1 = "ID" Field2 = "Author" Field3 = "Title" Field4 = "Genre" Field5 = "Price" Field6 = "Published" />
<Record RecordNo = "2" Field1 = "Book001" Field2 = "Gambardella, Matthew" Field3 = "XML Developer's Guide" Field4 = "Computer" Field5 = "44.95" Field6 = "2000-10-01" />
<Record RecordNo = "3" Field1 = "Book002" Field2 = "Ralls, Kim" Field3 = "Midnight Rain" Field4 = "Fantasy" Field5 = "5.95" Field6 = "2000-12-16" />
<Record RecordNo = "4" Field1 = "Book003" Field2 = "Randall, Cynthia" Field3 = "Lover Birds" Field4 = "Romance" Field5 = "4.95" Field6 = "2000-09-02" />
</Table>
превратиться в ...
<Table>
<Parent ID = "Book001">
<Child Key = "Author" Value = "Gambardella, Matthew" />
<Child Key = "Title" Value = "XML Developer's Guide" />
<Child Key = "Genre" Value = "Computer" />
<Child Key = "Price" Value = "44.95" />
<Child Key = "Published" Value = "2000-10-01" />
</Parent>
<Parent ID = "Book002">
<Child Key = "Author" Value = "Ralls, Kim" />
<Child Key = "Title" Value = "Midnight Rain" />
<Child Key = "Genre" Value = "Fantasy" />
<Child Key = "Price" Value = "5.95" />
<Child Key = "Published" Value = "2000-12-16" />
</Parent>
<Parent ID = "Book003">
<Child Key = "Author" Value = "Randall, Cynthia" />
<Child Key = "Title" Value = "Lover Bird" />
<Child Key = "Genre" Value = "Romance" />
<Child Key = "Price" Value = "4.95" />
<Child Key = "Published" Value = "2000-09-02" />
</Parent>
</Table>
используя XSLT-1.0?
В таблице стилей можно использовать следующие два шаблона.
Они используют значения первого Record
, отраженного как TableHeader (th
) в xsl:variable
:
<xsl:template match = "Table">
<xsl:copy>
<xsl:apply-templates select = "node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match = "Record[@RecordNo > 1]">
<xsl:variable name = "th" select = "/Table/Record[@RecordNo = 1]" />
<Parent>
<xsl:attribute name = "{$th/@Field1}"><xsl:value-of select = "@Field1" /></xsl:attribute>
<xsl:for-each select = "./@*[starts-with(local-name(),'Field')][position() > 1]">
<xsl:sort select = "local-name()" /> <!-- to process attributes in the right order -->
<xsl:variable name = "attrName" select = "concat('Field',position()+1)" />
<xsl:variable name = "HeaderName" select = "$th/@*[contains(local-name(),$attrName)]" />
<Child Key = "{$HeaderName}" Value = "{.}" />
</xsl:for-each>
</Parent>
</xsl:template>
Это будет работать для произвольного количества узлов Field*
.
Спасибо за критику. Я добавил xsl:sort
для обработки атрибутов в правильном порядке.
Один из вариантов - использовать xsl:key
, который выбирает все атрибуты первого Record
и использует атрибуты name()
для ключа.
Атрибут Field1
нужно будет обрабатывать иначе, чем другие, поскольку он используется в Parent
вместо Child
.
Пример...
Ввод XML
<Table>
<Record RecordNo = "1" Field1 = "ID" Field2 = "Author" Field3 = "Title" Field4 = "Genre" Field5 = "Price" Field6 = "Published" />
<Record RecordNo = "2" Field1 = "Book001" Field2 = "Gambardella, Matthew" Field3 = "XML Developer's Guide" Field4 = "Computer" Field5 = "44.95" Field6 = "2000-10-01" />
<Record RecordNo = "3" Field1 = "Book002" Field2 = "Ralls, Kim" Field3 = "Midnight Rain" Field4 = "Fantasy" Field5 = "5.95" Field6 = "2000-12-16" />
<Record RecordNo = "4" Field1 = "Book003" Field2 = "Randall, Cynthia" Field3 = "Lover Birds" Field4 = "Romance" Field5 = "4.95" Field6 = "2000-09-02" />
</Table>
XSLT 1.0
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output indent = "yes"/>
<xsl:strip-space elements = "*"/>
<!--Select all the attributes of the first Record and use the attributes
name for the key. See https://www.w3.org/TR/xslt-10/#key -->
<xsl:key name = "keys" match = "Record[@RecordNo=1]/@*" use = "name()"/>
<xsl:template match = "/Table">
<xsl:copy>
<!--Process Record elements, but do not process the Record
with the attribute RecordNo value of 1. That Record is only used for
the Key values.-->
<xsl:apply-templates select = "Record[not(@RecordNo=1)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match = "Record">
<Parent>
<!--Process the attributes of Record, but do not process the RecordNo
attribute.-->
<xsl:apply-templates select = "@*[not(name()='RecordNo')]">
<!--Sort by attribute name to guarantee the order they're processed.-->
<xsl:sort select = "name()"/>
</xsl:apply-templates>
</Parent>
</xsl:template>
<xsl:template match = "@Field1" priority = "1">
<!--For the Field1 attribute, create an attribute. Since we know the name of
this attribute, we could also use key('keys','Field1') to get the name.
Also, the curly braces ({}) is an AVT (Attribute Value Template).
See https://www.w3.org/TR/xslt-10/#attribute-value-templates-->
<xsl:attribute name = "{key('keys',name())}">
<xsl:value-of select = "."/>
</xsl:attribute>
</xsl:template>
<xsl:template match = "@*">
<!--For any other attributes, create a Child element. They key lookup is based on the
name of the attribute. (The second argument in key() corresponds to the "use"
attribute of xsl:key.)-->
<Child Key = "{key('keys',name())}" Value = "{.}"/>
</xsl:template>
</xsl:stylesheet>
Выход
<Table>
<Parent ID = "Book001">
<Child Key = "Author" Value = "Gambardella, Matthew"/>
<Child Key = "Title" Value = "XML Developer's Guide"/>
<Child Key = "Genre" Value = "Computer"/>
<Child Key = "Price" Value = "44.95"/>
<Child Key = "Published" Value = "2000-10-01"/>
</Parent>
<Parent ID = "Book002">
<Child Key = "Author" Value = "Ralls, Kim"/>
<Child Key = "Title" Value = "Midnight Rain"/>
<Child Key = "Genre" Value = "Fantasy"/>
<Child Key = "Price" Value = "5.95"/>
<Child Key = "Published" Value = "2000-12-16"/>
</Parent>
<Parent ID = "Book003">
<Child Key = "Author" Value = "Randall, Cynthia"/>
<Child Key = "Title" Value = "Lover Birds"/>
<Child Key = "Genre" Value = "Romance"/>
<Child Key = "Price" Value = "4.95"/>
<Child Key = "Published" Value = "2000-09-02"/>
</Parent>
</Table>
Рабочий пример: http://xsltfiddle.liberty-development.net/pPqsHTa/2
Удивительный! Оно работает. Теперь мне просто нужно узнать, как это работает! Большое спасибо.
Обратите внимание, что нет никакой гарантии, что дочерние элементы Author / Title / Genre ... появятся именно в этом порядке. Если вам нужна эта гарантия, вам нужно отсортировать атрибуты по имени при обработке.
@MichaelKay - Это правда. На всякий случай, если это важно для OP, я добавил сортировку.
@BrianBates - Я обновил свою таблицу стилей некоторыми комментариями. Надеюсь, они помогут вам понять, как это работает. Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать. Спасибо!
Это решение ошибочно предполагает, что @ * будет обрабатывать атрибуты в том порядке, в котором они записаны в исходном XML. Нет никаких гарантий: порядок атрибутов непредсказуем.