Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- fj-doc-core, check table columns and rows integrity <https://github.com/fugerit-org/fj-doc/issues/480>

## [8.14.1] - 2025-08-17

### Changed
Expand Down
24 changes: 24 additions & 0 deletions docs/html/doc_meta_info.html
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,30 @@ <h2 style="font-weight: bold;">Properties for generic metadata</h2>
<td id="cell_13_4" style=" width: 5%; border-top: 1px solid black; border-bottom: 1px solid black; border-left: 1px solid black; border-right: 1px solid black; padding: 2px;">
<p>8.11.5</p>
</td>
</tr>
<tr>
<td id="cell_14_0" style=" width: 20%; border-top: 1px solid black; border-bottom: 1px solid black; border-left: 1px solid black; border-right: 1px solid black; padding: 2px;">
<span id="table-check-integrity"></span>
<p style="font-style: italic;">table-check-integrity</p>
</td>
<td id="cell_14_1" style=" width: 40%; border-top: 1px solid black; border-bottom: 1px solid black; border-left: 1px solid black; border-right: 1px solid black; padding: 2px;">
<p>
Check integrity of table structure, see https://github.com/fugerit-org/fj-doc/issues/480.
Allowed values :
'disabled' - no check (default value),
'warn' - report on the log table structure issues as warning,
'fail' - report on the log table structure issues as error and throw a DocFeatureRuntimeException.
</p>
</td>
<td id="cell_14_2" style=" width: 25%; border-top: 1px solid black; border-bottom: 1px solid black; border-left: 1px solid black; border-right: 1px solid black; padding: 2px;">
<p>all, </p>
</td>
<td id="cell_14_3" style=" width: 10%; border-top: 1px solid black; border-bottom: 1px solid black; border-left: 1px solid black; border-right: 1px solid black; padding: 2px;">
<p>warn</p>
</td>
<td id="cell_14_4" style=" width: 5%; border-top: 1px solid black; border-bottom: 1px solid black; border-left: 1px solid black; border-right: 1px solid black; padding: 2px;">
<p>8.15.0</p>
</td>
</tr>
</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ public static DocInput newInput( String type, Reader reader, int source ) {
public static DocInput newInput( String type, DocBase doc, Reader reader, int source ) {
return new DocInput( type, doc, reader, source );
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import org.fugerit.java.core.io.StreamIO;
import org.fugerit.java.core.lang.helpers.ClassHelper;
import org.fugerit.java.doc.base.config.DocException;
import org.fugerit.java.doc.base.feature.DocFeatureRuntimeException;
import org.fugerit.java.doc.base.feature.FeatureConfig;
import org.fugerit.java.doc.base.model.DocBase;
import org.fugerit.java.doc.base.parser.DocParser;
import org.fugerit.java.doc.base.xml.DocXMLUtils;
Expand Down Expand Up @@ -89,23 +91,34 @@ public DocParser getParserForSource( int sourceType ) {
public boolean isSourceSupported( int sourceType ) {
return ( getParserForSource(sourceType) != null );
}
public DocBase parseRE( Reader reader, int sourceType ) {

public DocBase parseRE(Reader reader, int sourceType, FeatureConfig featureConfig) {
DocBase docBase = null;
try {
docBase = this.parse(reader, sourceType);
docBase = this.parse(reader, sourceType, featureConfig);
} catch (DocFeatureRuntimeException e) {
throw e;
} catch (DocException e) {
throw new ConfigRuntimeException( e );
}
return docBase;
}

public DocBase parseRE( Reader reader, int sourceType ) {
return this.parseRE( reader, sourceType, FeatureConfig.DEFAULT );
}

public DocBase parse( Reader reader, int sourceType ) throws DocException {
return this.parse( reader, sourceType, FeatureConfig.DEFAULT );
}

public DocBase parse( Reader reader, int sourceType, FeatureConfig featureConfig ) throws DocException {
DocBase docBase = null;
DocParser parser = this.getParserForSource(sourceType);
if ( parser == null ) {
throw new DocException( "No parser found for source type : "+sourceType );
} else {
parser.setFeatureConfig( featureConfig );
docBase = parser.parse(reader);
}
return docBase;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,11 @@ public void process( String chainId, String type, DocProcessContext context, Out
}

public SAXParseResult process( String chainId, String type, DocProcessContext context, OutputStream os, boolean validate ) throws Exception {
return process( this.getDocProcessConfig().getChain( chainId ), this.getDocHandlerFacade(), type, context, os, validate );
}

public static SAXParseResult process( MiniFilterChain chain, DocHandlerFacade docHandlerFacade, String type, DocProcessContext context, OutputStream os, boolean validate ) throws Exception {
SAXParseResult result = null;
MiniFilterChain chain = this.getDocProcessConfig().getChain( chainId );
DocProcessData data = new DocProcessData();
chain.apply(context, data);
if ( validate ) {
Expand All @@ -112,8 +115,8 @@ public SAXParseResult process( String chainId, String type, DocProcessContext co
}
DocInput input = DocInput.newInput( type, docBase, data.getCurrentXmlReader() );
DocOutput output = DocOutput.newOutput( os );
this.getDocHandlerFacade().handle(input, output);
docHandlerFacade.handle(input, output);
return result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.fugerit.java.doc.base.feature;

import lombok.Getter;
import org.fugerit.java.core.lang.ex.CodeException;
import org.fugerit.java.core.lang.ex.CodeRuntimeException;
import org.fugerit.java.core.lang.ex.ExConverUtils;
import org.fugerit.java.core.util.result.Result;

import java.util.ArrayList;
import java.util.List;

public class DocFeatureRuntimeException extends CodeRuntimeException {

/**
* <p>Default value for the code field in a DocFeatureRuntimeException.</p>
*/
public static final int DEFAULT_CODE = CodeException.DEFAULT_CODE;

@Getter
private final List<String> messages;

public DocFeatureRuntimeException(String message, Throwable cause, int code) {
this(message, cause, code, new ArrayList<>());
}

public DocFeatureRuntimeException(String message, Throwable cause, int code, List<String> messages) {
super(message, cause, code);
this.messages = messages;
}

public DocFeatureRuntimeException(String message, int code, List<String> messages) {
this(message, null, code, messages);
}

public static DocFeatureRuntimeException standardExceptionWrapping(Exception e ) throws DocFeatureRuntimeException {
throw convertEx( "Doc Feature runtime error", e );
}

public static DocFeatureRuntimeException convertEx( String baseMessage, Exception e ) {
DocFeatureRuntimeException res = null;
if ( e instanceof DocFeatureRuntimeException ) {
res = (DocFeatureRuntimeException)e;
} else {
res = new DocFeatureRuntimeException( ExConverUtils.defaultMessage(baseMessage, e), e, Result.RESULT_CODE_KO );
}
return res;
}

public static DocFeatureRuntimeException convertExMethod( String method, Exception e ) {
return convertEx( ExConverUtils.defaultMethodMessage(method), e );
}

public static DocFeatureRuntimeException convertEx( Exception e ) {
return convertEx( ExConverUtils.DEFAULT_CAUSE_MESSAGE, e );
}

/**
* Convert the exception to DocFeatureRuntimeException
*
* RuntimeException are left unchanged
*
* @param baseMessage the base message
* @param e the exception
* @return the runtime exception
*/
public static RuntimeException convertToRuntimeEx( String baseMessage, Exception e ) {
RuntimeException res = null;
if ( e instanceof RuntimeException ) {
res = (RuntimeException)e;
} else {
res = new DocFeatureRuntimeException( ExConverUtils.defaultMessage(baseMessage, e), e, Result.RESULT_CODE_KO );
}
return res;
}


/**
* Convert the exception to DocFeatureRuntimeException
*
* RuntimeException are left unchanged
*
* @param e the exception
* @return the runtime exception
*/
public static RuntimeException convertToRuntimeEx( Exception e ) {
return convertToRuntimeEx( ExConverUtils.DEFAULT_CAUSE_MESSAGE, e );
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.fugerit.java.doc.base.feature;


import org.fugerit.java.doc.base.feature.tableintegritycheck.TableIntegrityCheckConstants;
import org.fugerit.java.doc.base.typehelper.generic.GenericConsts;

public interface FeatureConfig {

static FeatureConfig fromFailWhenElementNotFound( boolean failWhenElementNotFound ) {
return new FeatureConfig() {
@Override
public boolean isFailWhenElementNotFound() {
return failWhenElementNotFound;
}
};
}

static final FeatureConfig DEFAULT = new FeatureConfig() {};

default String getTableCheckIntegrity() {
return TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_DEFAULT;
}

default boolean isFailWhenElementNotFound() {
return GenericConsts.FAIL_WHEN_ELEMENT_NOT_FOUND_DEFAULT;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.fugerit.java.doc.base.feature.tableintegritycheck;

import lombok.extern.slf4j.Slf4j;
import org.fugerit.java.core.lang.helpers.StringUtils;
import org.fugerit.java.core.util.result.Result;
import org.fugerit.java.doc.base.feature.DocFeatureRuntimeException;
import org.fugerit.java.doc.base.feature.FeatureConfig;
import org.fugerit.java.doc.base.model.*;
import org.fugerit.java.doc.base.typehelper.generic.GenericConsts;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

@Slf4j
public class TableIntegrityCheck {

private TableIntegrityCheck() {}

private static final Map<String, Consumer<DocTable>> DOC_CONSUMER_MAP = new HashMap<>();
static {
DOC_CONSUMER_MAP.put(TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_DISABLED, doc -> log.debug("Table Integrity Check disabled") );
DOC_CONSUMER_MAP.put(TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_WARN, doc -> checkWorker( doc, result -> {} ) );
DOC_CONSUMER_MAP.put(TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_FAIL, doc -> checkWorker( doc, result -> {
if ( result.getResultCode() != Result.RESULT_CODE_KO ) {
throw new DocFeatureRuntimeException( "Table check integrity failed, see logs for details.", result.getResultCode(), result.getMessages() );
}
} ) );
}

private static int processCells( DocRow docRow, final int currentColParam, Map<Integer,Integer> rowSpanTracker ) {
int currentCol = currentColParam;
for (DocElement cell : docRow.getElementList()) {
DocCell docCell = (DocCell) cell;
int colspan = Math.max(1, docCell.getColumnSpan());
int rowspan = Math.max(1, docCell.getRowSpan());
int startCol = currentCol;
currentCol += colspan;
// mark future row occupation
if (rowspan > 1) {
for (int c = startCol; c < startCol+colspan; c++) {
rowSpanTracker.put(c, rowSpanTracker.getOrDefault(c,0) + (rowspan-1));
}
}
}
return currentCol;
}

private static void validateRows(TableIntegrityCheckResult result, DocTable docTable, int columns, Map<Integer,Integer> rowSpanTracker ) {
int rowIndex = 0;
for (DocElement row : docTable.getElementList()) {
DocRow docRow = (DocRow) row;
int currentCol = 0;
// consume row spans first
while (currentCol < columns && rowSpanTracker.getOrDefault(currentCol, 0) > 0) {
rowSpanTracker.put(currentCol, rowSpanTracker.get(currentCol) - 1);
currentCol++;
}
// process actual row cells exactly once
currentCol = processCells(docRow, currentCol, rowSpanTracker);
if (currentCol != columns) {
result.getMessages().add(
String.format("Row %s has %s columns instead of %s", rowIndex, currentCol, columns)
);
}
rowIndex++;
}
}


private static TableIntegrityCheckResult checkWorker(DocTable docTable, Consumer<TableIntegrityCheckResult> resultConsumer) {
TableIntegrityCheckResult result = new TableIntegrityCheckResult(Result.RESULT_CODE_OK);
int columns = docTable.getColumns();
Map<Integer,Integer> rowSpanTracker = new HashMap<>();
validateRows( result, docTable, columns, rowSpanTracker );
for (Map.Entry<Integer,Integer> e : rowSpanTracker.entrySet()) {
if (e.getValue() > 0) {
result.getMessages().add( String.format( "Unfinished rowspan at column %s", e.getKey() ) );
}
}
result.setResultCode( result.getMessages().size() );
if ( result.getResultCode() == Result.RESULT_CODE_OK ) {
log.debug( "Table Integrity Check OK" );
} else {
log.warn( "Table Integrity Check FAILED : {}", result.getResultCode() );
result.getMessages().forEach(log::warn);
resultConsumer.accept( result );
}
return result;
}

public static void apply(DocBase docBase, DocTable docTable, FeatureConfig featureConfig) {
String tableCheckIntegrityInfo = StringUtils.valueWithDefault(
docBase.getStableInfoSafe().getProperty( GenericConsts.DOC_TABLE_CHECK_INTEGRITY ),
featureConfig.getTableCheckIntegrity() );
Consumer<DocTable> consumer = DOC_CONSUMER_MAP.get( tableCheckIntegrityInfo );
consumer.accept( docTable );
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.fugerit.java.doc.base.feature.tableintegritycheck;

public class TableIntegrityCheckConstants {

private TableIntegrityCheckConstants() {}

public static final String TABLE_INTEGRITY_CHECK_DISABLED = "disabled";
public static final String TABLE_INTEGRITY_CHECK_FAIL = "fail";
public static final String TABLE_INTEGRITY_CHECK_WARN = "warn";
public static final String TABLE_INTEGRITY_CHECK_DEFAULT = TABLE_INTEGRITY_CHECK_DISABLED;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.fugerit.java.doc.base.feature.tableintegritycheck;

import lombok.Getter;
import lombok.Setter;
import org.fugerit.java.core.util.result.BasicResult;

import java.util.ArrayList;
import java.util.List;

public class TableIntegrityCheckResult extends BasicResult {

public TableIntegrityCheckResult(int resultCode) {
super(resultCode);
this.messages = new ArrayList<>();
}

@Getter @Setter
private List<String> messages;

}
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,15 @@ public Properties getInfo() {
return info;
}

private Properties stableInfo;

public Properties getStableInfo() {
return stableInfo;
}
@Getter @Setter private Properties stableInfo;

public void setStableInfo(Properties stableInfo) {
this.stableInfo = stableInfo;
public Properties getStableInfoSafe() {
if ( this.getStableInfo() == null ) {
this.setStableInfo( this.getInfo() );
}
return this.getStableInfo();
}

public String getInfoPageWidth() {
return this.getStableInfo().getProperty( DocInfo.INFO_NAME_PAGE_WIDTH );
}
Expand Down
Loading