11. prosince 2012

DOM, Java a odstranění child nodes

Dneska to bude jen takový krátký. Řešil jsem nějaký problém, v rámci kterého se pracovalo s DOMem, do kterého jsem potřeboval přidat nějaké nody. Jak jsem průběžně zjistil, někdy mi do metody přicházel dokument, který už ty nody někdy obsahoval (a někdy ne).

Jako čistý řešení mi přišlo dané nody (včechno to byly child elementy jednoho nodu), pokud tam jsou, odstranit a pak je přidat s potřebnými hodnotami. K tomu jsem se rozhodl vzhledem k API, jež DOM v Javě nabízí - které mi tedy moc použitelné nepřijde.

Dokument, o kterém mluvím má (zjednodušeně) takovouhle strukturu:
<root>
    <child1/>
    <child2/>
    <child3/>
</root>
Obecně jsem čekal, že <root> element bude mít nějakou metodu, která mi vrátí kolekci child nodů. Taková metoda sice existuje - getChildNodes() - ale bohužel vrací interface NodeList:
package org.w3c.dom;

public interface NodeList {
    public Node item(int index);

    public int getLength();
}
Nevím jak vám, ale mě kolekce s takovým rozhraním přijde dost hrozná.

A teď, proč o tom vlastně píšu. Řekl jsem si, OK, získám NodeList, projdu ho v cyklu a jednotlivé nody odstraním. Takže jsem napsal něco takového:
if (root.hasChildNodes()) {
        NodeList children = root.getChildNodes();
        int count = children.getLength();

    for (int i = 0; i < count; i++) {
        Node child = children.item(i);
        root.removeChild(child);
    }
}
Načež mě překvapilo, že mi to vyhazuje NullPointerException. Ono totiž, když se odstraní element z DOMu, tak se odstraní i z daného NodeListu. Divný, ale budiž. Řešením by tedy bylo
Node child = children.item(0);
ale to se mi designově nelíbilo, takže jsem skončil s tímhle kódem:
if (root.hasChildNodes()) {
    int count = root.getChildNodes().getLength();

    for (int i = 0; i < count; i++) {
        Node child = root.getFirstChild();

        root.removeChild(child);
    }
}
A jak byste to řešili vy? Mě by se líbilo nějaké elegantní řešení.

11 komentářů:

  1. Zajimave, co zkusit jednoduchy while

    while(root.hasChildNodes()) {
    root.removeChild(root.getFirstChild());
    }

    popravde, to rozhrani je silene, ale co uz :)

    OdpovědětVymazat
    Odpovědi
    1. S daným API je to asi nejlepší řešení, nechápu co se na tom komu může nelíbit. Maximálně tak se možná může ukázat že by bylo efektivnější míst getFirstChild volat getLastChild, ale to už závisí na konkrétní implementaci.

      Vymazat
  2. Použití live view je podle mne v pořádku, navíc to je uvedeno v dokumentaci. Ale jinak souhlasím, že to API by mělo aspoň implementovat Iterable.
    S tímto API mě napadá navíc maximálně jen procházení pozpátku. Sice taky nic moc, ale aspoň je na tom vidět, že se cyklus týká všech uzlů, což getFirstChild trochu zamlžuje.
    for (int i = count - 1; i >= count; i--) {
    Node child = children.item(i);
    root.removeChild(child);
    }

    OdpovědětVymazat
    Odpovědi
    1. Jasně, v dokumentaci. Ale myslím si, že API by mělo být intuitivní.

      To procházení pozpátku mě taky napadlo. Bylo by to sice cool :-/ ale až by to po mně někdo za rok četl, tak by si asi říkal, co to tam vyvádím.

      Vymazat
  3. To prochazeni pozpatku muze byt teoreticky pomaly, kdyby tam tech uzlu bylo moc (kdovi jestli to neni nejaky LinkedList). Cely je to nejaky divny, live view je OK, ale nevidim duvod proc to neni List. Ja kdysi resil presne totez v Javascriptu a samozrejme jsem narazil uplne stejne.

    OdpovědětVymazat
    Odpovědi
    1. LinkedList tam asi bude - nějak si musej držet pořadí elementů.

      S tou performance by bylo zajímavý to vyzkoušet. Možná jestli najdu trochu volného času.

      Vymazat
    2. To nejak nechapu, pokud vim tak LinkedList je obousmernej spojovej seznam, tzn. ze je uplne jedno odkud ho prochazis.

      Vymazat
    3. Ale jenom za předpokladu, že použiješ ListIterator a pojedeš od konce pomocí hasPrevious()/previous(). Pokud bys použil obyčejnej for cyklus s get(i), tak ta složitost bude dost nehezká.

      Každopádně si vůbec netroufám hádat, jak to je vlastně ve skutečnosti implementované, protože při stisknutí Ctrl+T na org.w3c.dom.Node na mě vyskočí seznam asi 150 tříd. :D

      Vymazat
    4. Tak. Ten "LinkedList" je trochu zavádějící. Předpokládám, že to bylo myšleno spíš abstraktně (jako že je to slinkované), než že to je konkrétní implementace. U NodeListu to totiž být nemusí být obousměrný seznam.

      Vymazat