Czasami zachodzi potrzeba sięgnięcia do źródła danych jakim są strony internetowe. Niestety często zdarza się tak, że kod HTML nie jest dobrze sformatowany i nie da się go przejrzeć za pomocą narzędzi do XML.
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
public class Moja {
public static void main(String[] args) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("index.htm"); // błąd podczas konwersji dokumentu na XML
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Wykonanie tego kodu może spowodować pojawienie się w zależności od zawartości dokumentu index.htm różnych błędów np.:
- Server returned HTTP response code: 503
- The entity „nbsp” was referenced, but not declared.
Powodami najczęściej są brakujące domknięcia tagów, czy niezrozumiałe dla parsera XML znaki twardych spacji „ ”. Można taki kod poprawić ręcznie, można napisać własne narzędzie do normalizacji dokumentów HTML, ale na pewno szybciej i prościej będzie skorzystać z gotowego rozwiązania.
W tym artykule postaram się przybliżyć bibliotekę HTMLCleaner która udostępniana jest na licencji BSD, a więc każdy może ją bez przeszkód używać, modyfikować i rozpowszechniać.
Instalacja
Biblioteka jest dostępna do pobrania pod adresem http://htmlcleaner.sourceforge.net/download.php. Po pobraniu rozpakowujemy archiwum i dodajemy plik htmlcleaner.jar do naszego projektu.
Aby dodać do projektu bibliotekę HTMLCleaner w środowisku Eclipse Galileo należy:
- Kliknąć prawym klawiszem myszy na projekcie i wybrać Build Path -> Configure Build Path…
- W nowym oknie przechodzimy do zakładki Libraries i klikamy na przycisk Add External JARs…
- Wybieramy plik htmlcleaner.jar i zatwierdzamy zmiany klikając na przycisk OK.
Podstawy
import java.io.File;
import java.io.IOException;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
public class Moja {
public static void main(String[] args) {
HtmlCleaner cleaner = new HtmlCleaner();
try {
TagNode rodzic = cleaner.clean(new File("index.htm"));
TagNode[] dzieci = rodzic.getAllElements(false); // true/false - wywołanie rekurencyjne/bez rekurencji
for (TagNode dz : dzieci) {
System.out.println(dz);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Linia 9:
Wykorzystanie obiektu stworzonego w linii 7 do wyczyszczenia pliku index.html (plik nie jest modyfikowany, jego zawartość przechowywana jest w pamięci). Czyszczenie kodu to nic innego jak poprawienie istniejącej struktury tak aby spełniała standardy XML (w przypadku braku tagu zamykającego, jest on dopisywany, itp.). Metoda clear() ma wiele wersji przystosowanych między innymi do pozyskiwania kody HTML z plików (File), serwerów (URL) czy w postaci łańcuchów tekstowych (String), my chcemy wczytać dane z pliku zapisanego w katalogu z którego uruchamiana jest aplikacja więc wykorzystaliśmy wersję pobierającą w parametrze obiekt typu File. Klasa TagNode wykorzystywana jest do przechowywania informacji o pojedynczym węźe, umożliwia dostęp do atrybutów węzła oraz jego dzieci za pośrednictwem między innymi takich metod:
- TagNode[] getAllElements(boolean arg), List getAllElementsList(boolean arg)
- String getAttributeByName(String name)
Zwraca wartość atrybutu danego węzła.
- Map getAttributes()
Zwraca mapę atrybutów gdzie kluczem jest nazwa atrybutu.
- TagNode getParent()
Zwraca węzeł który jest rodzicem danego węzła.
- String getText()
Zwraca tekst jaki zawiera dany element, trzeba jednak zaznaczyć że funkcja ta działa rekurencyjnie i zwraca również tekst który umieszczony został w wewnętrznych węzłach (dzieciach).
Linia 10:
Pobranie wszystkich elementów z głównego węzła (html). Ustawienie parametru na false zwróci dwa węzły: head i body (bo są dziećmi węzła głównego: html). Ustawienie parametru na true rekurencyjnie zwróci węzły z całego dokumentu HTML.
Linia 12:
Wypisanie nazw wszystkich elementów pozyskanych w linii 10. Klasa TagNode ma przeciążoną metodą toString() w taki sposób aby zwracała nazwę węzła.