Export dat do XLS a CzechIdM

Pro našeho klienta jsme implementovali export vybraných atributů identit do excelovského formátu XLS, jelikož otevírání CSV v tabulkovém editoru může být komplikované především z důvodu nastavení oddělovacího znaku jednotlivých sloupců. Dalším omezením je editace vzhledu výsledného exportu, s čímž si tabulkový editor pro CSV neporadí. V tomto článku si tedy ukážeme základní kroky nutné pro práci se soubory XLS pomocí frameworku Apache POI.

Apache POI

POI je javovský framework vydávaný Apache Software Foundation, takže stejně jako u ostatních produktů Apache má otevřený zdrojový kód a je vydáván pod svobodnou licencí. Současnou stabilní verzí je 3.11. Obecně se tento framework snaží poskytovat API pro standardní sadu dokumentů společnosti Microsoft, nejde tedy jen o práci se soubory typu XLS nebo XLSX, na které se v článku zaměřujeme, ale i o wordovské dokumenty nebo prezentace.

Závislosti a příprava Eclipse

Všechny třídy, které budeme pro práci s frameworkem potřebovat, jsou obsaženy v jediném JARu, nazvaném poi.jar. Tento archiv potřebuje další knihovny, ale my bude pro využití v CzechIdM potřebovat přidat pouze jedinou, a to commons-codec.jar z adresáře /lib. Ostatní, například log4j, už IdM obsahuje.

Pokud používáme k buildu projektu Ant, musíme knihovny ručně přidat do classpath. CzechIdM je distribuováno jako enterprise archiv, a jednotlivé moduly – tedy WAR a EJB – na tomto archivu závisejí. Proto pro přídání nových knihoven do classpath stačí nakopírovat knihovny do adresáře /lib projektu CzechIdM-ear. Jak WAR tak i EJB mají tento EAR v build path a můžou využívat jeho knihovny.

Pro build mavenem musíme přidat závislost do souboru pom.xml projektu CzechIdM-parent. V něm uvedeme kompletní element i s verzí, v EJB se na něj pouze odkazuje. Pro CzechIdM-parent/pom.xml:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.10-FINAL</version>
</dependency>

Pro CzechIdM-ejb/pom.xml:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
</dependency>

Tím máme nastaveny projekty a můžeme se přesunout na samotné generování XLS.

Tvorba XLS

Samotné vytváření souboru je vlastně kopie jeho struktury, kterou ale musí programátor napsat ručně. Nejprve vytvoříme workbook a sešit, do kterých chceme zapisovat. Nastavíme font a vytvoříme styl buněk tabulky. Jednotlivé řádky sešitu vytvoříme zavoláním metody createRow(int) na aktuálním sešitu. Právě parametr metody zde vytváří poněkud nepříjemnou vlastnost, a to že pokud si nedáme pozor na číslování, můžeme si přepisovat data. Stejná vlastnost bohužel platí i pro sloupce řádků. Následující ukázka kódu zachycuje vytvoření jednoduchého souboru XLS:

public static void main(String[] args) throws Exception {
    FileOutputStream out = new FileOutputStream("workbook.xls");
    // create a new workbook
    Workbook wb = new HSSFWorkbook();
    // create a new sheet
    Sheet s = wb.createSheet();
    // set the sheet name in Unicode
    wb.setSheetName(0, "export");

    // create workbook font
    // set font 1 to 12 point type
    // make it bold
    // arial is the default font
    Font f = wb.createFont();
    f.setFontHeightInPoints((short) 12);

    // create cell style
    CellStyle cs = wb.createCellStyle();
    cs.setFont(f);
    cs.setWrapText(true);
    cs.setDataFormat(wb.createDataFormat().getFormat("#,##0.0"));

    // create row and cells
    Row r = s.createRow(0);
    for (int i = 0; i < 5; i++) {
        Cell c = r.createCell(i);
        // set cell style and its value
        c.setCellStyle(cs);
        c.setCellValue("Column no. : " + i);
    }

    // write workbook to output stream
    wb.write(out);
    out.close();
}

Nepříjemnosti s typováním

Jelikož potřebujeme generovat soubory dynamicky a formát výsledného XLS není natvrdo zadrátovaný v kódu, je potřeba zpracovávat všechny vstupní hodnoty jako Object. POI ale přijímá jako vstupní parametry pouze řetězce, datumy a čísla. Samozřejmě se opět můžeme ke všem vstupům chovat stejně a volat Object.toString(), ale to nás dostane k problémům s formátováním. Zkuste si to například pro datum nebo List, s výsledkem ale nejspíše spokojeni nebudete. Toto omezení frameworku vede na kód z následující ukázky:

protected void writeAttribute(Object value) {
    /* … */
    // handle format of collections
    if (Collection.class.isAssignableFrom(value.getClass())) {
        Collection<?> collection = (Collection<?>) value;
        if (collection.isEmpty()) {
            cell.setCellValue("");
        } else {
            // separate list elements by new lines
            StringBuilder sb = new StringBuilder();
            for (Object object : collection) {
                sb.append(object).append("\n");
            }
            sb.deleteCharAt(sb.length() - 1);
            cell.setCellValue(sb.toString());
        }
    }
    /* … */
}

Pokud tedy chceme zapisovat různorodé objekty a jejich návratová hodnota metody toString() se neschoduje s požadovaným formátem XLS, musíme si pro každý formát napsat handler.

Ukončení zápisu

Zapsat výsledný workbook je velice jednoduché – třída Workbook poskyje metodu write(OutputStream), jejíž zavolání přesměruje všechna data do poskytnutého streamu. Nejsme tedy omezeni například na zápis pouze do souboru na disk, což nám v IdM přijde vhod. Zapisovací metodu „nakrmíme“ třídou ByteArrayOutputStream, z níž pohodlně dostaneme data jako pole bytů. Jakmile pak uživatel využije možnosti exportu XLS, vyskočí mu v prohlížeči nabídka na stažení sešitu a nepotřebuje nic hledat. Ukázku výsledného souboru si můžete prohlédnout na obrázku níže.

Ukázka výsledného XLS.

Ukázka výsledného XLS.

Závěr

Možnosti Apache POI jsou velmi široké a tento článek pokryl pouze jejich malou část. Pokud vytváříte aplikace, jejichž části musí být použitelné v kancelářském prostředí, je to i přes drobné nedostatky zajisté dobrá volba.

Pokud byste měli nějaké dotazy, neváhejte mě kontaktovat na info@bcvsolutions.eu.