All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hcl.domino.richtext.RichTextTableParser Maven / Gradle / Ivy

There is a newer version: 1.44.0
Show newest version
/*
 * ==========================================================================
 * Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
 *                            All rights reserved.
 * ==========================================================================
 * Licensed under the  Apache License, Version 2.0  (the "License").  You may
 * not use this file except in compliance with the License.  You may obtain a
 * copy of the License at .
 *
 * Unless  required  by applicable  law or  agreed  to  in writing,  software
 * distributed under the License is distributed on an  "AS IS" BASIS, WITHOUT
 * WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the  specific language  governing permissions  and limitations
 * under the License.
 * ==========================================================================
 */
package com.hcl.domino.richtext;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;

import com.hcl.domino.design.GenericFormOrSubform;
import com.hcl.domino.richtext.records.CDBegin;
import com.hcl.domino.richtext.records.CDEnd;
import com.hcl.domino.richtext.records.CDPreTableBegin;
import com.hcl.domino.richtext.records.CDTableBegin;
import com.hcl.domino.richtext.records.CDTableCell;
import com.hcl.domino.richtext.records.CDTableDataExtension;
import com.hcl.domino.richtext.records.CDTableEnd;
import com.hcl.domino.richtext.records.RichTextRecord;

/**
 * Utility class that identifies tables in richtext and returns them row by row and cell by cell.
* The following methods will be called during parsing:
*
    *
  • {@link #nonTableRecordFound(RichTextRecord)} - for all CD records outside of tables (before and after)
  • *
  • {@link #tableBeginFound(int, Collection, CDTableBegin, Collection)} - when a table has been found
  • *
  • {@link #rowFound(int, short, Collection)} - for each table row with all contained records
  • *
  • {@link #cellFound(int, short, short, CDTableCell, Collection)} - for each table cell with the table cell content
  • *
  • {@link #tableEndFound(int, CDTableEnd)} - when the table end has been reached
  • *
* Nested tables in cells are reported as part of the row/cell content.

* * @author Karsten Lehmann */ public abstract class RichTextTableParser { private ListIterator> rtIterator; /** * Creates a new parser instance * * @param records list of richtext records */ public RichTextTableParser(RichTextRecordList records) { this(records.listIterator()); } /** * Creates a new parser instance * * @param rtIterator list iterator of richtext records */ public RichTextTableParser(ListIterator> rtIterator) { this.rtIterator = rtIterator; } /** * Creates a new parser instance * * @param formOrSubform form or subform design element */ public RichTextTableParser(GenericFormOrSubform formOrSubform) { this(formOrSubform.getDocument().getRichTextItem("$body")); } /** * call this method to start parsing */ public void parse() { //index of current table int tableIndex = 0; //optional CDBEGIN/CDEND block with CDPRETABLEBEGIN (R5+) and CDTABLEDATAEXTENSION (R6+) List> tableHeaderRecords = new ArrayList<>(); //all records belonging to a table enclosed in CDTABLEBEGIN / CDTABLEEND List> tableContentRecords = new ArrayList<>(); while (rtIterator.hasNext()) { RichTextRecord record = rtIterator.next(); if (record instanceof CDBegin && ((CDBegin)record).getSignature() == RichTextConstants.SIG_CD_PRETABLEBEGIN) { //CDBEGIN/CDEND block with CDPRETABLEBEGIN (R5+) and CDTABLEDATAEXTENSION (R6+) tableHeaderRecords.clear(); tableHeaderRecords.add(record); //collect all table header records until CDEND while (rtIterator.hasNext()) { RichTextRecord headerRecord = rtIterator.next(); tableHeaderRecords.add(headerRecord); if (headerRecord instanceof CDEnd && ((CDEnd)headerRecord).getSignature() == RichTextConstants.SIG_CD_PRETABLEBEGIN) { break; } } } else if (record instanceof CDTableBegin) { tableContentRecords.clear(); tableContentRecords.add(record); int nestedTableDepth = 1; //read all table content into tableContentRecords, ignore nested tables while (rtIterator.hasNext()) { RichTextRecord recordInTable = rtIterator.next(); tableContentRecords.add(recordInTable); if (recordInTable instanceof CDTableBegin) { nestedTableDepth++; } else if (recordInTable instanceof CDTableEnd) { nestedTableDepth--; if (nestedTableDepth==0) { break; } } } if (nestedTableDepth>0) { throw new IllegalStateException("Unexpected non-closed nested table(s) found"); } parseTableContent(tableIndex, tableHeaderRecords, tableContentRecords); tableHeaderRecords.clear(); tableContentRecords.clear(); tableIndex++; } else { nonTableRecordFound(record); } } } /** * Splits the table content into rows, reporting each row via {@link #rowFound(int, short, Collection)} * * @param tableIndex index of table (0=first) * @param tableHeaderRecords R5/R6 records with additional table infos, e.g. {@link CDPreTableBegin} and {@link CDTableDataExtension} * @param tableContentRecords table content enclosed in {@link CDTableBegin} and {@link CDTableEnd} * @return true to continue parsing */ private boolean parseTableContent(int tableIndex, List> tableHeaderRecords, List> tableContentRecords) { //find first cell CDTableCell firstCellRecord = null; //any records before the first cell List> nonRowRecords = new ArrayList<>(); ListIterator> tableContentRecordsIt = tableContentRecords.listIterator(); CDTableBegin tableBeginRecord = (CDTableBegin) tableContentRecordsIt.next(); //report any content before the first cell, e.g. CDTABLELABEL while (tableContentRecordsIt.hasNext()) { RichTextRecord record = tableContentRecordsIt.next(); if (record instanceof CDTableCell) { firstCellRecord = (CDTableCell) record; break; } else { nonRowRecords.add(record); } } if (firstCellRecord!=null) { if (!tableBeginFound(tableIndex, tableHeaderRecords, tableBeginRecord, nonRowRecords)) { return false; } List> rowRecords = new ArrayList<>(); rowRecords.add(firstCellRecord); short currRowIdx = firstCellRecord.getRow(); CDTableEnd tableEndRecord = null; while (tableContentRecordsIt.hasNext()) { RichTextRecord record = tableContentRecordsIt.next(); if (record instanceof CDTableBegin) { //nested table found, skip it until the end rowRecords.add(record); int nestedTableDepth = 0; while (tableContentRecordsIt.hasNext()) { RichTextRecord nestedRecord = tableContentRecordsIt.next(); rowRecords.add(nestedRecord); if (nestedRecord instanceof CDTableBegin) { nestedTableDepth++; } else if (nestedRecord instanceof CDTableEnd) { nestedTableDepth--; if (nestedTableDepth==0) { break; } } } } else if (record instanceof CDTableEnd) { //end of table found tableEndRecord = (CDTableEnd) record; break; } else if (record instanceof CDTableCell) { CDTableCell cellRecord = (CDTableCell) record; short cellRowIdx = cellRecord.getRow(); if (cellRowIdx == currRowIdx) { //same row as before rowRecords.add(record); } else { //report data for whole row if (!rowFound(tableIndex, currRowIdx, rowRecords)) { return false; } //and split row into cells if (!parseRowContent(tableIndex, currRowIdx, rowRecords)) { return false; } //start recording records for the new row rowRecords.clear(); rowRecords.add(cellRecord); currRowIdx = cellRowIdx; } } else { rowRecords.add(record); } } if (!rowRecords.isEmpty()) { //report last row if (!rowFound(tableIndex, currRowIdx, rowRecords)) { return false; } if (!parseRowContent(tableIndex, currRowIdx, rowRecords)) { return false; } } if (tableEndRecord!=null) { if (!tableEndFound(tableIndex, tableEndRecord)) { return false; } } } return true; } /** * Splits a row into cells, reporting the content of each cell via {@link #cellFound(int, CDTableCell, Collection)} * * @param tableIndex table index in richtext (0=first table) * @param rowIdx index of row (0=first row) * @param rowRecords records in row starting with {@link CDTableCell} * @return true to continue parsing */ private boolean parseRowContent(int tableIndex, short rowIdx, List> rowRecords) { CDTableCell cellRecord = null; List> cellContentRecords = new ArrayList<>(); ListIterator> rowRecordsIt = rowRecords.listIterator(); while (rowRecordsIt.hasNext()) { RichTextRecord record = rowRecordsIt.next(); if (record instanceof CDTableCell) { //new cell has started, report content of last cell if (!cellContentRecords.isEmpty()) { if (cellRecord!=null && !cellFound(tableIndex, rowIdx, cellRecord.getColumn(), cellRecord, cellContentRecords)) { return false; } cellContentRecords.clear(); } cellRecord = (CDTableCell) record; } else if (record instanceof CDTableBegin) { //nested table found; skip it cellContentRecords.add(record); int nestedTableDepth = 0; while (rowRecordsIt.hasNext()) { RichTextRecord nestedRecord = rowRecordsIt.next(); cellContentRecords.add(record); if (nestedRecord instanceof CDTableBegin) { nestedTableDepth++; } else if (nestedRecord instanceof CDTableEnd) { nestedTableDepth--; if (nestedTableDepth==0) { break; } } } } else { cellContentRecords.add(record); } } //report content of last cell if (cellRecord!=null && !cellContentRecords.isEmpty()) { if (!cellFound(tableIndex, rowIdx, cellRecord.getColumn(), cellRecord, cellContentRecords)) { return false; } } return true; } /** * Method is invoked for CD records outside of a table * * @param record record * @return true to continue parsing */ public abstract boolean nonTableRecordFound(RichTextRecord record); /** * Method is invoked when a {@link CDTableBegin} record has been found * * @param tableIndex table index in richtext (0=first table) * @param tableHeaderRecords additional table infos like {@link CDPreTableBegin}/{@link CDTableDataExtension} enclosed in {@link CDBegin}/{@link CDEnd} or empty list for pre R5 tables * @param tableBegin table begin record * @param nonRowRecords records that are not part of the first cell in the first row * @return true to continue parsing */ public abstract boolean tableBeginFound( int tableIndex, Collection> tableHeaderRecords, CDTableBegin tableBegin, Collection> nonRowRecords); /** * Method is invoked with all CD records of a table row * * @param tableIndex table index in richtext (0=first table) * @param rowIndex row index (0=first row) * @param records all records in row, each cell starting with {@link CDTableCell} * @return true to continue parsing */ public abstract boolean rowFound(int tableIndex, short rowIndex, Collection> records); /** * Method is invoked for each cell of a table * * @param tableIndex table index in richtext (0=first table) * @param rowIndex row index (0=first row) * @param colIndex column index * @param cellRecord table cell record, e.g. to read spans via {@link CDTableCell#getRowSpan()} or {@link CDTableCell#getColumnSpan()} * @param cellContent content of table cell * @return true to continue parsing */ public abstract boolean cellFound(int tableIndex, short rowIndex, short colIndex, CDTableCell cellRecord, Collection> cellContent); /** * Method is invoked if {@link CDTableEnd} record was found * * @param tableIndex table index in richtext (0=first table) * @param tableEnd table end record * @return true to continue parsing */ public abstract boolean tableEndFound(int tableIndex, CDTableEnd tableEnd); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy