Select Page

Drzewo naszego dokumentu

Spróbujmy przespacerować się po drzewie naszego XMLa. Aby lepiej zwizualizować jego strukturę, przygotowałem schemat który pokazuje zależności między poszczególnymi elementami.

XML Tree structure

XML Tree structure with all nested elements and some properties.

Jak widać, nawet przy bardzo prostym dokumencie (nasz przykład z urlOne zawiera tylko jeden akapit z dwoma lokalnymi formatowaniami) drzewo jest dość złożone.
Żeby poruszać się po drzewie XML trzeba pamiętać o tym, że elementy z jakimi będziemy mieli do czynienia są różnych typów. Warto zapoznać się z ich zestawieniem. Dla nas najistotniejsze będą nodeType === 1, który oznacza, że element ma jeszcze jakiegoś potomka i nodeType === 3, który oznacza element tekstowy. To rozróżnienie jest dla nas ważne ponieważ jeśli trafimy na element typu 1 to musimy pobrać jego potomków i będziemy tak postępować aż trafimy na element typu 3 i pobierzemy jego zawartość tekstową.

W drogę

Od ostatniego wpisu kod rozbiłem na funkcje, które powinny trochę go uporządkować. Na pewno nadal będę go korygował, prawdobodobnie rozbiję go też na moduły, ale jeszcze nie wiem ile ich będzie. Nasz spacer po DOM będzie dość krótki.

// Read content from node given
function traverseDOM( tag ) {
	const tagContent = children( document.getElementsByTagName( tag )[ 0 ]);
	logger( tagContent );
	logger( tagContent.map( element => readFromFirst( children( element ))));
}
// Read nodes starting from highest child
function readFromFirst( node ) {
	return node.reduce(( prev, current ) => {
		if ( current.nodeType === 1 ) {
			return ( prev + readFromFirst( children( current )));
		} else if ( current.nodeType === 3 ) {
			logger( current.nodeValue );
			return ( prev + `<${current.parentElement.attributes[ 0 ].nodeValue}>` + current.nodeValue );
		}
	}, '' );
}

Jak widać doszły dwie funkcje. Pierwsza służy do pobrania elementu drzewa DOM, od którego chcemy zacząć. My wywołamy ją traverseDOM( 'office:text' ); co oznacza, że startujemy od elementu <office:text>. Ten fragment został przedstawiony na ilustracji wyżej. Funkcja children( element ), która się pojawia ma za zadanie zamienić dzieci elementu (nodeList) na tablicę, po której będziemy mogli się bez problemu poruszać.
Uwaga Łatwo zapomnieć, że element od którego startujemy jest pobrany przez document.getElementsByTagName. Wynikiem tej operacji jest tablica jednoelementowa, do którem musimy się dostać przez jej indeks [ 0 ]. Ta drobna nieuwaga kosztowała mnie sporo czasu i nerwów.

Funkcja druga readFromFirst( node ) jest wywoływana rekurencyjnie. Sprawdzamy jakiego typu jest nasz element. Jeśli 3 to onacza, że ma jakieś dzieci, które pobieramy i sprawdzamy. Jeśli znajdziemy element typu 3 (czyli tekst), zwracamy jego wartość. Ponieważ jeden akapit może mieć wiele różnych stylów (znakowych), konieczne jest sklejenie wszystkich fragmentów w jedną całość (tak aby powstał kompletny akapit). W tym celu użyjemy funkcji Array.reduce().
Warto zwrócić uwagę na linię 15. return ( prev +<${current.parentElement.attributes[ 0 ].nodeValue}>+ current.nodeValue );. Jeśli trafimy na element tekstowy (który nie ma już potomków), chcemy pobrać informację o stylu, jaki został dla niego użyty. W tym celu sięgamy do atrybutów jego rodzica, gdyż tam zapisana jest nazwa stylu. Dobrze widać to na ilustracji, gdzie wszystkie elementy typu 3 mają nodeName: "#text", ale ich rodzice mają kolejno text:style-name="P1" (akapit), text:style-name="T2"(pierwszy <text:span>), text:style-name="T1" (drugi <text:span>).

Wynikiem funkcji traverseDOM dla naszego prostego przykładu będzie tablica
["", "<P1>To <T2>jest<P1> test <T1>projektu<P1>."].
Indeks 0 to text:sequence-decls, które nas nie interesują i powinny zostać pominięte w kolejnych krokach. Kolejna pozycja to nasz tekst z onaczonymi stylami. Daleko mu do doskonałości, ale pokazuje że wykonaliśmy krok we właściwą stronę.

Co dalej?

Wygląda na to, że nadchodzi czas na gruntowne przemyślenie kolejnych posunięć. Dopóki operujemy jedynie na prostym tekście (akapity z formatowaniem), styl dla akapitu może pojawiać się tylko w miejscu jego rozpoczęcia (ponieważ jeden akapit może mieć zdefiniowany tylko jeden styl akapitowy). Style znakowe powinny wskazywać początek i koniec konkretnego stylu znakowego. Czyli nasz przykład mógłby zostać zapisany następująco:
"<styl-akapitu>To <styl-znakowy-1>jest</styl-znakowy-1> test <styl-znakowy-2>projektu</styl-znakowy-2>.".