XSL / Schemas для начинающих - PullRequest
1 голос
/ 27 августа 2010

Уже давно использовал XML для хранения и передачи данных, но никогда не проверял и не преобразовывал его.В настоящее время начинаем новый проект и принимаем некоторые дизайнерские решения и должны знать некоторые элементарные вещи о XSL и схемах.

Наш XML такой (извините, пример скучной книги :)):

<Books>
  <Book>
    <ID>1</ID>
    <Name>Book1</Name>
    <Price>24.??</Price>
    <Country>US</Country>
  </Book>
  <Book>
    <ID>1</ID>
    <Name></Name>
    <Price>24.69</Price>
  </Book>
</Books>

Наши требования:

  1. Преобразование

    a) Превратить «США» в Соединенные Штаты
    b) если цена> 20, создатьnew lLement <Expensive>True</Expensive>

    Я предполагаю, что это сделано с помощью XSLT, но кто-нибудь может дать мне несколько советов о том, как этого добиться?

  2. Проверка

    a) является ID целым числом, является Price a float (самая важная работа, если честно)
    b) Все ли теги заполнены, например, тег name не заполнен (2-й по важности))
    c) Имеются ли все теги, например, Страна отсутствует в книге 2
    d) [Возможно, сложно] Является ли элемент идентификатора уникальным во всех книгах?(приятно иметь)

Из того, что я прочитал, это делается с помощью схемы или Relax NG, но результаты проверки могут быть выведены в простой HTML для отображения списка илиошибки?

например
Книга 1: Цена "Цена. ??"is not float
Книга 2: ID не уникален, Имя пусто, Страна отсутствует

Или было бы лучше сделать эти вещи программно в C #?Благодарю.

Ответы [ 3 ]

3 голосов
/ 27 августа 2010

Эта таблица стилей:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="map"
exclude-result-prefixes="m">
    <xsl:key name="kTestIntID" match="Book"
             use="number(ID)=number(ID) and not(contains(ID,'.'))"
             m:message="Books with no integer ID"/>
    <xsl:key name="kTestFloatPrice" match="Book"
             use="number(Price)=number(Price) and contains(Price,'.')"
             m:message="Books with no float Price"/>
    <xsl:key name="kTestEmptyElement" match="Book"
             use="not(*[not(node())])"
             m:message="Books with empty element"/>
    <xsl:key name="kTestAllElements" match="Book"
             use="ID and Name and Price and Country"
             m:message="Books with missing element"/>
    <xsl:key name="kBookByID" match="Book" use="ID"/>
    <m:map from="US" to="United States"/>
    <m:map from="CA" to="Canada"/>
    <xsl:variable name="vCountry" select="document('')/*/m:map"/>
    <xsl:variable name="vKeys" select="document('')/*/xsl:key/@name
                                           [starts-with(.,'kTest')]"/>
    <xsl:variable name="vTestNotUniqueID"
                  select="*/*[key('kBookByID',ID)[2]]"/>
    <xsl:template match="/" name="validation">
        <xsl:param name="pKeys" select="$vKeys"/>
        <xsl:param name="pTest" select="$vTestNotUniqueID"/>
        <xsl:param name="pFirst" select="true()"/>
        <xsl:choose>
            <xsl:when test="$pTest and $pFirst">
                <html>
                    <body>
                        <xsl:if test="$vTestNotUniqueID">
                            <h2>Books with no unique ID</h2>
                            <ul>
                                <xsl:apply-templates
                                 select="$vTestNotUniqueID"
                                 mode="escape"/>
                            </ul>
                        </xsl:if>
                        <xsl:variable name="vCurrent" select="."/>
                        <xsl:for-each select="$vKeys">
                            <xsl:variable name="vKey" select="."/>
                            <xsl:for-each select="$vCurrent">
                                <xsl:if test="key($vKey,'false')">
                                    <h2>
                                        <xsl:value-of
                                         select="$vKey/../@m:message"/>
                                    </h2>
                                    <ul>
                                        <xsl:apply-templates
                                         select="key($vKey,'false')"
                                         mode="escape"/>
                                    </ul>
                                </xsl:if>
                            </xsl:for-each>
                        </xsl:for-each>
                    </body>
                </html>
            </xsl:when>
            <xsl:when test="$pKeys">
                <xsl:call-template name="validation">
                    <xsl:with-param name="pKeys"
                     select="$pKeys[position()!=1]"/>
                    <xsl:with-param name="pTest"
                     select="key($pKeys[1],'false')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="Book" mode="escape">
        <li>
            <xsl:call-template name="escape"/>
        </li>
    </xsl:template>
    <xsl:template match="*" name="escape" mode="escape">
        <xsl:value-of select="concat('&lt;',name(),'&gt;')"/>
        <xsl:apply-templates mode="escape"/>
        <xsl:value-of select="concat('&lt;/',name(),'&gt;')"/>
    </xsl:template>
    <xsl:template match="text()" mode="escape">
        <xsl:value-of select="normalize-space()"/>
    </xsl:template>

    <!-- Up to here, rules for validation.
         From here, rules for transformation -->

    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Country/text()">
        <xsl:variable name="vMatch"
                      select="$vCountry[@from=current()]"/>
        <xsl:choose>
            <xsl:when test="$vMatch">
                <xsl:value-of select="$vMatch/@to"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="Price[. > 20]">
        <xsl:call-template name="identity"/>
        <Expensive>True</Expensive>
    </xsl:template>
</xsl:stylesheet>

С вашим входом, выход:

<html>
<body>
<h2>Books with no unique ID</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;Book1&lt;/Name&gt;&lt;Price&gt;24.??&lt;/Price&gt;&lt;Country&gt;US&lt;/Country&gt;&lt;/Book&gt;</li>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;&lt;/Name&gt;&lt;Price&gt;24.69&lt;/Price&gt;&lt;/Book&gt;</li>
</ul>
<h2>Books with no float Price</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;Book1&lt;/Name&gt;&lt;Price&gt;24.??&lt;/Price&gt;&lt;Country&gt;US&lt;/Country&gt;&lt;/Book&gt;</li>
</ul>
<h2>Books with empty element</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;&lt;/Name&gt;&lt;Price&gt;24.69&lt;/Price&gt;&lt;/Book&gt;</li>
</ul>
<h2>Books with missing element</h2>
<ul>
<li>&lt;Book&gt;&lt;ID&gt;1&lt;/ID&gt;&lt;Name&gt;&lt;/Name&gt;&lt;Price&gt;24.69&lt;/Price&gt;&lt;/Book&gt;</li>
</ul>
</body>
</html>

При правильном вводе:

<Books>
    <Book>
        <ID>1</ID>
        <Name>Book1</Name>
        <Price>19.50</Price>
        <Country>US</Country>
    </Book>
    <Book>
        <ID>2</ID>
        <Name>Book2</Name>
        <Price>24.69</Price>
        <Country>CA</Country>
    </Book>
</Books>

Выход:

<Books>
    <Book>
        <ID>1</ID>
        <Name>Book1</Name>
        <Price>19.50</Price>
        <Country>United States</Country>
    </Book>
    <Book>
        <ID>2</ID>
        <Name>Book2</Name>
        <Price>24.69</Price>
        <Expensive>True</Expensive>
        <Country>Canada</Country>
    </Book>
</Books>

Примечание : использование клавиш для исполнения. Это доказательство концепции. В реальной жизни вывод XHTML должен быть заключен в инструкцию xsl:message. От http://www.w3.org/TR/xslt#message

Инструкция xsl: message отправляет сообщение таким образом, что зависит от процессор XSLT. Содержание Инструкция xsl: message является шаблоном. Xsl: message создается создание экземпляров контента для создания Фрагмент XML. Этот фрагмент XML является содержание сообщения.

ПРИМЕЧАНИЕ. XSLT-процессор может реализовывать xsl: сообщение при появлении окна предупреждения или путем записи в файл журнала.

Если атрибут terminate имеет значение да, тогда процессор XSLT следует прекратить обработку после отправив сообщение. Значение по умолчанию нет.

Редактировать : сжатие кода и решение проблемы карты страны.

Редактировать 2 : В реальной жизни, с большими XML-документами и более гибкими инструментами, лучшим подходом было бы запустить преобразование с помощью процессора, поддерживающего схему XSLT 2.0 для проверки, или выполнить проверку независимо с хорошо Знаете валидаторы схемы. Если по какой-то причине эти варианты недоступны, не соглашайтесь с моим ответом для проверки концепции, поскольку наличие ключей для каждого правила проверки приводит к значительному использованию памяти для больших документов. Лучший способ в последнем случае - добавить правила для отлова ошибок проверки, заканчивая преобразование сообщением. Как пример, эта таблица стилей:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="map"
exclude-result-prefixes="m">
    <xsl:key name="kIDByValue" match="ID" use="."/>
    <m:map from="US" to="United States"/>
    <m:map from="CA" to="Canada"/>
    <xsl:variable name="vCountry" select="document('')/*/m:map"/>
    <xsl:template name="location">
        <xsl:param name="pSteps" select="ancestor-or-self::*"/>
        <xsl:if test="$pSteps">
            <xsl:call-template name="location">
                <xsl:with-param name="pSteps"
                                select="$pSteps[position()!=last()]"/>
            </xsl:call-template>
            <xsl:value-of select="concat('/',
                                         name($pSteps[last()]),
                                         '[',
                                         count($pSteps[last()]/
                                               preceding-sibling::*
                                               [name()=
                                                name($pSteps[last()])])
                                         +1,
                                         ']')"/>
        </xsl:if>
    </xsl:template>
    <xsl:template match="ID[not(number()=number() and not(contains(.,'.')))]">
        <xsl:message terminate="yes">
            <xsl:text>No integer ID at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="Price[not(number()=number() and contains(.,'.'))]">
        <xsl:message terminate="yes">
            <xsl:text>No float Price at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="Book/*[not(node())]">
        <xsl:message terminate="yes">
            <xsl:text>Empty element at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="Book[not(ID and Name and Price and Country)]">
        <xsl:message terminate="yes">
            <xsl:text>Missing element at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <xsl:template match="ID[key('kIDByValue',.)[2]]">
        <xsl:message terminate="yes">
            <xsl:text>Duplicate ID at </xsl:text>
            <xsl:call-template name="location"/>
        </xsl:message>
    </xsl:template>
    <!-- Up to here, rules for validation.
         From here, rules for transformation -->
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Country/text()">
        <xsl:variable name="vMatch"
                      select="$vCountry[@from=current()]"/>
        <xsl:choose>
            <xsl:when test="$vMatch">
                <xsl:value-of select="$vMatch/@to"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="Price[. > 20]">
        <xsl:call-template name="identity"/>
        <Expensive>True</Expensive>
    </xsl:template>
</xsl:stylesheet>

При вашем вводе это сообщение останавливает преобразование:

Duplicate ID ar /Books[1]/Book[1]/ID[1]

При правильном вводе выходы такие же, как и раньше.

0 голосов
/ 27 августа 2010

В общем XSL-образовании вы можете найти XSL Primer , который я написал несколько лет назад. Он не актуален для всех последних тенденций, но охватывает основы того, как обрабатывается документ XML.

0 голосов
/ 27 августа 2010

Вот схема RelaxNG :

<grammar xmlns="http://relaxng.org/ns/structure/1.0"
         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">

  <start>
    <element name="Books">
      <zeroOrMore>
        <element name="Book">
          <element name="ID"><data type="ID"/></element>
          <element name="Name"><text/></element>
          <element name="Price"><data type="decimal"/></element>
          <element name="Country"><data type="NMTOKEN"/></element>
        </element>
      </zeroOrMore>
    </element>
  </start>

</grammar>

и это версия схемы XML. (Я думаю.)

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="Books">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="Book"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Book">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="ID"/>
        <xs:element ref="Name"/>
        <xs:element ref="Price"/>
        <xs:element ref="Country"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="ID" type="xs:ID"/>
  <xs:element name="Name" type="xs:string"/>
  <xs:element name="Price" type="xs:decimal"/>
  <xs:element name="Country" type="xs:NMTOKEN"/>
</xs:schema>

Здесь стоит отметить пару вещей:

  • Простой тип ID заставит валидаторы проверять множественные вхождения идентификатора и жаловаться, если они есть. Однако недостатком использования тегов ID является то, что они не могут начинаться с цифры. Итак, A1, A2, ... An было бы хорошо, но идентификаторы, такие как 1, 2, ...., n, в любом случае считались бы недействительными.
  • Цена указана с учетом десятичного типа. Число с плавающей запятой не подходит для финансовых показателей из-за ошибок округления.

Выполнение этого через xmllint с исходным XML-документом в качестве ввода (с измененными идентификаторами) дает:

wilfred$ xmllint --noout --relaxng ./books.rng ./books.xml
./books.xml:5: element Price: Relax-NG validity error : Type decimal doesn't allow value '24.??'
./books.xml:5: element Price: Relax-NG validity error : Error validating datatype decimal
./books.xml:5: element Price: Relax-NG validity error : Element Price failed to validate content
./books.xml:8: element Book: Relax-NG validity error : Expecting an element , got nothing
./books.xml fails to validate
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...