Marzec
06
2009

Zapis do pliku XML – część II

Słowa kluczowe: , | Kategorie: Java
No Gravatar

poprzedniej części zajmowałem się tworzeniem adnotacji, które mają posłużyć do zapisu obiektów w formacie XML.

Zanim przejdę do tworzenia samego rdzenia, czyli obiektu serializującego, utworzę jakieś przykładowe klasy, aby na bieżąco sprawdzać poprawność zawartości pliku XML. Aby mieć absolutną pewność, że nie popełniam błędów, wykorzystam stronę W3C Markup Validation Service.

Spróbuję utworzyć całkowicie poprawny plik XHTML. Jeżeli pomyślnie przejdzie sprawdzenie przez wspomnianą stronę, to znaczy, że wszystko działa prawidłowo.

Klasa XHTML

Na początek omówię klasę, która będzie pełnić dwie podstawowe funkcje:

  • klasy bazowej dla dokumentów zapisywanych do pliku XHTML,
  • fabryki dokumentów XHTML.
package xml.xhtml;

import xml.annotation.Element;
import xml.annotation.XML;
import xml.xhtml.elements.HTML;

public abstract class XHTML {

}

Wewnątrz klasy zdefiniowałem stałą, która posłuży do określenia przestrzeni nazw znacznika html wszystkich dokumentów XHTML:

public static final String XMLNS =
    "http://www.w3.org/1999/xhtml";

Dokument XHTML posiada jeden główny element – html. Definicja klasy tego elementu zostanie omówiona innym razem. Na razie dodaję pole oraz metodę pobierającą element z dokumentu:

private HTML html;

@Element
public HTML getHTML() {
    return this.html;
}

Jeszcze pozostaje dodanie domyślnego konstruktora:

protected XHTML() {
    this.html = new HTML();
    this.html.setXMLNS(XMLNS);
}

Wraz z dokumentem XHTML tworzony jest jego główny element (html), któremu od razu zostaje przypisana wartość atrybutu xmlns.

Jednak utworzenie instancji klasy XHTML nie jest w tej chwili możliwe, i to przynajmniej z dwóch powodów:

  1. Klasa xml.xhtml.XHTML jest abstrakcyjna.
  2. Konstruktor xml.xhtml.XHTML.XHTML() jest chroniony (protected).

Najpierw rozwiążę pierwszy problem. Utworzę (w tym samym pliku) klasy potomne.

Klasy potomne

Poniżej znajdują się definicje klas reprezentujących różne rodzaje dokumentów XHTML:

@XML(doctype = "<!DOCTYPE html PUBLIC " +
        "\"-//W3C//DTD XHTML 1.0 Strict//EN\" " +
        "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")
class StrictXHTML extends XHTML {
    protected StrictXHTML() {
        super();
    }
}

@XML(doctype = "<!DOCTYPE html PUBLIC " +
        "\"-//W3C//DTD XHTML 1.0 Transitional//EN\" " +
        "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">")
class TransitionalXHTML extends XHTML {
    protected TransitionalXHTML() {
        super();
    }
}

@XML(doctype = "<!DOCTYPE html PUBLIC " +
        "\"-//W3C//DTD XHTML 1.0 Frameset//EN\" " +
        "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">")
class FramesetXHTML extends XHTML {
    protected FramesetXHTML() {
        super();
    }
}

Powyższe klasy odzwierciedlają dokumenty XHTML utworzone w oparciu o DTD xhtml1-strict.dtd, xhtml1-transitional.dtd oraz xhtml1-frameset.dtd. Wszystkie też posiadają konstruktor domyślny, którego ciało ogranicza się do wywołania konstruktora domyślnego klasy XHTML (może w ostatnim przypadku należałoby jeszcze dodać jakieś ramki, ale to już szczegół).

Klasy potomne nie są już abstrakcyjne, ale ich konstruktory wciąż są chronione. Co więcej domyślna widoczność klasy ogranicza ich użycie wyłącznie do pakietu xml.xhtml.

Typ wyliczeniowy DTD

Przed napisaniem metody tworzącej nowy dokument, dodam wewnątrz klasy XHTML pomocniczy typ wyliczeniowy (enum), określający wykorzystany plik DTD:

public enum DTD {
    STRICT (StrictXHTML.class),
    TRANSITIONAL (TransitionalXHTML.class),
    FRAMESET (FramesetXHTML.class);

    private Class<? extends XHTML> xhtmlClass;

    private DTD(Class<? extends XHTML> xhtmlClass) {
        this.xhtmlClass = xhtmlClass;
    }

    private XHTML getInstance()
    throws InstantiationException, IllegalAccessException {
        return this.xhtmlClass.newInstance();
    }
}

Jak widać, typ wyliczeniowy języka Java, to coś zupełnie innego niż w C++. Jest tutaj słowo kluczowe enum, identyfikator typu, lista wartości (STRICT, TRANSITIONAL, FRAMESET) i to chyba wszystkie podobieństwa.

Typ wyliczeniowy jest w zasadzie klasą, której jedyne instancje zdefiniowane są jako stałe. Wewnątrz DTD znajduje się pole xhtmlClass, konstruktor DTD() oraz metoda getInstance(). Więcej informacji na temat typów wyliczeniowych znajduje się w dokumentacji.

Klasa Class – począwszy od Java 1.5 – jest generyczna. Oznacz to, że trzeba określić typ, jakiego dotyczy. Zazwyczaj wybór jest dość oczywisty:

  • StrictXHTML.class jest typu Class<StrictXHTML>,
  • TransitionalXHTML.class jest typu Class<TransitionalXHTML>,
  • FramesetXHTML.class jest typu Class<FramesetXHTML>,
  • Object.class jest typu… Class<Object>.

Jednak można się natknąć także na typy:

  • Class<?> – dowolna klasa,
  • Class<? extends XHTML> – dowolna klasa dziedzicząca z XHTML,
  • Class<? implements ActionListener> – dowolna klasa implementująca interfejs ActionListener.

Zatem składowe typu DTD pełnią następujące funkcje:

private Class<? extends XHTML> xhtmlClass
klasa dokumentu XHTML,
private DTD(Class<? extends XHTML> xhtmlClass)
konstruktor ustawiający klasę dokumentu, wywoływany podczas definiowania wartości typu wyliczeniowego,
private XHTML getInstance()
metoda służąca do pobrania instancji klasy powiązanej z wartością typu wyliczeniowego.

Tworzenie instancji

Gdyby metoda DTD.getInstance() była publiczna, to tworzenie nowej instancji mogłoby się ograniczyć do jej wywołania:

import xml.xhtml.XHTML;
import static xml.xhtml.XHTML.DTD;

// ...

XHTML strict = DTD.STRICT.getInstance();
XHTML transitional = DTD.TRANSITIONAL.getInstance();
XHTML frameset = DTD.FRAMESET.getInstance();

Jednak w takiej sytuacji trzeba by obsłużyć jeszcze dwa wyjątki, które nie powinny się wcale pojawić. Poza tym zazwyczaj pisze się statyczną metodę create() w klasie, której instancja ma zostać utworzona, albo w specjalnej klasie fabryki. Dlatego też postanowiłem dodać taką metodę do klasy XHTML:

public static XHTML create(DTD dtd) {
    try {
        return dtd.getInstance();
    } catch (InstantiationException e) {
        return null;
    } catch (IllegalAccessException e) {
        return null;
    }
}

Teraz utworzenie nowego dokumentu XHTML wygląda w następujący sposób:

import xml.xhtml.XHTML;
import static xml.xhtml.XHTML.DTD;

// ...

XHTML strict = XHTML.create(DTD.STRICT);
XHTML transitional = XHTML.create(DTD.TRANSITIONAL);
XHTML frameset = XHTML.create(DTD.FRAMESET);

Podsumowanie

Przy okazji omówienia klasy XHTML, napisałem dzisiaj trochę informacji na temat typów generycznych i wyliczeniowych.

Następnym razem zajmę się tworzeniem klas podstawowych elementów (znaczników) XHTML, takich jak: html, head, body, meta czy title.

Napisz Komentarz

*