Voici quelque chose qui m'a toujours mystifié à propos de XSLT:
Exemple:
<person>
<firstName>Deane</firstName>
<lastName>Barker</lastName>
</person>
Voici un fragment de XSLT:
<!-- Template #1 -->
<xsl:template match="/">
<xsl:value-of select="firstName"/> <xsl:value-of select="lastName"/>
</xsl:template>
<!-- Template #2 -->
<xsl:template match="/person/firstName">
First Name: <xsl:value-of select="firstName"/>
</xsl:template>
Deux questions à ce sujet:
Les modèles "ultérieurs" sont-ils donc redevables à ce qui s'est produit dans les modèles "antérieurs", ou opèrent-ils sur le document source, inconscients de ce qui a été transformé "avant"? (Tous ces mots sont entre guillemets, car j'ai du mal à discuter des problèmes temporels alors que je ne sais vraiment pas comment l'ordre des modèles est déterminé en premier lieu ...)
Dans l'exemple ci-dessus, nous avons un modèle qui correspond sur le nœud racine ("/") qui - une fois l'exécution terminée - a essentiellement supprimé tous les nœuds de la sortie. Cela étant, cela empêcherait-il l'exécution de tous les autres modèles, car il n'y a plus rien à faire correspondre une fois le premier modèle terminé?
À ce stade, je me suis inquiété du fait que les modèles ultérieurs ne s'exécutent pas parce que les nœuds sur lesquels ils ont opéré n'apparaissent pas dans la sortie, mais qu'en est-il de l'inverse? Un modèle "antérieur" peut-il créer un nœud avec lequel un modèle "ultérieur" peut faire quelque chose?
Sur le même XML que ci-dessus, considérez ce XSL:
<!-- Template #1 -->
<xsl:template match="/">
<fullName>
<xsl:value-of select="firstName"/> <xsl:value-of select="lastName"/>
</fullName>
</xsl:template>
<!-- Template #2 -->
<xsl:template match="//fullName">
Full Name: <xsl:value-of select="."/>
</xsl:template>
Le modèle n ° 1 crée un nouveau nœud appelé "fullName". Le modèle n ° 2 correspond sur ce même nœud. Le modèle n ° 2 s'exécutera-t-il parce que le nœud "fullName" existe dans la sortie au moment où nous nous déplaçons vers le modèle n ° 2?
Je me rends compte que je suis profondément ignorant du "zen" du XSLT. À ce jour, mes feuilles de style ont consisté en un modèle correspondant au nœud racine, puis sont complètement procédurales à partir de là. J'en ai assez de faire ça. Je préfère réellement comprendre XSLT correctement, d'où ma question.
J'adore ta question. Vous êtes très clair sur ce que vous ne comprenez pas encore. Vous avez juste besoin de quelque chose pour lier les choses. Ma recommandation est que vous lisiez "Comment XSLT fonctionne" , un chapitre que j'ai écrit pour répondre exactement aux questions que vous posez. J'adorerais savoir si cela relie les choses pour vous.
Moins formellement, je vais essayer de répondre à chacune de vos questions.
- Dans quel ordre les modèles s'exécutent-ils et
- Quand ils s'exécutent, correspondent-ils sur (a) le XML source d'origine, ou (b) la sortie actuelle du XSLT à ce point?
À un moment donné du traitement XSLT, il y a, dans un sens, deux contextes, que vous identifiez comme (a) et (b): où vous êtes dans le arbre source, et où vous êtes dans arbre de résultat. Où vous vous trouvez dans l'arborescence source s'appelle nœud actuel. Il peut changer et sauter tout autour de l'arborescence source, lorsque vous choisissez des ensembles arbitraires de nœuds à traiter à l'aide de XPath. Cependant, sur le plan conceptuel, vous ne "sautez" jamais dans l'arbre des résultats de la même manière. Le processeur XSLT le construit de manière ordonnée; il crée d'abord le nœud racine de l'arbre de résultats; puis il ajoute des enfants, construisant le résultat dans l'ordre des documents (profondeur d'abord). [Votre message me motive à reprendre ma visualisation logicielle pour les expériences XSLT ...]
L'ordre des règles de modèle dans une feuille de style n'a jamais d'importance. Vous ne pouvez pas dire, simplement en regardant la feuille de style, dans quel ordre les règles du modèle seront instanciées, combien de fois une règle sera instanciée, ou même si elle le sera. (match="/"
Est une exception; vous pouvez toujours savoir qu'il sera déclenché.)
Je suppose que le modèle n ° 1 s'exécutera en premier. Je ne sais pas pourquoi je suppose cela - est-ce simplement parce qu'il apparaît en premier dans le document?
Nan. Il serait appelé en premier même si vous le mettez en dernier dans le document. L'ordre des règles de modèle n'a jamais d'importance (sauf en cas d'erreur lorsque vous avez plusieurs règles de modèle avec la même priorité correspondant au même nœud; même dans ce cas, il est facultatif pour l'implémenteur et vous ne devez jamais vous fier à un tel comportement). Il est appelé en premier car la première chose qui se produit toujours chaque fois que vous exécutez un processeur XSLT est un appel virtuel à <xsl:apply-templates select="/"/>
. Le seul appel virtuel construit l'arborescence de résultats entière. Rien ne se passe en dehors. Vous pouvez personnaliser ou "configurer" le comportement de cette instruction en définissant des règles de modèle.
Le modèle n ° 2 s'exécutera-t-il? Il correspond à un nœud dans le XML source, mais au moment où nous arrivons à ce modèle (en supposant qu'il s'exécute en deuxième), le nœud "firstName" ne sera pas dans l'arborescence de sortie.
Le modèle n ° 2 (ni aucune autre règle de modèle) ne sera jamais déclenché à moins que vous ayez un appel <xsl:apply-templates/>
Quelque part dans la règle match="/"
. Si vous n'en avez pas, aucune règle de modèle autre que match="/"
Ne sera déclenchée. Pensez-y de cette façon: pour qu'une règle de modèle soit déclenchée, elle ne peut pas simplement correspondre à un nœud dans l'entrée. Il doit correspondre à un nœud que vous choisissez processus (en utilisant <xsl:apply-templates/>
). À l'inverse, il continuera à correspondre au nœud autant de fois que vous choisissez de le traiter.
Est-ce que [le modèle
match="/"
] Empêcherait tous les autres modèles de s'exécuter car il n'y a plus rien à faire correspondre une fois le premier modèle terminé?
Cette règle prévient le reste de nulle part en y incluant <xsl:apply-templates/>
. Il y a encore beaucoup de nœuds qui pourraient être traités dans l'arborescence source. Ils sont toujours tous là, mûrs pour la cueillette; traiter chacun autant de fois que vous le souhaitez. Mais la seule façon de les traiter à l'aide de règles de modèle est d'appeler <xsl:apply-templates/>
.
À ce stade, je me suis inquiété du fait que les modèles ultérieurs ne s'exécutent pas parce que les nœuds sur lesquels ils ont opéré n'apparaissent pas dans la sortie, mais qu'en est-il de l'inverse? Un modèle "antérieur" peut-il créer un nœud avec lequel un modèle "ultérieur" peut faire quelque chose?
Ce n'est pas qu'un modèle "antérieur" crée un nouveau nœud à traiter; c'est qu'un modèle "antérieur" traite à son tour plus de nœuds de l'arborescence source, en utilisant la même instruction (<xsl:apply-templates
). Vous pouvez la considérer comme appelant la même "fonction" récursivement, avec des paramètres différents à chaque fois (les nœuds à traiter déterminés par le contexte et l'attribut select
).
Au final, vous obtenez une pile arborescente d'appels récursifs à la même "fonction" (<xsl:apply-templates>
). Et cette structure arborescente est isomorphe à votre résultat réel. Tout le monde ne s'en rend pas compte ou n'y a pas pensé de cette façon; c'est parce que nous n'avons pas encore d'outils de visualisation efficaces ...
Le modèle n ° 1 crée un nouveau nœud appelé "fullName". Le modèle n ° 2 correspond sur ce même nœud. Le modèle n ° 2 s'exécutera-t-il parce que le nœud "fullName" existe dans la sortie au moment où nous nous déplaçons vers le modèle n ° 2?
Nan. La seule façon de faire une chaîne de traitement est de la configurer explicitement de cette façon. Créez une variable, par exemple, $tempTree
, Qui contient le nouvel élément <fullName>
, Puis traitez it, comme ceci <xsl:apply-templates select="$tempTree">
. Pour ce faire dans XSLT 1.0, vous devez encapsuler la référence de variable avec une fonction d'extension (par exemple, exsl:node-set()
), mais dans XSLT 2.0, cela fonctionnera tel quel.
Que vous traitez des nœuds à partir de l'arborescence source d'origine ou dans une arborescence temporaire que vous construisez, vous devez indiquer explicitement quels nœuds vous souhaitez traiter.
Ce que nous n'avons pas couvert, c'est comment XSLT obtient tout son comportement implicite. Vous devez également comprendre les règles de modèle intégrées. J'écris des feuilles de style tout le temps qui n'incluent même pas de règle explicite pour le nœud racine (match="/"
). Au lieu de cela, je me fie à la règle intégrée pour les nœuds racine (appliquer des modèles aux enfants), qui est la même que la règle intégrée pour les nœuds d'élément. Ainsi, je peux ignorer de grandes parties de l'entrée, laisser le processeur XSLT la parcourir automatiquement et ce n'est que lorsqu'il rencontrera un nœud qui m'intéresse que je ferai quelque chose de spécial. Ou je pourrais écrire une seule règle qui copie tout de manière récursive (appelée la transformation d'identité), en la remplaçant uniquement lorsque cela est nécessaire, pour apporter des modifications incrémentielles à l'entrée. Après avoir lu "Comment fonctionne XSLT", votre prochaine tâche consiste à rechercher la "transformation d'identité".
Je me rends compte que je suis profondément ignorant du "zen" du XSLT. À ce jour, mes feuilles de style ont consisté en un modèle correspondant au nœud racine, puis sont complètement procédurales à partir de là. J'en ai assez de faire ça. Je préfère réellement comprendre XSLT correctement, d'où ma question.
Je t'applaudis. Il est maintenant temps de prendre la "pilule rouge": lire "Comment XSLT fonctionne"
Les modèles toujours correspondent dans le XML source. L'ordre n'a donc pas vraiment d'importance, à moins que 2 modèles ou plus correspondent au (x) même (s) nœud (s). Dans ce cas, quelque peu contre-intuitivement, la règle avec le modèle correspondant last est déclenchée.
Dans votre 1er exemple, le modèle n ° 1 s'exécute car lorsque vous commencez à traiter le XML d'entrée, il commence à la racine et c'est le seul modèle de votre feuille de style qui correspond à l'élément racine. Même s'il était 2e dans la feuille de style, il fonctionnerait toujours 1er.
Dans cet exemple, le modèle 2 ne s'exécutera pas car vous avez déjà traité l'élément racine à l'aide du modèle 1 et il n'y a plus d'éléments à traiter après la racine. Si vous souhaitez traiter d'autres éléments à l'aide de modèles supplémentaires, vous devez le remplacer par.
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
Cela vous permet ensuite de définir un modèle pour chaque élément qui vous intéresse et de traiter le xml de manière plus logique, plutôt que de le faire de manière procédurale.
Notez également que cet exemple ne produira rien car dans le contexte actuel (la racine), il n'y a pas d'élément firstName, seulement un élément person donc il devrait être:
<xsl:template match="/">
<xsl:value-of select="person/firstName"/> <xsl:value-of select="person/lastName"/>
</xsl:template>
Je trouve plus facile de penser que vous parcourez le xml, en commençant à la racine et en recherchant le modèle qui correspond à cet élément, puis en suivant ces instructions pour générer la sortie. Le XSLT transforme le document d'entrée en sortie afin que le doucument de sortie soit vide au début de la transformation. La sortie n'est pas utilisée dans le cadre de la transformation, c'est juste la sortie de celle-ci.
Dans votre 2ème exemple, le modèle n ° 2 ne s'exécutera pas car le modèle est exécuté sur le xml d'entrée et non sur la sortie.
La réponse d'Evan est fondamentalement bonne.
Cependant, une chose qui semble faire défaut est la capacité à "appeler" des morceaux de code sans faire de correspondance. Cela permettrait - au moins de l'avis de certains - de permettre une bien meilleure structuration.
J'ai fait un petit exemple pour essayer de montrer ce que je veux dire.
<xsl:template match="/" name="dotable">
<!-- Surely the common html part could be placed somewhere else -->
<!-- the head and the opening body -->
<html>
<head><title>Salary table details</title></head>
<body>
<!-- Comments are better than nothing -->
<!-- but that part should really have been somewhere else ... -->
<!-- Now do what we really want here ... this really is making the table! -->
<h1>Salary Table</h1>
<table border = "3" width="80%">
<xsl:for-each select="//entry">
<tr>
<td><xsl:value-of select="name" /></td>
<td><xsl:value-of select="firstname" /></td>
<td><xsl:value-of select="age" /></td>
<td><xsl:value-of select="salary" /></td>
</tr>
</xsl:for-each>
</table>
<!-- Now close out the html -->
</body>
</html>
<!-- this should also really be somewhere else -->
<!-- This approach works, but leads to horribly monolithic code -->
<!-- Further - it leads to templates including code which is strictly -->
<!-- not relevant to them. I've not found a way round this yet -->
</xsl:template>
Cependant, après avoir bidouillé un peu, et au début avoir utilisé l'indication que s'il y a deux modèles correspondants, le dernier dans le code sera sélectionné, puis restructurer mon code (pas tous montrés ici), j'ai réussi ce qui semble pour fonctionner, et nous espérons que génère le code correct, ainsi que l'affichage des données souhaitées -
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- <?xml version="1.0"?>-->
<xsl:template name="dohtml">
<html>
<xsl:call-template name="dohead" />
<xsl:call-template name="dobody" />
</html>
</xsl:template>
<xsl:template name="dohead">
<head>
<title>Salary details</title>
</head>
</xsl:template>
<xsl:template name="dobody">
<body>
<xsl:call-template name="dotable" />
</body>
</xsl:template>
<xsl:template match="/entries" name="dotable">
<h1>Salary Table</h1>
<table border = "3" width="80%">
<xsl:for-each select="//entry">
<tr>
<td><xsl:value-of select="name" /></td>
<td><xsl:value-of select="firstname" /></td>
<td><xsl:value-of select="age" /></td>
<td><xsl:value-of select="salary" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="/" name="main">
<xsl:call-template name="dohtml" />
</xsl:template>
[Faites défiler le code ci-dessus de haut en bas si vous ne pouvez pas tout voir]
La façon dont cela fonctionne est que le modèle principal correspond toujours - correspond à /
Cela a les morceaux de code - modèles - qui sont appelés.
Cela signifie maintenant qu'il n'est pas possible de faire correspondre un autre modèle sur/mais il est possible de faire correspondre explicitement sur un nœud nommé, qui dans ce cas est le nœud de plus haut niveau dans les entrées appelées xml.
Une petite modification du code a produit l'exemple donné ci-dessus.