Нормализовать XML в пару ключ-значение с помощью XSL 1.0

Дан 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?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
52
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В таблице стилей можно использовать следующие два шаблона. Они используют значения первого 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*.

Это решение ошибочно предполагает, что @ * будет обрабатывать атрибуты в том порядке, в котором они записаны в исходном XML. Нет никаких гарантий: порядок атрибутов непредсказуем.

Michael Kay 01.05.2018 22:28

Спасибо за критику. Я добавил xsl:sort для обработки атрибутов в правильном порядке.

zx485 01.05.2018 22:38
Ответ принят как подходящий

Один из вариантов - использовать 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

Удивительный! Оно работает. Теперь мне просто нужно узнать, как это работает! Большое спасибо.

Brian Bates 01.05.2018 22:16

Обратите внимание, что нет никакой гарантии, что дочерние элементы Author / Title / Genre ... появятся именно в этом порядке. Если вам нужна эта гарантия, вам нужно отсортировать атрибуты по имени при обработке.

Michael Kay 01.05.2018 22:30

@MichaelKay - Это правда. На всякий случай, если это важно для OP, я добавил сортировку.

Daniel Haley 01.05.2018 23:44

@BrianBates - Я обновил свою таблицу стилей некоторыми комментариями. Надеюсь, они помогут вам понять, как это работает. Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать. Спасибо!

Daniel Haley 01.05.2018 23:44

Другие вопросы по теме