Marzec
07
2009

Zapis do pliku XML – część III

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

Tym razem mam zamiar utworzyć elementy wykorzystywane przez klasę XHTML, opisaną w części II. Do dzieła!

Klasa HTML

Plik XHTML zawiera element html, reprezentowany przez klasę:

package xml.xhtml.elements;

import xml.annotation.Attribute;
import xml.annotation.Element;

@Element
public class HTML {
}

Klasa ta zawiera kilka pól reprezentujących atrybuty oraz elementy znacznika html, a także metody służące do ich ustawiania i pobierania.

Znacznik html posiada atrybut xmlns:

private String xmlns;

public void setXMLNS(String xmlns) {
    this.xmlns = xmlns;
}

@Attribute("xmlns")
public String getXMLNS() {
    return this.xmlns;
}

oraz elementy head:

private Head head;

public void setHead(Head head) {
    this.head = head;
}

@Element
public Head getHead() {
    return this.head;
}

body:

private Body body;

public void setBody(Body body) {
    this.body = body;
}

@Element
public Body getBody() {
    return this.body;
}

Wewnątrz klasy HTML znajduje się też domyślny konstruktor, który tworzy elementy head oraz body:

public HTML() {
    this.setXMLNS(null);
    this.setHead(new Head());
    this.setBody(new Body());
}

Użyte w kodzie adnotacje to @Element oraz @Attribute. Podczas gdy nazwa atrybutu została zdefiniowana w adnotacji do metody, nazwy elementów ustalane są na poziomie adnotacji do klasy.

Po lekturze poprzednich części wszystko powinno być już oczywiste. Teraz już przejdę do definicji kolejnych elementów.

Klasa Head

Najpierw element head:

package xml.xhtml.elements;

import java.util.ArrayList;
import java.util.List;

import xml.annotation.Contents;
import xml.annotation.Element;

@Element
public class Head {
}

Wewnątrz musi znajdować się element title, który zawiera tytuł strony:

private Title title;

@Element
public Title getTitle() {
    return this.title;
}

Jak widać, brakuje tu metody ustawiającej tytuł. Nie jest ona potrzebna. Ponieważ element ten jest wymagany w XHTML, będzie tworzony w konstruktorze. Jego ponowne ustawienie nie ma sensu, gdyż zawiera jedynie tekst, który można w każdej chwili zmienić.

Poza jednym elementem title, wewnątrz nagłówka XHTML może istnieć wiele elementów meta:

private List<Meta> meta;

public void addMeta(Meta meta) {
    this.meta.add(meta);
}

@Contents
public List<Meta> getMeta() {
    return this.meta;
}

Użyta tutaj została adnotacja @Contents w odniesieniu do listy obiektów klasy Meta. List jest interfejsem generycznym, więc wymagane jest określenie typu elementów listy.

Na koniec pozostał jeszcze konstruktor. Tutaj tworzony jest element title (zawierający pusty tytuł) oraz pusta lista elementów meta:

public Head() {
    this.title = new Title(null);
    this.meta = new ArrayList<Meta>();
}

Należy zauważyć, że pole meta ma typ List<Meta>, natomiast inicjowane jest obiektem typu ArrayList<Meta>. Dzieje się tak dlatego, że typ List jest interfejsem, więc nie można utworzyć jego instancji. Natomiast można utworzyć instancję klasy ArrayList, implementującej wspomniany interfejs.

Ale dlaczego nie użyć klasy ArrayList wszędzie tam, gdzie występuje interfejs List? Jedna z najważniejszych zasad projektowania aplikacji mówi, że jeżeli można pokryć wymaganą funkcjonalność interfejsem, to należy go użyć zamiast klasy. Takie rozwiązanie zapewnia hermetyzację zmienności implementacji.

Klasa Body

Kolejna klasa definiuje ciało dokumentu XHTML:

package xml.xhtml.elements;

import xml.annotation.Element;

@Element
public class Body {

    public Body() {
    }

}

Jak widać, klasa ta zawiera wyłącznie domyślny konstruktor. Do utworzenia poprawnego pliku XHTML wystarczy pusty element body, więc dodanie zawartości strony pozostawię na inną okazję.

Klasa Title

Wewnątrz elementu head istnieją jeszcze elementy title oraz meta. Najpierw zajmę się wymaganym przez format XHTML tytułem strony:

package xml.xhtml.elements;

import xml.annotation.Element;
import xml.annotation.Text;

@Element
public class Title {
}

Wewnątrz elementu znajduje się jedynie tekst:

private String title;

public void setTitle(String title) {
    this.title = title;
}

@Text
public String getTitle() {
    return this.title;
}

ustawiany także w konstruktorze klasy:

public Title(String title) {
    this.setTitle(title);
}

Klasa Meta

Na koniec zostawiłem element, który nie jest obowiązkowy, ale na pewno przydatny. Metadane dokumentu XHTML pozwalają przechowywać takie informacje jak autor strony, słowa kluczowe czy zestaw użytych znaków. Do rzeczy:

package xml.xhtml.elements;

import xml.annotation.Attribute;
import xml.annotation.Element;

@Element
public class Meta {
}

Na początek zdefiniuję typ wyliczeniowy HTTPEquiv, który będzie zawierał wszystkie wartości atrybutu http-equiv znacznika meta:

public enum HTTPEquiv {
    CONTENT_TYPE ("content-type"),
    CONTENT_STYLE_TYPE ("content-style-type"),
    EXPIRES ("expires"),
    REFRESH ("refresh"),
    SET_COOKIE ("set-cookie");

    private String value;

    private HTTPEquiv(String value) {
        this.value = value;
    }

    private String getValue() {
        return this.value;
    }
}

Typy wyliczeniowe omówiłem szczegółowo w poprzedniej części, więc wszystko powinno być zrozumiałe.

Typ ten wykorzystuję dla zdefiniowania atrybutu http-equiv:

private HTTPEquiv httpEquiv;

@Attribute("http-equiv")
public String getHttpEquiv() {
    if (this.httpEquiv == null) {
        return null;
    }
    return this.httpEquiv.getValue();
}

Inne atrybuty to name:

private String name;

@Attribute("name")
public String getName() {
    return this.name;
}

content:

private String content;

@Attribute("content")
public String getContent() {
    return this.content;
}

oraz scheme:

private String scheme;

@Attribute("scheme")
public String getScheme() {
    return this.scheme;
}

Nie zaimplementowałem funkcji ustawiających te wartości, gdyż będą one inicjowane w konstruktorach:

public Meta(HTTPEquiv httpEquiv, String content) {
    this.httpEquiv = httpEquiv;
    this.name = null;
    this.content = content;
    this.scheme = null;
}

public Meta(String name, String content) {
    this.httpEquiv = null;
    this.name = name;
    this.content = content;
    this.scheme = null;
}

public Meta(String name, String content, String scheme) {
    this.httpEquiv = null;
    this.name = name;
    this.content = content;
    this.scheme = scheme;
}

Utworzyłem typ wyliczeniowy HTTPEquiv, ponieważ – zgodnie ze specyfikacją – odpowiadający mu atrybut ma skończoną listę możliwych wartości. W przypadku atrybutu name istnieją pewne standardowe wartości, lecz twórca strony może utworzyć też własne. Dlatego zamiast typu wyliczeniowego, należy raczej zdefiniować kilka stałych:

public static final String AUTHOR = "author";
public static final String DESCRIPTION = "description";
public static final String KEYWORDS = "keywords";
public static final String GENERATOR = "generator";
public static final String REVISED = "revised";

Jest jeszcze jeden powód utworzenia typu wyliczeniowego. Gdyby stworzyć konstruktory:

  • Meta(String httpEquiv, String content) oraz
  • Meta(String name, String content)

powstałaby dwuznaczność. Dodatkowy typ pozwala jej uniknąć.

Podsumowanie

W powyższym tekście nie pojawiło się nic nowego. Utworzyłem jedynie klasy niezbędne do wygenerowania poprawnego pliku XHTML. Następnym razem przejdę do sedna sprawy, czyli do tworzenia zawartości XML.

Napisz Komentarz

*