Ce chapitre décrit la fonctionnalité optionnelle "Traversal" du DOM niveau 2. Ses interfaces
TreeWalker,
NodeIterator
et NodeFilter permettent de traverser facilement, de façon fiable et
sélectivement le contenu d'un document.
Les interfaces décrites dans cette section ne sont pas obligatoires. Une application DOM peut employer la méthode
hasFeature(feature, version) de l'interface DOMImplementation,
respectivement avec les valeurs de paramètre "Traversal" et "2.0", pour déterminer si ce module est reconnu ou non par la mise en œuvre.
Pour la gestion complète de ce module, une mise en œuvre doit également gérer la fonctionnalité "Core"
définie dans la spécification DOM niveau 2 Core [DOM niveau 2 Core].
Consulter la section à propos de la
conformité →vf
dans la spécification DOM niveau 2 Core [DOM niveau 2 Core]
pour d'autres renseignements.
Les interfaces NodeIterator
et TreeWalker permettent de représenter de deux façons
les nœuds du sous-arbre d'un document et une position dans les nœuds de ce sous-arbre. L'interface
NodeIterator présente une vue à plat du sous-arbre
en tant que séquence ordonnée de nœuds, dans l'ordre du document. Comme cette vue se présente indépendamment de la hiérarchie,
les itérateurs disposent de méthodes pour avancer ou reculer mais pas pour remonter ou descendre.
Au contraire, l'interface TreeWalker maintient les relations hiérarchiques du
sous-arbre, qui permettent une navigation dans la hiérarchie. En général, les objets
TreeWalker se prêteront mieux aux tâches
où on manipulera la structure du document autour des nœuds sélectionnés, tandis que
les itérateurs NodeIterator se prêteront mieux
aux tâches qui se concentrent sur le contenu de chaque nœud sélectionné.
Les interfaces NodeIterator
et TreeWalker présentent chacune une vue d'un sous-arbre du document
qui ne contiendra peut-être pas tous les nœuds présents dans le sous-arbre. Dans cette spécification, on l'appelle la vue logique
en la distinguant de la vue physique qui correspond au sous-arbre du document en soi. Lorsqu'on crée un itérateur, ou un objet
TreeWalker, on peut l'associer
à un filtre NodeFilter, lequel examine chaque nœud et
détermine son apparition ou non dans la vue logique. En outre, on peut utiliser des indicateurs
pour définir les types de nœud susceptibles d'apparaître dans la vue logique.
Les objets NodeIterator
et TreeWalker sont dynamiques : la vue logique change
pour refléter les changements opérés sur le document sous-jacent. Toutefois, ils se distinguent dans leurs réponses à ces changements.
Les itérateurs NodeIterator, qui présentent les nœuds en séquence,
essayent de maintenir leur emplacement relativement à une position dans la séquence quand son contenu change.
Les objets TreeWalker, qui présentent les nœuds sous une forme
d'arbre filtré, maintiennent leur emplacement relativement à leur nœud courant et lui restent attachés quand ce nœud
se déplace dans un nouveau contexte. Ces comportements sont expliqués plus loin en détails.
NodeIteratorL'itérateur NodeIterator permet de retourner
les membres d'une liste de nœuds en séquence. Dans les interfaces DOM courantes, cette liste se composera toujours
des nœuds d'un sous-arbre, présentés dans l'ordre du document.
À la création d'un itérateur, l'appel de sa méthode nextNode() retourne le premier nœud dans la vue logique du sous-arbre ;
dans la plupart des cas, il s'agit de la racine du sous-arbre.
Chaque appel successif fait avancer l'objet NodeIterator
à travers la liste, en retournant le nœud suivant disponible dans la vue logique. La méthode nextNode()
retourne la valeur null quand il n'y a plus de nœud visible.
On crée les itérateurs NodeIterator en utilisant la
méthode createNodeIterator qui se trouve dans l'interface DocumentTraversal.
Quand on crée un itérateur NodeIterator, on peut utiliser des indicateurs
pour déterminer quels types de nœud seront « visibles » et quels nœuds seront « invisibles »
pendant la traversée de l'arbre ; ces indicateurs peuvent se combiner avec l'opérateur OR. L'itérateur
dépasse les nœuds invisibles comme s'ils n'existaient pas.
Le code suivant crée un itérateur puis appelle une fonction pour imprimer le nom de chaque élément :
NodeIterator iter=
((DocumentTraversal)document).createNodeIterator(
root, NodeFilter.SHOW_ELEMENT, null);
while (Node n = iter.nextNode())
printMe(n);
Les itérateurs NodeIterator présentent les nœuds sous forme d'une
liste ordonnée et avancent ou reculent dans celle-ci. L'itérateur est toujours positionné entre deux nœuds, avant le premier ou bien
après le dernier. À sa création, l'itérateur est positionné avant le premier élément. Le diagramme suivant montre une vue de la liste d'un
sous-arbre particulier par un itérateur, sa position est marquée par un caractère astérisque « * » :
* A B C D E F G H I
Chaque appel de la méthode nextNode() retourne le nœud suivant et avance la position.
Par exemple, en commençant à partir de la position précédente, le premier appel de nextNode() retourne A
et avance l'itérateur à la position suivante :
[A] * B C D E F G H I
La position d'un itérateur NodeIterator est mieux décrite par rapport
au dernier nœud retourné, qu'on appelle le nœud de référence. Quand on crée un itérateur,
le premier nœud est le nœud de référence et l'itérateur se positionne avant lui. Dans les diagrammes suivants,
le nœud de référence est signalé par des crochets.
L'appel de la méthode previousNode() retourne le nœud précédent et recule la position. Par exemple, en commençant
avec l'itérateur entre A
et B
, la méthode retournera A
et déplacera la position comme ceci :
* [A] B C D E F G H I
Si on appelle la méthode nextNode() à la fin d'une liste, ou si on appelle previousNode()
au début d'une liste, la valeur null sera retournée et la position de l'itérateur ne changera pas.
À la création d'un itérateur NodeIterator, le nœud de référence
est le premier :
* [A] B C D E F G H I
Un itérateur NodeIterator peut être actif pendant l'édition de la
structure de données où il navigue, et un itérateur doit donc avoir un comportement progressif face au changement.
Les ajouts et retraits dans la structure de données sous-jacente n'invalident pas l'objet NodeIterator ;
en fait, il n'est jamais invalidé tant que l'on n'invoque pas sa méthode detach(). Afin d'y parvenir,
l'itérateur utilise le nœud de référence pour maintenir sa position. L'état d'un itérateur peut aussi dépendre de sa position avant
ou après le nœud de référence.
Si les changements sur la liste itérée ne produisent pas le retrait du nœud de référence, alors ils n'affectent pas
l'état de l'itérateur NodeIterator.
Par exemple, l'état de l'itérateur n'est pas affecté par l'insertion de nouveaux nœuds à son voisinage ou par le retrait
d'autres nœuds hormis celui de référence. Soit la situation initiale suivante :
A B C [D] * E F G H I
Retirons E
. L'état final est le suivant :
A B C [D] * F G H I
Lorsqu'on insère un nouveau nœud, l'itérateur NodeIterator
reste au contact du nœud de référence, et l'insertion d'un nœud X
entre D
et F
interviendra donc
entre l'itérateur et F
:
A B C [D] * X F G H I
Le déplacement d'un nœud équivaut à un retrait suivi d'une insertion. Si on déplace et positionne I
avant X
,
le résultat est le suivant :
A B C [D] * I X F G H
Si on retire le nœud de référence pendant l'itération de la liste, un autre nœud est sélectionné
comme nœud de référence. Si la position du nœud de référence est avant celle de
l'itérateur NodeIterator, ce qui est normalement le cas
après un appel de la méthode nextNode(), le nœud le plus proche avant l'itérateur est sélectionné comme
nœud de référence. Supposons que l'on retire le nœud D
dans la situation initiale suivante :
A B C [D] * F G H I
Le nœud C
devient le nouveau nœud de référence, car il est le plus proche de
l'itérateur NodeIterator et avant lui :
A B [C] * F G H I
Si le nœud de référence est après l'itérateur NodeIterator,
ce qui est normalement le cas après un appel de la méthode previousNode(), le nœude le plus proche après l'itérateur
est sélectionné comme nœud de référence. Supposons que l'on retire E
dans la situation initiale suivante :
A B C D * [E] F G H I
Le nœud F
devient le nouveau nœud de référence, car il est le plus proche de
l'itérateur NodeIterator et après lui :
A B C D * [F] G H I
Comme indiqué précédemment, déplacer un nœud équivaut à le retirer puis à l'insérer.
Supposons que l'on veuille déplacer le nœud D
à la fin de la liste dans la situation initiale suivante :
A B C [D] * F G H I C
L'état final sera le suivant :
A B [C] * F G H I D
Le retrait d'un nœud de référence quand sa position est en fin de la liste représente un cas particulier.
Supposons que l'on retire le nœud C
dans la situation initiale suivante :
A B * [C]
Conformément aux règles précédentes, le nouveau nœud de référence devrait être celui le plus proche après
l'itérateur NodeIterator mais il n'y en a aucun après C
.
La même situation peut se produire quand la méthode previousNode() vient juste de retourner le premier nœud de la liste
lequel est retiré ensuite. Donc, s'il n'y a aucun nœud en direction originale du nœud de référence,
le nœud le plus proche dans la direction opposée est sélectionné comme nœud de référence :
A [B] *
Si l'itérateur NodeIterator est positionné dans un bloc
de nœuds qui est retiré, la règle précédente indique clairement l'action entreprise. Par exemple, supposons que C
soit le nœud parent de D
, E
et F
et que l'on retire C
dans la situation initiale suivante :
A B C [D] * E F G H I D
L'état final sera le suivant :
A [B] * G H I D
En définitive, remarquer que le retrait du nœud racine d'un
itérateur NodeIterator de son
parent n'altère pas la liste en cours d'itération et ne change donc pas
l'état de l'itérateur.
La structure de données sous-jacente en cours d'itération peut contenir des nœuds qui ne font pas partie de la vue logique et qui,
de ce fait, ne seront pas retournés par l'itérateur NodeIterator.
Si un nœud est exclu en conséquence de la valeur de l'indicateur whatToShow, la méthode nextNode()
retourne le nœud visible suivant et dépasse en les excluant ceux qui sont « invisibles ». Si un
filtre NodeFilter est présent, il s'appliquera avant de retourner le nœud ;
si le filtre n'accepte pas le nœud, le processus se répète jusqu'à ce que le filtre en accepte un et que le nœud soit retourné.
Si aucun nœud visible n'est rencontré, la valeur null est retournée et l'itérateur se positionne à la fin de la liste.
Auquel cas, le nœud de référence est le dernier nœud, qu'il soit visible ou non. La même approche est adoptée
pour la méthode previousNode(), en direction opposée.
Dans les exemples suivants, on utilisera des lettres minuscules pour représenter les nœuds présents dans la structure de données mais absents de la vue logique. Par exemple, prenons la liste suivante :
A [B] * c d E F G
Un appel de la méthode nextNode() retourne E
et fait avancer l'itérateur à la position suivante :
A B c d [E] * F G
Les nœuds invisibles peuvent toutefois servir de référence, si un nœud de référence est retiré.
Supposons que le nœud E
soit retiré dans la situation initiale précédente. L'état final est le suivant :
A B c [d] * F G
Supposons qu'un nouveau nœud X
visible soit inséré avant d
. L'état final est le suivant :
A B c X [d] * F G
Remarquer qu'un appel de la méthode previousNode() retourne désormais le nœud X
. Il importe de ne pas
dépasser les nœuds invisibles quand le nœud de référence est retiré car, dans certains cas tel que le précédent,
des résultats erronés seront retournés. Quand E
a été retourné, si le nouveau nœud de référence avait été B
au lieu de
d
, l'appel de la méthode previousNode() n'aurait pas retourné X
.
NodeFilterL'interface NodeFilter permet à l'utilisateur de
créer des objets qui « trient » les nœuds. Chaque filtre contient une fonction écrite par l'utilisateur qui examine le nœud
et détermine s'il doit être présenté ou non comme partie de la vue logique de la traversée du document. Pour utiliser un
filtre NodeFilter, on crée un
objet NodeIterator, ou
TreeWalker, qui l'utilise. Le
moteur de traversée applique le filtre à chaque nœud et,
si le filtre ne retient pas le nœud, la traversée dépasse le nœud comme s'il n'était pas présent dans le document.
Les filtres NodeFilter n'ont pas besoin de savoir comment
naviguer dans la structure contenant les nœuds sur lesquels ils agissent.
Les filtres sont consultés lorsqu'une opération de traversée a lieu, ou lorsqu'on retire le nœud de référence d'un
itérateur NodeIterator dans le sous-arbre en cours d'itération et
que l'itérateur doit en sélectionner un nouveau. Toutefois, la temporisation exacte des appels de ces filtre peut varier
d'une mise en œuvre DOM à l'autre. Pour cette raison,
les filtres NodeFilter ne devraient pas essayer de maintenir d'état
fondé sur l'historique des invocations passées ; le comportement final ne sera peut-être pas portable.
De même, les objets TreeWalker et
NodeIterator devraient se comporter comme s'ils n'avaient
aucune mémoire des résultats de filtrages passés ni anticipaient de résultats futurs.
Si les conditions examinées par un filtre NodeFilter ont changé
(par exemple, un attribut testé a été ajouté ou retiré) depuis la dernière logique de traversée du nœud,
ce changement de visibilité ne sera découvert qu'à la prochaine opération de traversée. Par exemple, si la constante de filtrage
du nœud courant change de FILTER_SHOWFILTER_ACCEPT à FILTER_SKIP, un
objet TreeWalker pourra naviguer hors de ce nœud, dans toutes les
directions, mais pas y revenir tant que les conditions de filtrage n'auront pas à nouveau changé. On peut écrire des
filtres NodeFilter qui changent pendant une traversée
mais leur comportement pouvant être ambigu on devrait si possible éviter de le faire.
NodeFilterUn filtre NodeFilter contient une seule méthode acceptNode()
qui permet à un itérateur NodeIterator ou à un
objet TreeWalker de passer un nœud Node au filtre,
afin qu'il détermine si ce nœud devrait être présent ou non dans la vue logique. La fonction acceptNode()
retourne l'une de trois valeurs, qui indiquent comment traiter le nœud Node.
Si acceptNode() retourne la valeur FILTER_ACCEPT, le nœud Node sera présent dans la vue logique ;
pour la valeur FILTER_SKIP, le nœud ne sera pas présent dans la vue logique quoique ses enfants puissent l'être ;
pour la valeur FILTER_REJECT, ni le nœud ni ses descendants
ne seront dans la vue logique. Puisque les itérateurs NodeIterator
présentent les nœuds sous forme d'une liste ordonnée sans hiérarchie, les valeurs FILTER_REJECT
et FILTER_SKIP sont synonymes pour ces itérateurs qui ne dépassent que le seul nœud courant.
Supposons un filtre qui accepte les ancres nommées dans un document HTML. En HTML, un attribut HREF peut
désigner tout élément A ayant un attribut NAME. Voici un filtre NodeFilter
en Java pour examiner un nœud et déterminer s'il s'agit d'une ancre nommée :
class NamedAnchorFilter implements NodeFilter
{
short acceptNode(Node n) {
if (n.getNodeType()==Node.ELEMENT_NODE) {
Element e = (Element)n;
if (! e.getNodeName().equals(A
))
return FILTER_SKIP;
if (e.getAttributeNode("NAME") != null)
return FILTER_ACCEPT;
}
return FILTER_SKIP;
}
}
Si le filtre précédent NodeFilter ne devait servir qu'avec des
itérateurs NodeIterator,
il pourrait utiliser la valeur FILTER_REJECT à chaque fois que la valeur FILTER_SKIP était employée, et
le comportement ne changerait pas. Par contre, pour un objet TreeWalker,
une valeur FILTER_REJECT rejetterait les enfants de tout élément qui n'est pas une ancre nommée et,
comme les ancres nommées sont toujours contenues dans d'autres éléments, aucune ancre nommée n'aurait été trouvée.
La valeur FILTER_SKIP rejette le nœud en question mais continue d'examiner les enfants ; le filtre précédent
fonctionnera donc avec un itérateur NodeIterator comme avec un
objet TreeWalker.
Pour utiliser ce filtre, l'utilisateur devra créer une instance de filtre NodeFilter
et un itérateur NodeIterator qui l'utilise :
NamedAnchorFilter myFilter = new NamedAnchorFilter();
NodeIterator iter=
((DocumentTraversal)document).createNodeIterator(
node, NodeFilter.SHOW_ELEMENT, myFilter);
Remarquer que l'emploi de l'indicateur SHOW_ELEMENT n'est pas strictement nécessaire dans cet exemple,
dans la mesure où le filtre NodeFilter teste
l'attribut nodeType (N.D.T. getNodeType). Toutefois, certaines mises en œuvre
des interfaces Traversal peuvent améliorer les performances de l'attribut whatToShow en profitant d'une connaissance
de la structure du document, ce qui rend l'utilisation de SHOW_ELEMENT précieuse. Au contraire, alors qu'on pouvait supprimer
le test de l'attribut nodeType dans le filtre, le filtre serait devenu dépendant de l'attribut whatToShow
pour distinguer les objets de type Element, Attr et ProcessingInstruction.
NodeFilter et les exceptionsPour l'écriture d'un filtre NodeFilter, les
utilisateurs devraient éviter d'écrire du code susceptible de soulever une exception. Néanmoins, puisqu'une mise en œuvre DOM
ne peut pas empêcher les exceptions, il importe de bien définir le comportement des filtres susceptibles de le faire.
Un itérateur TreeWalker,
ou un objet NodeIterator, ne capture pas ni n'altère l'exception lancée
par un filtre mais la laisse se propager jusqu'au code de l'utilisateur. Les fonctions suivantes peuvent invoquer un
filtre NodeFilter et peuvent donc propager une exception
si le filtre en lance une :
NodeIterator.nextNode()NodeIterator.previousNode()TreeWalker.firstChild()TreeWalker.lastChild()TreeWalker.nextSibling()TreeWalker.previousSibling()TreeWalker.nextNode()TreeWalker.previousNode()TreeWalker.parentNode()NodeFilter et la mutation du documentLes filtres NodeFilter bien conçus ne devraient pas avoir à modifier
la structure sous-jacente du document. Mais une mise en œuvre DOM ne peut pas empêcher l'utilisateur d'écrire un filtre
qui altère la structure du document. Le module Traversal ne propose aucun traitement spécial pour gérer ce cas.
Par exemple, si un filtre NodeFilter retire un nœud d'un document,
il peut toujours l'accepter, c'est-à-dire que le nœud peut être retourné par
l'itérateur NodeIterator,
ou l'objet TreeWalker, bien qu'il ne soit plus dans le sous-arbre traversé.
En général, cela peut conduire à des résultats incohérents et ambigus, et on encourage donc les utilisateurs à écrire des
filtres NodeFilter qui ne changent pas la structure du document.
Faites plutôt l'édition dans la boucle contrôlée par l'objet de traversée.
NodeFilter et les indicateurs whatToShowLes itérateurs NodeIterator et les
objets TreeWalker appliquent leurs indicateurs whatToShow
avant d'appliquer les filtres. Si un nœud est dépassé du fait des indicateurs actifs whatToShow,
aucun filtre NodeFilter ne sera appelé pour évaluer le nœud.
Remarquer que ce comportement est similaire à celui pour la valeur FILTER_SKIP ; les enfants du nœud
seront pris en compte et des filtres pourront être appelés pour les évaluer. Remarquer aussi qu'il s'agira en réalité d'un
d'un « dépassement » même si le filtre NodeFilter
aurait préféré rejeter le sous-arbre entier ; si cela posait un problème pour votre application, fixez la valeur de whatToShow
à SHOW_ALL et faites le test nodeType dans le filtre.
TreeWalkerL'interface TreeWalker offre beaucoup des avantages de
l'interface NodeIterator. La différence principale entre les deux
réside dans le fait que l'interface TreeWalker
présente une vue arborescente des nœuds dans un sous-arbre au lieu de la vue en liste de l'itérateur. En d'autres termes,
un itérateur permet de se déplacer en avant et en arrière, tandis qu'un
objet TreeWalker permet de se déplacer au
parent d'un nœud, à l'un de ses enfants ou
à un frère.
L'utilisation de l'interface TreeWalker ressemble assez à
une navigation en se servant directement du nœud, les méthodes de navigation des deux interfaces étant analogues.
Par exemple, voici une fonction qui parcourt récursivement un arbre de nœuds dans l'ordre du document,
en entreprenant des actions distinctes d'abord pour l'accès au nœud puis après le traitement des enfants :
processMe(Node n) {
nodeStartActions(n);
for (Node child=n.firstChild();
child != null;
child=child.nextSibling()) {
processMe(child);
}
nodeEndActions(n);
}
Faire la même chose avec un objet TreeWalker
est très semblable. Sauf que, puisque la navigation avec
l'objet TreeWalker change la position courante,
la position aura changé à la fin de la fonction. Un attribut en lecture/écriture appelé currentNode
permet de rechercher et de fixer la valeur du nœud courant d'un
objet TreeWalker. On l'utilisera pour s'assurer que la
position de l'objet TreeWalker sera restaurée
quand la fonction aura terminé :
processMe(TreeWalker tw) {
Node n = tw.getCurrentNode();
nodeStartActions(tw);
for (Node child=tw.firstChild();
child!=null;
child=tw.nextSibling()) {
processMe(tw);
}
tw.setCurrentNode(n);
nodeEndActions(tw);
}
L'avantage d'employer un objet TreeWalker au lieu d'une
navigation directe par œud est que l'objet TreeWalker
permet à l'utilisateur de choisir une vue appropriée de l'arbre. On peut utiliser des indicateurs pour exposer ou cacher des
nœuds Comment ou ProcessingInstruction ; on peut développer ou exposer des entités
sous forme de nœuds EntityReference. En outre, on peut utiliser des
filtres NodeFilter pour présenter une vue personnalisée de l'arbre.
Supposons qu'un programme ait besoin d'une vue d'un document qui montre quelles tables apparaissent dans chaque chapitre,
listées par chapitre. Dans cette vue, seuls les éléments CHAPTER et les éléments TABLE qu'ils contiennent sont visibles.
La première étape consiste à écrire un filtre adéquat :
class TablesInChapters implements NodeFilter {
short acceptNode(Node n) {
if (n.getNodeType()==Node.ELEMENT_NODE) {
if (n.getNodeName().equals("CHAPTER"))
return FILTER_ACCEPT;
if (n.getNodeName().equals("TABLE"))
return FILTER_ACCEPT;
if (n.getNodeName().equals("SECT1")
|| n.getNodeName().equals("SECT2")
|| n.getNodeName().equals("SECT3")
|| n.getNodeName().equals("SECT4")
|| n.getNodeName().equals("SECT5")
|| n.getNodeName().equals("SECT6")
|| n.getNodeName().equals("SECT7"))
return FILTER_SKIP;
}
return FILTER_REJECT;
}
}
Ce filtre suppose que les éléments TABLE soient directement contenus dans des éléments CHAPTER ou SECTn. En cas de rencontre d'un autre type d'élément, l'élément et ses enfants seront rejetés. En cas de rencontre d'un élément SECTn, l'élément sera dépassé mais ses enfants seront explorés pour voir s'ils contiennent des éléments TABLE.
Le programme peut désormais créer une instance de ce filtre NodeFilter,
créer un objet TreeWalker qui l'utilise et passer cet
objet TreeWalker à la fonction ProcessMe() :
TablesInChapters tablesInChapters = new TablesInChapters();
TreeWalker tw =
((DocumentTraversal)document).createTreeWalker(
root, NodeFilter.SHOW_ELEMENT, tablesInChapters);
processMe(tw);
(Une fois encore, on a choisi à la fois de tester l'attribut nodeType conformément à la logique du filtre et
d'utiliser l'indicateur SHOW_ELEMENT, pour les raisons abordées dans l'exemple antérieur avec
l'itérateur NodeIterator).
Sans changer la fonction ProcessMe() précédente, elle ne traitera maintenant que les éléments CHAPTER et TABLE.
Le programmeur peut écrire d'autres filtres ou paramétrer d'autres indicateurs pour sélectionner des jeux de nœuds différents ;
si les fonctions utilisent un objet TreeWalker pour naviguer,
elles reconnaîtront toute vue du document définie avec un objet TreeWalker.
Remarquer que la structure de la vue filtrée d'un document d'un objet TreeWalker
peut différer significativement de la vue du document en question. Par exemple, un objet TreeWalker,
dont le paramètre whatToShow a seulement la valeur SHOW_TEXT, présenterait tous les nœuds Text
comme si tous étaient frères et pourtant n'avaient aucun parent.
Comme pour les itérateurs NodeIterator,
un objet TreeWalker peut être actif, alors que la
structure de données où il navigue est en cours d'édition, et il doit donc avoir un comportement progressif face au changement.
Les ajouts et retraits dans la structure de données sous-jacente n'invalident pas un
objet TreeWalker ; en fait,
un objet TreeWalker n'est jamais invalidé.
Mais la réponse d'un objet TreeWalker face aux changements
est très différente de celle de l'itérateur NodeIterator.
Tandis que les itérateurs NodeIterator
répondront à une édition en maintenant leur position dans la liste itérée, les
objets TreeWalker resteront plutôt attachés
au nœud désigné par leur attribut currentNode. Toutes les méthodes de navigation de
l'interface TreeWalker opèrent au niveau du nœud courant
currentNode au moment où on les invoquent, quoiqu'il soit arrivé au nœud, ou autour du nœud,
depuis son dernier accès par l'objet TreeWalker.
Ceci reste vrai même si l'objet currentNode est sorti de son sous-arbre d'origine.
Pour exemple, considérons le fragment de document suivant :
...
<subtree>
<twRoot>
<currentNode/>
<anotherNode/>
</twRoot>
</subtree>
...
Supposons que l'on ait créé un objet TreeWalker
dont le nœud racine est l'élément <twRoot/> et dont le nœud courant est l'élément <currentNode/>.
Pour cette illustration, on supposera que tous les nœuds montrés ci-dessus sont acceptés par les indicateurs whatToShow
et les filtres de l'objet TreeWalker.
Si on utilise la méthode removeChild() pour retirer l'élément <currentNode/>
de son parent, cet élément reste le nœud courant currentNode
de l'objet TreeWalker, même s'il n'est plus dans le
sous-arbre du nœud racine. On peut toujours utiliser
l'objet TreeWalker pour naviguer à travers les enfants
éventuels du nœud currentNode orphelin, mais on ne peut plus naviguer hors du contexte de ce nœud currentNode
puisqu'il n'y a aucun parent disponible.
Si on utilise la méthode insertBefore(), ou appendChild(), pour donner
un nouveau parent à l'élément <currentNode/>, alors la navigation de
l'objet TreeWalker opérera à partir de la nouvelle position
du nœud courant currentNode. Par exemple, si on insère <currentNode/> immédiatement après l'élément <anotherNode/>,
la méthode previousSibling() de l'objet TreeWalker
établirait <anotherNode/> comme nœud courant et l'appel de la méthode parentNode() remonterait à l'élément <twRoot/>.
Si au lieu de cela on insère le nœud courant currentNode dans l'élément <subtree/> de cette façon :
...
<subtree>
<currentNode/>
<twRoot>
<anotherNode/>
</twRoot>
</subtree>
...
On aura sorti le nœud courant currentNode du nœud racine de
l'objet TreeWalker. Cela n'invalide pas l'objet
TreeWalker, et on peut toujours l'utiliser pour
naviguer par rapport au nœud courant currentNode. Par exemple, l'appel de la méthode parentNode()
l'établirait sur l'élément <subtree/> quoiqu'il soit aussi en-dehors du nœud racine d'origine.
Par contre, si la navigation de l'objet TreeWalker
devait le ramener dans le sous-arbre du nœud racine original (par exemple, au lieu d'appeler la méthode parentNode(),
si on avait appelé nextNode() qui déplacerait
l'objet TreeWalker sur l'élément <twRoot/>)
le nœud racine « recapturera » l'objet TreeWalker
et l'empêchera de retourner en arrière.
Les choses sont un peu plus compliquées avec des filtres. Le repositionnement du nœud courant currentNode
(ou la sélection explicite d'un nouveau currentNode, ou le changement des conditions qui interviennent dans la décision
du filtre NodeFilter) peut amener un objet
TreeWalker à avoir un nœud courant currentNode
qui ne serait pas visible sinon dans la vue (logique) filtrée du document. On peut assimiler ce nœud à un
« membre éphémère » de cette vue. Quand on demande
à l'objet TreeWalker de naviguer hors de ce nœud,
c'est comme s'il avait été visible mais qu'on ne puisse pas retourner à lui tant que les conditions n'auront pas changé
pour le rendre à nouveau visible.
Notamment, si le nœud courant currentNode devient partie d'un sous-arbre qui aurait été rejeté
par le filtre sinon, ce sous-arbre entier peut être ajouté comme membre éphémère de la vue logique.
On pourra naviguer dans ce sous-arbre (soumis au filtrage normal) tant qu'on ne va pas en amont
de l'ancêtre rejeté. Le comportement est comme si seul le
nœud rejeté avait été dépassé (puisqu'on s'est promené dans son sous-arbre)
jusqu'à ce qu'on en sorte ; le filtrage normal s'applique ensuite.
Les itérateurs servent à parcourir un ensemble de nœuds, par exemple, l'ensemble de nœuds d'un objet NodeList,
le sous-arbre d'un document commandé par un objet Node particulier, le résultat d'une recherche
ou tout autre ensemble de nœuds. L'ensemble de nœuds à itérer est déterminé par la mise en œuvre de
l'itérateur NodeIterator. Le DOM niveau 2 définit une seule mise en œuvre NodeIterator
pour la traversée d'un sous-arbre d'un document, dans l'ordre du document. Les instances de ces itérateurs sont créées
en appelant la fonction DocumentTraversal.createNodeIterator().
// Introduite dans DOM niveau 2 :
interface NodeIterator {
readonly attribute Node root;
readonly attribute unsigned long whatToShow;
readonly attribute NodeFilter filter;
readonly attribute boolean expandEntityReferences;
Node nextNode()
raises(DOMException);
Node previousNode()
raises(DOMException);
void detach();
};
expandEntityReferences de type boolean, en lecture seulefalse, ces whatToShow et le filtre. Remarquer également que c'est actuellement
la seule situation où un itérateur NodeIterator pourra rejeter un sous-arbre entier au lieu de dépasser
des nœuds individuels.whatToShow pour cacher le nœud de référence d'entité et fixez la valeur true
à l'attribut expandEntityReferences à la création de l'itérateur. Pour obtenir une vue du document avec des
nœuds d'appel d'entité sans résolution des entités, utilisez les indicateurs whatToShow pour exposer
le nœud d'appel d'entité et fixez la valeur false à l'attribut expandEntityReferences.filter de type NodeFilter, en lecture seuleNodeFilter utilisé pour trier les nœuds.root de type Node, en lecture seuleNodeIterator comme défini à sa création.whatToShow de type unsigned long, en lecture seuleNodeFilter. Les nœuds non acceptés par
whatToShow seront dépassés mais leurs enfants peuvent encore être pris en compte.
Remarquer que le dépassement prime, le cas échéant, sur le filtre.detachNodeIterator de l'ensemble dans lequel il itérait, en libérant
toutes les ressources de calcul et en plaçant l'itérateur dans l'état INVALID. Après l'invocation de la méthode detach,
les appels des méthodes nextNode ou previousNode soulèveront une exception INVALID_STATE_ERR.
nextNodeNodeIterator, le premier appel de la méthode nextNode() retournera le premier nœud
dans l'ensemble.
|
|
L'objet |
|
|
INVALID_STATE_ERR : Soulevée si on appelle cette méthode après que la méthode |
previousNodeNodeIterator.
|
|
L'objet |
|
|
INVALID_STATE_ERR : Soulevée si on appelle cette méthode après que la méthode |
Les filtres sont des objets qui savent trier
les nœuds. Si on attribue un filtre NodeFilter à un
itérateur NodeIterator, ou à un
objet TreeWalker, ce dernier applique le filtre avant de retourner
le nœud suivant. Si le filtre indique d'accepter le nœud, la logique de traversée le retourne ; sinon la traversée examine
le nœud suivant et fait comme si le nœud rejeté n'était pas là.
Le DOM ne fournit aucun filtre. Le filtre NodeFilter n'est qu'une interface que les utilisateurs
peuvent mettre en œuvre afin de créer leurs propres filtres.
Les filtres NodeFilter n'a pas besoin de savoir comment passer d'un nœud à l'autre ni de connaître quoi que ce soit
au sujet de la structure de données traversée. Il est donc très facile d'écrire des filtres, puisqu'ils doivent uniquement savoir évaluer
un seul nœud. On peut se servir d'un filtre pour plusieurs types de traversée, ce qui favorise un recyclage du code.
// Introduite dans DOM niveau 2 :
interface NodeFilter {
// Les constantes retournées par l'attribut acceptNode
const short FILTER_ACCEPT = 1;
const short FILTER_REJECT = 2;
const short FILTER_SKIP = 3;
// Les constantes de l'attribut whatToShow
const unsigned long SHOW_ALL = 0xFFFFFFFF;
const unsigned long SHOW_ELEMENT = 0x00000001;
const unsigned long SHOW_ATTRIBUTE = 0x00000002;
const unsigned long SHOW_TEXT = 0x00000004;
const unsigned long SHOW_CDATA_SECTION = 0x00000008;
const unsigned long SHOW_ENTITY_REFERENCE = 0x00000010;
const unsigned long SHOW_ENTITY = 0x00000020;
const unsigned long SHOW_PROCESSING_INSTRUCTION = 0x00000040;
const unsigned long SHOW_COMMENT = 0x00000080;
const unsigned long SHOW_DOCUMENT = 0x00000100;
const unsigned long SHOW_DOCUMENT_TYPE = 0x00000200;
const unsigned long SHOW_DOCUMENT_FRAGMENT = 0x00000400;
const unsigned long SHOW_NOTATION = 0x00000800;
short acceptNode(in Node n);
};
La méthode acceptNode() retourne les constantes suivantes :
FILTER_ACCEPTNodeIterator
ou les objets TreeWalker retourneront le nœud.FILTER_REJECTNodeIterator
ou les objets TreeWalker ne retourneront pas le nœud.
En ce qui concerne les objets TreeWalker,
les enfants du nœud seront également rejetés. Les itérateurs NodeIterator
traitent cette valeur comme un synonyme de la valeur FILTER_SKIP.FILTER_SKIPNodeIterator
ou les objets TreeWalker ne retourneront pas le nœud.
Pour les itérateurs NodeIterator et
les objets TreeWalker, les enfants du nœud
seront toutefois pris en compte.Voici les valeurs admissibles du paramètre whatToShow utilisé avec les objets
TreeWalker
et les itérateurs NodeIterator. Ce sont les mêmes que celles du
jeu de types possibles de l'objet Node et leurs valeurs sont dérivées en utilisant une
position de bit correspondant à la valeur de l'attribut nodeType
du type de nœud équivalent. Si on fixe un bit dans whatToShow à false, cela sera considéré
comme une requête pour dépasser ce type de nœud ; le comportement dans ce cas est similaire à celui pour la valeur FILTER_SKIP.
Remarquer que si on devait un jour introduire des types de nœud supérieurs à 32, ils ne seraient alors plus testables
individuellement via whatToShow. Si le besoin se faisait sentir, on pourrait le gérer en séletionnant la
valeur SHOW_ALL et en utilisant un filtre NodeFilter approprié.
SHOW_ALLNode.SHOW_ATTRIBUTEAttr. Cela n'a de sens que pour la création d'un itérateur ou d'un objet TreeWalker
dont la racine est un nœud d'attribut ; auquel cas, le nœud d'attribut apparaîtra en première position dans l'itération
ou la traversée. Puisque les attributs ne sont jamais les enfants d'autres nœuds, ils n'apparaissent pas
au cours de la traversée de l'arbre du document.SHOW_CDATA_SECTIONCDATASection.SHOW_COMMENTComment.SHOW_DOCUMENTDocument.SHOW_DOCUMENT_FRAGMENTDocumentFragment.SHOW_DOCUMENT_TYPEDocumentType.SHOW_ELEMENTElement.SHOW_ENTITYEntity. Cela n'a de sens que pour la création d'un itérateur ou d'un objet TreeWalker
dont la racine est un nœud Entity ; auquel cas, le nœud apparaîtra en première position de la traversée.
Puisque les entités ne font pas partie de l'arbre du document, ils n'apparaissent pas au cours de sa traversée.SHOW_ENTITY_REFERENCEEntityReference.SHOW_NOTATIONNotation. Cela n'a de sens que pour la création d'un itérateur d'un objet TreeWalker
dont la racine est un nœud Notation ; auquel cas, le nœud Notation apparaîtra en première position
de la traversée. Puisque les notations ne font pas partie de l'arbre du document, ils n'apparaissent pas au cours de sa traversée.SHOW_PROCESSING_INSTRUCTIONProcessingInstruction.SHOW_TEXTText.acceptNodeTreeWalker ou
d'un itérateur NodeIterator.
Cette fonction sera appelée par la mise en œuvre TreeWalker
ou NodeIterator ; elle est normalement appelée
directement depuis le code de l'utilisateur. (Quoiqu'on puisse le faire de cette façon, si on souhaite employer le même filtre
pour guider sa propre logique d'application).
n de type Node|
|
Une constante pour déterminer si le nœud est accepté, rejeté ou dépassé, comme défini précédemment. |
Les objets TreeWalker servent à naviguer dans l'arbre, ou un sous-arbre, d'un document en utilisant la
vue du document définie par leurs indicateurs whatToShow et, le cas échéant, leurs filtres.
Une fonction effectuant une navigation avec un objet TreeWalker reconnaîtra automatiquement n'importe quelle vue
définie par un objet TreeWalker.
L'omission de nœuds dans la vue logique d'un sous-arbre peut se traduire par une structure qui diffère
significativement du même sous-arbre dans le document complet non filtré. Les nœuds
frères dans la vue de l'objet TreeWalker peuvent être
les enfants de nœuds distincts très éloignés dans la vue originale. Par exemple, prenons
un filtre NodeFilter qui dépassent tous les nœuds sauf les
nœuds Text et le nœud racine d'un document. Dans la vue logique finale, tous les nœuds Text seront
frères et apparaîtront comme enfants du nœud racine,
quelle que soit leur profondeur d'imbrication dans la structure du document original.
// Introduite dans DOM niveau 2 :
interface TreeWalker {
readonly attribute Node root;
readonly attribute unsigned long whatToShow;
readonly attribute NodeFilter filter;
readonly attribute boolean expandEntityReferences;
attribute Node currentNode;
// soulève une exception DOMException à l'initialisation
Node parentNode();
Node firstChild();
Node lastChild();
Node previousSibling();
Node nextSibling();
Node previousNode();
Node nextNode();
};
currentNode de type NodeTreeWalker est actuellement positionné.TreeWalker n'accepte plus
le nœud courant. On peut fixer explicitement le nœud courant currentNode sur tout autre nœud,
qu'il soit ou non dans le sous-arbre défini par le nœud racine, ou qu'il soit accepté ou non par le filtre et
les indicateurs whatToShow. Les traversées ultérieures adviennent relativement au nœud courant currentNode,
même s'il ne fait pas partie de la vue courante, en appliquant les filtres dans la direction demandée ; si aucune traversée n'est possible, l'attribut
le nœud courant currentNode ne change pas.|
|
NOT_SUPPORTED_ERR : Soulevée si on essaye de donner à l'attribut |
expandEntityReferences de type boolean, en lecture seuleTreeWalker. Pour une valeur false, ces
whatToShow et, le cas échéant, le filtre.whatToShow le pour cacher et fixer la valeur de l'attribut expandEntityReferences
à true à la création de l'objet TreeWalker. Pour produire une vue du document avec des
nœuds d'appels d'entité sans résolution des entités, utiliser les indicateurs whatToShow pour exposer le
nœude d'appel d'entité et fixer la valeur de expandEntityReferences à false.filter de type NodeFilter, en lecture seuleroot de type Node, en lecture seuleTreeWalker, comme défini à sa création.whatToShow de type unsigned long, en lecture seuleTreeWalker.
Le jeu de constantes disponibles est défini dans l'interface NodeFilter.
Les nœuds non acceptés par l'attribut whatToShow seront dépassés toutefois leurs enfants seront pris en compte.
Remarquer que ce dépassement prime, le cas échéant, sur le filtre.firstChildTreeWalker au premier enfant
visible du nœud courant et retourne le nouveau nœud. Si le nœud courant n'a pas d'enfant visible, retourne la
valeur null et conserve le nœud courant.
|
|
Le nouveau nœud ou la valeur |
lastChildTreeWalker au dernier enfant visible
du nœud courant et retourne le nouveau œud. Si le nœud courant n'a pas d'enfant visible, retourne la
valeur null et conserve le nœud courant.
|
|
Le nouveau nœud ou la valeur |
nextNodeTreeWalker au nœud visible suivant dans l'ordre du document relativement
au nœud courant et retourne le nouveau nœud. Si aucun nœud ne suit le nœud courant, ou
si la recherche de la méthode nextNode essaye d'aller en amont du nœud racine de l'objet TreeWalker,
retourne la valeur null et conserve le nœud courant.
|
|
Le nouveau nœud ou la valeur |
nextSiblingTreeWalker au frère suivant
du nœud courant et retourne le nouveau nœud. Si aucun frère visible
ne suit le nœud courant, retourne la valeur null et conserve le nœud courant.
|
|
Le nouveau nœud ou la valeur |
parentNodeTreeWalker au nœud ancêtre visible
le plus proche du nœud courant et retourne cet ancêtre. Si la recherche de la méthode parentNode
essaye d'aller en amont du nœud racine de l'objet TreeWalker ou si elle échoue à trouver un
nœud ancêtre visible, cette méthode retient la position courante
et retourne la valeur null.
|
|
Le nouveau nœud parent ou la valeur |
previousNodeTreeWalker au nœud visible précédent dans l'ordre du document
relativement au nœud courant et retourne le nouveau nœud. Si aucun nœud ne précède le nœud courant,
ou si la recherche de previousNode essaye d'aller en amont du nœud racine de l'objet TreeWalker,
retourne la valeur null et conserve le nœud courant.
|
|
Le nouveau nœud ou la valeur |
previousSiblingTreeWalker au frère
précédent du nœud courant et retourne le nouveau nœud. Si aucun frère visible
ne précède le nœud courant, retourne la valeur null et conserve le nœud courant.
|
|
Le nouveau nœud ou la valeur |
L'interface DocumentTraversal contient des méthodes permettant de créer des itérateurs et des objets TreeWalker
pour la traversée d'un nœud et ses enfants dans l'ordre du document (c'est-à-dire, une traversée préordonnée en profondeur d'abord,
qui équivaut à l'ordre d'apparition des balises ouvrantes dans la représentation textuelle du document).
Dans les mises en œuvre DOM reconnaissant la fonctionnalité "Traversal",
l'interface DocumentTraversal sera mise en œuvre par les mêmes objets que ceux pour l'interface Document.
// Introduite dans DOM niveau 2 :
interface DocumentTraversal {
NodeIterator createNodeIterator(in Node root,
in unsigned long whatToShow,
in NodeFilter filter,
in boolean entityReferenceExpansion)
raises(DOMException);
TreeWalker createTreeWalker(in Node root,
in unsigned long whatToShow,
in NodeFilter filter,
in boolean entityReferenceExpansion)
raises(DOMException);
};
createNodeIteratorNodeIterator
en amont du sous-arbre qui prend racine au nœud indiqué.
root de type NodewhatToShow et, le cas échéant, le filtre ne sont pas pris en compte au paramétrage de cette position.
La racine ne doit pas avoir la valeur null.whatToShow de type unsigned longNodeFilter pour le jeu des valeurs possibles
SHOW_xxx.OR.filter de type NodeFilterNodeFilter à utiliser avec cet
objet TreeWalkernull pour ne pas indiquer de filtre.
entityReferenceExpansion de type boolean|
L'itérateur |
|
|
NOT_SUPPORTED_ERR : Soulevée si l'attribut |
createTreeWalkerTreeWalker
en amont du sous-arbre qui prend racine au nœud indiqué.
root de type Noderacine pour l'objet TreeWalker.
Les indicateurs whatToShow et le filtre NodeFilter
ne sont pas pris en compte au paramétrage de cette valeur ; tout type de nœud sera accepté comme racine.
L'attribut currentNode de l'objet TreeWalker
indique ce nœud, qu'il soit visible ou non. La racine a une fonction de point d'arrêt pour les méthodes de traversée
remontant dans la structure du document, telles que les méthodes parentNode et nextNode.
La valeur de l'attribut root ne doit pas être null.whatToShow de type unsigned longTreeWalker. Cf. la description du filtre NodeFilter
pour le jeu des valeurs possibles SHOW_xxx.OR.filter de type NodeFilterNodeFilter à utiliser avec
cet objet TreeWalker ou la valeur null pour aucun filtre.entityReferenceExpansion de type booleanfalse, le contenu des nœuds EntityReference n'est pas
présenté dans la vue logique.|
L'objet |
|
|
NOT_SUPPORTED_ERR : Soulevée si l'attribut |