środa, 16 marca 2011

Parsowanie plików Excela

Często zachodzi w naszym świecie potrzeba parsowania plików Excela. Jak to powiedział Sławek Sobótka,  podczas prezentacji pt. „Domain Driven Design” – Wszystko ma swoje miejsce i wszystko jest na miejscu [video 1] [video 2] w ramach WrocławJUG: „[...] ludzie biznesu traktują Excela jako notatnik”. I rzeczywiście. Dla ludzi, którzy mają styczność na codzień w pracy z tabelkami, Excel jest wygodnym i niezastąpionym narzędziem. Warto więc zaznajomić się ze sposobem odczytu danych z arkuszów kalkulacyjnych. Nie będę opisywał jak odczytywać diagramy UML’owe zapisane w Excelu (jak to przedstawił Sławek), ale pokażę poniżej proste parsowanie arkuszy kalkulacyjnych.

Szukając biblioteki javovej obsługującej wspomniane akrkusze natrafiłem najpierw na Java Excel API. Gdzieś przeczytałem, że jest ona łatwiejsza w użyciu niż inne rozwiązania i zacząłem z niej korzystać. Niestety szybko się okazało, że biblioteka słabo radzi sobie z krzaczkami (obcojęzycznymi znakami) i kontynuowałem moje poszukiwania. Natrafiłem wówczas na Apache POI.

Apache POI jest biblioteką nie tylko potrafiącą współpracować z plikami Excela, ale również innymi dokumentami Microsoft Office’a. Chcąc odczytać coś z arkusza kalkulacyjnego, musimy najpierw utworzyć obiekt typu Workbook.

Workbook wb = new HSSFWorkbook(
    new POIFSFileSystem(new FileInputStream(filename)));

Mając już taki obiekt, możemy sprawdzić z ilu zakładek składa się nasz dokument (wb.getNumberOfSheets()), pobrać wszystkie obrazki dokumentu (getAllPictures()), czcionkę z danej komórki (getFontAt(...)) i sporo innych. Aby odczytać dane z konkretnej zakładki, należy ją najpierw pobrać:

Sheet sheet = wb.getSheetAt(...);

Teraz jak już mamy obiekt typu Sheet możemy iterować po danej zakładce w następujący sposób:

for (Row row : sheet) {
    for (Cell cell : row) {
        // …
    }
}

Teraz zostaje już tylko odczytać zawartość danej komórki:

String str = cell.getStringCellValue();

Poniżej prezentuję prosty kod metody wyświetlającą zawartość całego pliku:

public static void showAll(String filename) throws IOException {
    Workbook wb = new HSSFWorkbook(
        new POIFSFileSystem(new FileInputStream(filename)));
    for(int i=0; i < wb.getNumberOfSheets(); i++) {
        Sheet sheet = wb.getSheetAt(i);
        for (Row row : sheet) {
            for (Cell cell : row) {
                String str = cell.getStringCellValue();
                System.out.println(str);
            }
        }
    }
}

Do czego używam tej biblioteki? Na przykład do internacjonalizacji aplikacji. W pliku Excela przygotowuję sobie tłumaczenia używane w tworzonej aktualnie aplikacji. Następnie puszczam taki plik przez mój mały programik, która czyta odpowiednie kolumny i generuje pliki properties. Zgodnie z tym co kiedyś mówił Marek Berkan podczas prezentacji "Automatyczne generowanie kodu" na 4Developers 2010 warto jest jak najwięcej kodu generować automatycznie. Dlatego, po za samym plikiem properties, generuję sobie również plik javowy, zawierający zdefiniowane stałe, których mogę używać w kodzie, chcąc się dostać do danego tekstu. Dzięki temu jak nazwa jakiejś właściwości się zmieni, wygeneruje się inny kod i kompilator będzie krzyczał, gdy gdzieś jeszcze używam nieaktualnej stałej. Chroni mnie to przed błędami w aplikacjach związanymi z tłumaczeniami.

Oczywiście sposobów na wykorzystanie owej biblioeki jest multum, więc zachęcam do testowania.