Estrai dati da tabelle HTML complesse a array 2d in Java

Come convertire le tabelle HTML con colspan e rowspan in array 2d (martix) in Java?

Ho trovato delle belle soluzioni in Python e jQuery ma non in Java (solo tabelle molto semplici via jsoup). C’è una soluzione carina con XSLT ma, a causa di file HTML di input malformati, non è OK per me.

Esempio di tabella di input:

 
H1H2
SubH2_1SubH2_2
A1B1C1
B2
C3
C4C5C6
D7D9
Notes

inserisci la descrizione dell'immagine qui

Output desiderato:

  [['H1', 'H2', 'H2'], ['', 'SubH2_1', 'SubH2_2'], ['A1', 'B1', 'C1'], ['A1', 'B2', 'C3'], ['C4', 'C5', 'C6'], ['D7', 'D9', 'D9'], ['Notes', 'Notes', 'Notes']] 

Ho trovato un modo per farlo usando l’ API Jsoup e Java 8 Stream:

 //given: final InputStream html = getClass().getClassLoader().getResourceAsStream("table.html"); //when: final Document document = Jsoup.parse(html, "UTF-8", "/"); final List> result = document.select("table tr") .stream() // Select all  tags in single row .map(tr -> tr.select("td")) // Repeat n-times those  that have `colspan="n"` attribute .map(rows -> rows.stream() .map(td -> Collections.nCopies(td.hasAttr("colspan") ? Integer.valueOf(td.attr("colspan")) : 1, td)) .flatMap(Collection::stream) .collect(Collectors.toList()) ) // Fold final structure to 2D List> .reduce(new ArrayList>(), (acc, row) -> { // First iteration - just add current row to a final structure if (acc.isEmpty()) { acc.add(row); return acc; } // If last array in 2D array does not contain element with `rowspan` - append current // row and skip to next iteration step final List last = acc.get(acc.size() - 1); if (last.stream().noneMatch(td -> td.hasAttr("rowspan"))) { acc.add(row); return acc; } // In this case last array in 2D array contains an element with `rowspan` - we are going to // add this element n-times to current rows where n == rowspan - 1 final AtomicInteger index = new AtomicInteger(0); last.stream() // Map to a helper list of (index in array, rowspan value or 0 if not present, Jsoup element) .map(td -> Arrays.asList(index.getAndIncrement(), Integer.valueOf(td.hasAttr("rowspan") ? td.attr("rowspan") : "0"), td)) // Filter out all elements without rowspan .filter(it -> ((int) it.get(1)) > 1) // Add all elements with rowspan to current row at the index they are present // (add them with `rowspan="n-1"`) .forEach(it -> { final int idx = (int) it.get(0); final int rowspan = (int) it.get(1); final Element td = (Element) it.get(2); row.add(idx, rowspan - 1 == 0 ? (Element) td.removeAttr("rowspan") : td.attr("rowspan", String.valueOf(rowspan - 1))); }); acc.add(row); return acc; }, (a, b) -> a) .stream() // Extract inner HTML text from Jsoup elements in 2D array .map(tr -> tr.stream() .map(Element::text) .collect(Collectors.toList()) ) .collect(Collectors.toList()); 

Ho aggiunto molti commenti che spiegano cosa succede al passo dell’algoritmo specifico.

In questo esempio ho usato il seguente file html:

  
H1H2
SubH2_1SubH2_2
A1B1C1
B2C3
C4C5C6
D7D9
Notes

È uguale al tuo, l’unica differenza è che l’utilizzo di rowspan è stato risolto: nell’esempio A1 viene ripetuto tre volte anziché due. Anche due

in questo esempio sono stati chiusi correttamente, altrimenti nella struttura finale vengono visualizzati due array aggiuntivi vuoti.

Ecco l’output della console:

 [H1, H2, H2] [, SubH2_1, SubH2_2] [A1, B1, C1] [A1, B2, C3] [C4, C5, C6] [D7, D9, D9] [Notes, Notes, Notes] 

È ansible eseguire questo esempio con l’HTML esatto mentre si incolla nella domanda, produrrà un output leggermente diverso:

 [H1, H2, H2] [] [, SubH2_1, SubH2_2] [] [A1, B1, C1] [A1, B2, C1] [A1, B2, C3] [C4, C5, C6] [D7, D9, D9] [Notes, Notes, Notes] 

Questi array vuoti vengono visualizzati perché nel tuo codice HTML sono presenti due elementi

chiusi.

 H1H2 SubH2_1SubH2_2 

Chiudendoli e eseguendo di nuovo l’algoritmo creerà il seguente risultato:

 [H1, H2, H2] [, SubH2_1, SubH2_2] [A1, B1, C1] [A1, B2, C1] [A1, B2, C3] [C4, C5, C6] [D7, D9, D9] [Notes, Notes, Notes] 

Come puoi vedere A1 esiste 3 volte perché ha un attributo rowspan="3" e B2 ha rowspan="2" e C1 ha rowspan="2" pure. Genera HTML che sembra “quasi” uguale a uno nel mio primo esempio, ma quando guardi più da vicino queste 3 righe vedrai che non si trovano allo stesso livello di pixel. Seguendo la risposta prevista, ho corretto l’HTML di input in modo che appaia e si comporti come ci si aspetta.

Cosa succede se non riesco a modificare l’input HTML?

Bene, se non puoi modificare l’input HTML allora dovrai:

  • filtra tutti gli array vuoti creati a causa di tag

    chiusi

  • rivedere le aspettative di output per A1 , B2 e C3 : la visualizzazione HTML non mostra la struttura esatta di questa tabella scritta in HTML.

Codice sorgente del progetto di esempio

Qui puoi trovare il codice sorgente completo di un test JUnit che ho usato per trovare la risposta alla tua domanda. Sentiti libero di scaricare questo esempio di progetto Maven ospitato su GitHub per giocare con l’implementazione dell’algoritmo.

Spero possa essere d’aiuto.