XSLT - как получить значение имени узла?

У нас есть (возможно, необычно отформатированные) файлы XML, которые (очень упрощенно) выглядят так:

<Contacts>
    <Contact>
        <Private>
            <Name>John Doe</Name>
            <Address>Somewhere1</Address>
            ...
        </Private>
    </Contact>
    <Contact>
        <Business>
            <Name>John Business</Name>
            <Address>Somewhere2</Address>
            <BusinessAddress>Somewhere3</BusinessAddress>
            ...
        </Business>
    </Contact>
    ...
</Contacts>

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

В настоящее время мы используем таблицу стилей для форматирования файла XML, который повторяет форматирование для всех общих атрибутов для каждого типа узла "Контакт":

<!-- ... loads of prologue code -->
<xsl:for-each select="Contacts/Contact/.">
    <xsl:choose>
    <xsl:when test="Private">
        <!--    code to format each and every attribute of the current 
                "Contact" element, e.g.: -->
        <td class="header">Private Contact</td>
        <td class="detail"><xsl:value-of select="./Name"/></td>
        <!--   ...  -->
    </xsl:when>

    <xsl:when test="Business">
        <!--    code to format each and every attribute of the current 
                "Contact" element, e.g.: -->
        <td class="header">Business Contact/<td>
        <td class="detail"><xsl:value-of select="./Name"/></td>
        <!--   ...  -->
    </xsl:when>

    <!--   ...  -->

    </xsl:choose>
</xsl:for-each>

Но мы получаем все больше и больше <Contact> типы с большим и большим количеством атрибутов, в результате чего получается таблица стилей длиной в милю, поэтому я хочу упростить таблицу стилей следующим образом:

<!-- ... loads of prologue code -->
<xsl:for-each select="Contacts/Contact/.">
    <!-- loads of formatting stuff common to each "Contact" type, but with 
         some wording that's "Contact" type dependant, e.g. -->
    <td class="header">**????** Contact</td>
    <td class="detail"><xsl:value-of select="./Name"/></td>
    <xsl:choose>
    <xsl:when **????** = "Private">
        <xsl:variable name="contactWhen">off</xsl:variable>
    </xsl:when>
    <xsl:when **????** = "Business">
        <xsl:variable name="contactWhen">office</xsl:variable>
    </xsl:when>
    <!-- ...  -->
    </xsl:choose>
     <td class="header">Contact/<td>
     <td class="detail">Only during $contactWhen hours</td>

    <xsl:choose>
    <xsl:when test="Private">
       <!-- code to format attributes specific for the current "Contact" -->
    </xsl:when>

    <xsl:when test="Business">
       <!-- code to format attributes specific for the current "Contact" -->
    </xsl:when>

    <!--    ...  -->

    </xsl:choose>
</xsl:for-each>

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

Моя проблема в том, что я не могу получить значение имени узла под Contactв этом примере Private или же Business, Я пытался использовать все варианты, которые я мог придумать, оба value-of select= и просто select= (a.o. "", ".", "./.", Contacts/Contact, Contacts/Contact/., [local-]name() и даже Field[@name='.']).

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

Что я должен код для **????** проверить значение имени текущего узла и, в конечном итоге, присвоить некоторой переменной (переменным) значение, основанное на результатах этого теста?

Спасибо за любую помощь,

Юуль

Привет,

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

Я попробовал все 3 предложения в цикле "для каждого" и непосредственно перед "выбрать", но все 3 не сработали:

<td class="content2" colspan="2"><xsl:value-of select="ancestor::Record/child::*[1]name"/></td>

не удается с ошибкой XML: ожидаемое "EOF" найдено "name"

<td class="content2" colspan="2"><xsl:value-of select="name()"/></td>

возвращает "Контакт", а не имя первого дочернего имени

<xsl:variable name="contactType">
<xsl:choose>
    <xsl:when test="Private">Private</xsl:when>
    <xsl:when test="Business">Business</xsl:when>
</xsl:choose>
<td class="content2" colspan="2"><xsl:value-of select="$contactType"/></td>
</xsl:variable>

не удается с ошибкой XML: ссылка на переменную contactType не может быть разрешена. Переменная может не быть определена или не может находиться в области видимости.

Может ли это быть вызвано наличием этих конструкций во встроенном коде, а не в шаблоне.

Пожалуйста, порекомендуйте,

Юуль

5 ответов

Использование xsl: выбор для включения имен элементов - неприятный запах кода в XSLT: для этого и нужны правила шаблонов. Моим первым шагом к улучшению этого кода будет замена кода

<xsl:for-each select="Contacts/Contact/.">
    <xsl:choose>
    <xsl:when test="Private">
     ...
    <xsl:when test="Business">

с

<xsl:apply-templates select="Contacts/Contact"/>

а также

<xsl:template match="Contact[Private]">
  ...
</xsl:template>

<xsl:template match="Contact[Business]">
  ...
</xsl:template>

и т.п.

Это не сразу решит проблему повторного использования кода на следующем уровне ниже. Это происходит, когда вы начинаете делать apply-templates на этом уровне.

<xsl:template match="Contact[Private]">
  <td class="header">Private Contact</td>
  <xsl:apply-templates select="Private/*"/>
</xsl:template>

<xsl:template match="Name | Address">
  <td class="detail"><xsl:value-of select="."/></td>
</xsl:template> 

Шаблонные правила - это способ, которым XSLT был разработан для использования. Этот пример является отличной иллюстрацией преимуществ, которые вы получаете, используя их правильно.

Измените вашу переменную на:

<xsl:variable name="contactWhen">
    <xsl:choose>
        <xsl:when test="Private">off</xsl:when>
        <xsl:when test="Business">office</xsl:when>
    </xsl:choose>
</xsl:variable>
<xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <xsl:for-each select="Contacts/Contact">
            <xsl:for-each select="child::*[1]">
                <td class="header"><xsl:value-of select="name()"/><xsl:text> Contact</xsl:text></td>
                <td class="detail"><xsl:value-of select="ancestor::Contact/child::*[1]/Name"/></td>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>
Try It

Имя узла первого потомка <Contact> можно получить с помощью name() функция. Однако, поскольку существуют условия для проверки имени узла, а затем сделать что-то еще, а именно. заполнить другое значение в $contactWhen или форматирование атрибутов в зависимости от типа контакта, вам придется использовать <xsl:choose><xsl:when test=""></xsl:when></xsl:choose> условия для каждого уникального контакта.

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

<xsl:template match="Contact/*[1]">
    <!-- fetching node name -->
    <xsl:variable name="nodeName" select="name()" />
    <td class="header"><xsl:value-of select="concat($nodeName, ' Contact')" /></td>
    <td class="detail"><xsl:value-of select="Name" /></td>

    <!-- contact hours -->
    <xsl:variable name="contactWhen">
        <xsl:choose>
            <xsl:when test="$nodeName = 'Private'">off</xsl:when>
            <xsl:when test="$nodeName = 'Business'">office</xsl:when>
        </xsl:choose>
    </xsl:variable>
    <td class="header">Contact</td>
    <td class="detail"><xsl:value-of select="concat('Only during ', $contactWhen, ' hours')" /></td>

    <!-- adding contact specific nodes and attributes -->
    <xsl:choose>
        <xsl:when test="$nodeName = 'Private'">
            <!-- code to format attributes specific for the "Private" Contact -->
            <td class="{$nodeName}">This is a Private Contact</td>
        </xsl:when>
        <xsl:when test="$nodeName = 'Business'">
            <!-- code to format attributes specific for the "Business" Contact -->
            <td class="{$nodeName}">This is a Business Contact</td>
        </xsl:when>
    </xsl:choose>
</xsl:template>

Вывод будет выглядеть примерно так

<td class="header">Private Contact</td>
<td class="detail">John Doe</td>
<td class="header">Contact</td>
<td class="detail">Only during off hours</td>
<td class="Private">This is a Private Contact</td>

<td class="header">Business Contact</td>
<td class="detail">John Business</td>
<td class="header">Contact</td>
<td class="detail">Only during office hours</td>
<td class="Business">This is a Business Contact</td>

Для улучшения обслуживания кода <xsl:choose> фрагменты кода могут быть перемещены в различные шаблоны, а затем с помощью <xsl:call-template> они могут быть вызваны для обработки. Это будет модульным кодом и сделает его менее загроможденным.

Я обнаружил проблему... Ответ Джоэла от 2-х дней назад действительно помогает; Я просто имел ссылку на переменную в самом объявлении, в то время как ссылка (и) должна быть сделана ПОСЛЕ полного объявления следующим образом:

<xsl:variable name="contactType">
<xsl:choose>
    <xsl:when test="Private">Private</xsl:when>
    <xsl:when test="Business">Business</xsl:when>
</xsl:choose>
</xsl:variable>
<td class="content2" colspan="2"><xsl:value-of select="$contactType"/></td>

на самом деле добавляет "Частный" или "Бизнес" в форматированный XML:-)

Спасибо всем, кто внес свой вклад, также пытаясь улучшить мои ограниченные навыки XSLT,

Юуль

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