2010-08-26 4 views
1

Nous avons utilisé XML depuis des siècles pour le stockage de données & transfert, mais n'ont jamais eu à le valider ou le transformer. En train de démarrer un nouveau projet et de prendre quelques décisions de conception et besoin de connaître quelques éléments rudimentaires sur XSL & Schémas.XSL/Schémas débutants question

Notre XML est comme ça (excusez l'exemple du livre ennuyeux :)):

<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> 

Nos exigences:

  1. Transformation

    a) Turn "US" dans États-Unis
    b) si Prix> 20 créer un nouvel élément <Expensive>True</Expensive>

    Je suppose que cela est fait avec XSLT, mais quelqu'un peut-il me donner quelques indications sur la façon d'y parvenir?

  2. Validation

    a) est un entier ID, est le prix d'un flotteur (pour être honnête)
    b) Est-ce que tous les tags rempli d'emploi le plus important, par exemple l'étiquette de nom n'est pas remplie (2e plus important)
    c) Toutes les étiquettes sont-elles présentes, par ex. Il manque un pays pour le livre 2
    d) [Probablement difficile] L'élément ID est-il unique dans tous les livres? (Agréable d'avoir)

D'après ce que j'ai lu ceci est fait avec un schéma ou Relax NG, mais les résultats peuvent être de la validation à un simple émis HTML pour afficher une liste ou d'erreurs?

par exemple.
Livre 1: Prix "Prix. ??" n'est pas flottant
Livre 2: ID n'est pas unique, Nom vide, Pays manquant

Ou serait-il préférable de faire ces choses par programmation en C#? Merci.

Répondre

3

Cette feuille de style:

<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> 

Avec votre entrée, sortie:

<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> 

Avec l'entrée correcte:

<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> 

Sortie:

<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> 

Remarque: utilisation des touches pour des performances. Ceci est une preuve de concept. Dans la vie réelle, la sortie XHTML doit être enveloppée dans une instruction xsl:message. De http://www.w3.org/TR/xslt#message

L'instruction xsl: message envoie un message d'une manière qui dépend de le processeur XSLT.Le contenu de la instruction xsl: message est un modèle. Le xsl: message est instancié par instancier le contenu pour créer un fragment XML . Ce fragment XML est le contenu du message. REMARQUE: Un processeur XSLT peut implémenter le message xsl: en ouvrant une boîte d'alerte ou en écrivant dans un fichier journal.

Si l'attribut fin a la valeur oui, alors le processeur XSLT doit terminer le traitement après l'envoi du message. La valeur par défaut est non.

Modifier: code de compactage et problème de carte de pays d'adressage.

Edit 2: Dans la vraie vie, avec de grands documents XML et d'autres outils de ENTERPRICE, la meilleure approche serait d'exécuter la transformation avec le processeur de schéma courant XSLT 2.0 pour valider ou exécuter la validation independly avec bien savoir schéma validateurs. Si pour une raison quelconque de ces choix ne sont pas aviable, ne vont pas avec ma preuve de concept réponse, car ayant des clés pour chaque règle de validation font cause beaucoup d'utilisation de la mémoire pour les grands documents. Le meilleur moyen pour le dernier cas est d'ajouter des règles pour intercepter les erreurs de validation terminant la transformation avec un message. A titre d'exemple, cette feuille de style:

<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> 

Avec votre entrée, ce message arrête la transformation:

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

Avec l'entrée correcte, émet le même que précédemment.

+0

vache sainte, je ne vais pas faire semblant de comprendre tout cela encore (qui ressemble à XSLT est un artform aquired!), Mais il fait exactement ce que nous voulons pas trop de lignes! Une question supplémentaire: si le code de pays est une liste de recherche, par ex. US = États-Unis, CA = Canada, le feriez-vous toujours de la même façon? –

+0

@Andre White: Voir ma modification adressant votre demande et une suggestion plus réelle de la vie. –

0

Voici le RelaxNG schema:

<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> 

et c'est la version du schéma XML. (Je crois.)

<?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> 

Couple de choses à noter ici:

  • Le type simple ID provoquera validateurs pour vérifier les multiples occurrences de l'identifiant, et se plaignent s'il y a lieu. Un inconvénient de l'utilisation des étiquettes d'identification est cependant qu'ils ne peuvent pas commencer avec un nombre. Ainsi, A1, A2, ... Un serait bien, mais comme ID 1, 2, ...., n serait considéré comme nul de toute façon.
  • Le prix a été établi pour être de type decimal. Float n'est jamais un type approprié pour les numéros financiers, en raison d'erreurs d'arrondi.

En l'utilisant xmllint le document XML d'origine en entrée (avec des identifiants modifiés) donne:

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 
0

sur l'enseignement général XSL, vous pouvez trouver utile un XSL Primer je l'ai écrit il y a quelques années. Il n'est pas à jour sur toutes les dernières tendances, mais couvre les bases de la façon dont le document XML est traité.