Skip to content

Commit 299519b

Browse files
authored
Merge pull request #488 from fugerit-org/480-enhancement-fj-doc-core-check-table-columns-and-rows-integrity
480 enhancement fj doc core check table columns and rows integrity
2 parents b5e0a06 + 9ea9998 commit 299519b

File tree

48 files changed

+1360
-173
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1360
-173
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- fj-doc-core, check table columns and rows integrity <https://github.com/fugerit-org/fj-doc/issues/480>
13+
1014
## [8.14.1] - 2025-08-17
1115

1216
### Changed

docs/html/doc_meta_info.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,30 @@ <h2 style="font-weight: bold;">Properties for generic metadata</h2>
303303
<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;">
304304
<p>8.11.5</p>
305305
</td>
306+
</tr>
307+
<tr>
308+
<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;">
309+
<span id="table-check-integrity"></span>
310+
<p style="font-style: italic;">table-check-integrity</p>
311+
</td>
312+
<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;">
313+
<p>
314+
Check integrity of table structure, see https://github.com/fugerit-org/fj-doc/issues/480.
315+
Allowed values :
316+
'disabled' - no check (default value),
317+
'warn' - report on the log table structure issues as warning,
318+
'fail' - report on the log table structure issues as error and throw a DocFeatureRuntimeException.
319+
</p>
320+
</td>
321+
<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;">
322+
<p>all, </p>
323+
</td>
324+
<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;">
325+
<p>warn</p>
326+
</td>
327+
<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;">
328+
<p>8.15.0</p>
329+
</td>
306330
</tr>
307331
</tbody>
308332
</table>

fj-doc-base/src/main/java/org/fugerit/java/doc/base/config/DocInput.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ public static DocInput newInput( String type, Reader reader, int source ) {
5656
public static DocInput newInput( String type, DocBase doc, Reader reader, int source ) {
5757
return new DocInput( type, doc, reader, source );
5858
}
59+
5960

6061
}

fj-doc-base/src/main/java/org/fugerit/java/doc/base/facade/DocFacadeSource.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import org.fugerit.java.core.io.StreamIO;
1010
import org.fugerit.java.core.lang.helpers.ClassHelper;
1111
import org.fugerit.java.doc.base.config.DocException;
12+
import org.fugerit.java.doc.base.feature.DocFeatureRuntimeException;
13+
import org.fugerit.java.doc.base.feature.FeatureConfig;
1214
import org.fugerit.java.doc.base.model.DocBase;
1315
import org.fugerit.java.doc.base.parser.DocParser;
1416
import org.fugerit.java.doc.base.xml.DocXMLUtils;
@@ -89,23 +91,34 @@ public DocParser getParserForSource( int sourceType ) {
8991
public boolean isSourceSupported( int sourceType ) {
9092
return ( getParserForSource(sourceType) != null );
9193
}
92-
93-
public DocBase parseRE( Reader reader, int sourceType ) {
94+
95+
public DocBase parseRE(Reader reader, int sourceType, FeatureConfig featureConfig) {
9496
DocBase docBase = null;
9597
try {
96-
docBase = this.parse(reader, sourceType);
98+
docBase = this.parse(reader, sourceType, featureConfig);
99+
} catch (DocFeatureRuntimeException e) {
100+
throw e;
97101
} catch (DocException e) {
98102
throw new ConfigRuntimeException( e );
99103
}
100104
return docBase;
101105
}
106+
107+
public DocBase parseRE( Reader reader, int sourceType ) {
108+
return this.parseRE( reader, sourceType, FeatureConfig.DEFAULT );
109+
}
102110

103111
public DocBase parse( Reader reader, int sourceType ) throws DocException {
112+
return this.parse( reader, sourceType, FeatureConfig.DEFAULT );
113+
}
114+
115+
public DocBase parse( Reader reader, int sourceType, FeatureConfig featureConfig ) throws DocException {
104116
DocBase docBase = null;
105117
DocParser parser = this.getParserForSource(sourceType);
106118
if ( parser == null ) {
107119
throw new DocException( "No parser found for source type : "+sourceType );
108120
} else {
121+
parser.setFeatureConfig( featureConfig );
109122
docBase = parser.parse(reader);
110123
}
111124
return docBase;

fj-doc-base/src/main/java/org/fugerit/java/doc/base/facade/ProcessDocFacade.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,11 @@ public void process( String chainId, String type, DocProcessContext context, Out
9696
}
9797

9898
public SAXParseResult process( String chainId, String type, DocProcessContext context, OutputStream os, boolean validate ) throws Exception {
99+
return process( this.getDocProcessConfig().getChain( chainId ), this.getDocHandlerFacade(), type, context, os, validate );
100+
}
101+
102+
public static SAXParseResult process( MiniFilterChain chain, DocHandlerFacade docHandlerFacade, String type, DocProcessContext context, OutputStream os, boolean validate ) throws Exception {
99103
SAXParseResult result = null;
100-
MiniFilterChain chain = this.getDocProcessConfig().getChain( chainId );
101104
DocProcessData data = new DocProcessData();
102105
chain.apply(context, data);
103106
if ( validate ) {
@@ -112,8 +115,8 @@ public SAXParseResult process( String chainId, String type, DocProcessContext co
112115
}
113116
DocInput input = DocInput.newInput( type, docBase, data.getCurrentXmlReader() );
114117
DocOutput output = DocOutput.newOutput( os );
115-
this.getDocHandlerFacade().handle(input, output);
118+
docHandlerFacade.handle(input, output);
116119
return result;
117120
}
118-
121+
119122
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.fugerit.java.doc.base.feature;
2+
3+
import lombok.Getter;
4+
import org.fugerit.java.core.lang.ex.CodeException;
5+
import org.fugerit.java.core.lang.ex.CodeRuntimeException;
6+
import org.fugerit.java.core.lang.ex.ExConverUtils;
7+
import org.fugerit.java.core.util.result.Result;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
public class DocFeatureRuntimeException extends CodeRuntimeException {
13+
14+
/**
15+
* <p>Default value for the code field in a DocFeatureRuntimeException.</p>
16+
*/
17+
public static final int DEFAULT_CODE = CodeException.DEFAULT_CODE;
18+
19+
@Getter
20+
private final List<String> messages;
21+
22+
public DocFeatureRuntimeException(String message, Throwable cause, int code) {
23+
this(message, cause, code, new ArrayList<>());
24+
}
25+
26+
public DocFeatureRuntimeException(String message, Throwable cause, int code, List<String> messages) {
27+
super(message, cause, code);
28+
this.messages = messages;
29+
}
30+
31+
public DocFeatureRuntimeException(String message, int code, List<String> messages) {
32+
this(message, null, code, messages);
33+
}
34+
35+
public static DocFeatureRuntimeException standardExceptionWrapping(Exception e ) throws DocFeatureRuntimeException {
36+
throw convertEx( "Doc Feature runtime error", e );
37+
}
38+
39+
public static DocFeatureRuntimeException convertEx( String baseMessage, Exception e ) {
40+
DocFeatureRuntimeException res = null;
41+
if ( e instanceof DocFeatureRuntimeException ) {
42+
res = (DocFeatureRuntimeException)e;
43+
} else {
44+
res = new DocFeatureRuntimeException( ExConverUtils.defaultMessage(baseMessage, e), e, Result.RESULT_CODE_KO );
45+
}
46+
return res;
47+
}
48+
49+
public static DocFeatureRuntimeException convertExMethod( String method, Exception e ) {
50+
return convertEx( ExConverUtils.defaultMethodMessage(method), e );
51+
}
52+
53+
public static DocFeatureRuntimeException convertEx( Exception e ) {
54+
return convertEx( ExConverUtils.DEFAULT_CAUSE_MESSAGE, e );
55+
}
56+
57+
/**
58+
* Convert the exception to DocFeatureRuntimeException
59+
*
60+
* RuntimeException are left unchanged
61+
*
62+
* @param baseMessage the base message
63+
* @param e the exception
64+
* @return the runtime exception
65+
*/
66+
public static RuntimeException convertToRuntimeEx( String baseMessage, Exception e ) {
67+
RuntimeException res = null;
68+
if ( e instanceof RuntimeException ) {
69+
res = (RuntimeException)e;
70+
} else {
71+
res = new DocFeatureRuntimeException( ExConverUtils.defaultMessage(baseMessage, e), e, Result.RESULT_CODE_KO );
72+
}
73+
return res;
74+
}
75+
76+
77+
/**
78+
* Convert the exception to DocFeatureRuntimeException
79+
*
80+
* RuntimeException are left unchanged
81+
*
82+
* @param e the exception
83+
* @return the runtime exception
84+
*/
85+
public static RuntimeException convertToRuntimeEx( Exception e ) {
86+
return convertToRuntimeEx( ExConverUtils.DEFAULT_CAUSE_MESSAGE, e );
87+
}
88+
89+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.fugerit.java.doc.base.feature;
2+
3+
4+
import org.fugerit.java.doc.base.feature.tableintegritycheck.TableIntegrityCheckConstants;
5+
import org.fugerit.java.doc.base.typehelper.generic.GenericConsts;
6+
7+
public interface FeatureConfig {
8+
9+
static FeatureConfig fromFailWhenElementNotFound( boolean failWhenElementNotFound ) {
10+
return new FeatureConfig() {
11+
@Override
12+
public boolean isFailWhenElementNotFound() {
13+
return failWhenElementNotFound;
14+
}
15+
};
16+
}
17+
18+
static final FeatureConfig DEFAULT = new FeatureConfig() {};
19+
20+
default String getTableCheckIntegrity() {
21+
return TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_DEFAULT;
22+
}
23+
24+
default boolean isFailWhenElementNotFound() {
25+
return GenericConsts.FAIL_WHEN_ELEMENT_NOT_FOUND_DEFAULT;
26+
}
27+
28+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.fugerit.java.doc.base.feature.tableintegritycheck;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.fugerit.java.core.lang.helpers.StringUtils;
5+
import org.fugerit.java.core.util.result.Result;
6+
import org.fugerit.java.doc.base.feature.DocFeatureRuntimeException;
7+
import org.fugerit.java.doc.base.feature.FeatureConfig;
8+
import org.fugerit.java.doc.base.model.*;
9+
import org.fugerit.java.doc.base.typehelper.generic.GenericConsts;
10+
11+
import java.util.HashMap;
12+
import java.util.Map;
13+
import java.util.function.Consumer;
14+
15+
@Slf4j
16+
public class TableIntegrityCheck {
17+
18+
private TableIntegrityCheck() {}
19+
20+
private static final Map<String, Consumer<DocTable>> DOC_CONSUMER_MAP = new HashMap<>();
21+
static {
22+
DOC_CONSUMER_MAP.put(TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_DISABLED, doc -> log.debug("Table Integrity Check disabled") );
23+
DOC_CONSUMER_MAP.put(TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_WARN, doc -> checkWorker( doc, result -> {} ) );
24+
DOC_CONSUMER_MAP.put(TableIntegrityCheckConstants.TABLE_INTEGRITY_CHECK_FAIL, doc -> checkWorker( doc, result -> {
25+
if ( result.getResultCode() != Result.RESULT_CODE_KO ) {
26+
throw new DocFeatureRuntimeException( "Table check integrity failed, see logs for details.", result.getResultCode(), result.getMessages() );
27+
}
28+
} ) );
29+
}
30+
31+
private static int processCells( DocRow docRow, final int currentColParam, Map<Integer,Integer> rowSpanTracker ) {
32+
int currentCol = currentColParam;
33+
for (DocElement cell : docRow.getElementList()) {
34+
DocCell docCell = (DocCell) cell;
35+
int colspan = Math.max(1, docCell.getColumnSpan());
36+
int rowspan = Math.max(1, docCell.getRowSpan());
37+
int startCol = currentCol;
38+
currentCol += colspan;
39+
// mark future row occupation
40+
if (rowspan > 1) {
41+
for (int c = startCol; c < startCol+colspan; c++) {
42+
rowSpanTracker.put(c, rowSpanTracker.getOrDefault(c,0) + (rowspan-1));
43+
}
44+
}
45+
}
46+
return currentCol;
47+
}
48+
49+
private static void validateRows(TableIntegrityCheckResult result, DocTable docTable, int columns, Map<Integer,Integer> rowSpanTracker ) {
50+
int rowIndex = 0;
51+
for (DocElement row : docTable.getElementList()) {
52+
DocRow docRow = (DocRow) row;
53+
int currentCol = 0;
54+
// consume row spans first
55+
while (currentCol < columns && rowSpanTracker.getOrDefault(currentCol, 0) > 0) {
56+
rowSpanTracker.put(currentCol, rowSpanTracker.get(currentCol) - 1);
57+
currentCol++;
58+
}
59+
// process actual row cells exactly once
60+
currentCol = processCells(docRow, currentCol, rowSpanTracker);
61+
if (currentCol != columns) {
62+
result.getMessages().add(
63+
String.format("Row %s has %s columns instead of %s", rowIndex, currentCol, columns)
64+
);
65+
}
66+
rowIndex++;
67+
}
68+
}
69+
70+
71+
private static TableIntegrityCheckResult checkWorker(DocTable docTable, Consumer<TableIntegrityCheckResult> resultConsumer) {
72+
TableIntegrityCheckResult result = new TableIntegrityCheckResult(Result.RESULT_CODE_OK);
73+
int columns = docTable.getColumns();
74+
Map<Integer,Integer> rowSpanTracker = new HashMap<>();
75+
validateRows( result, docTable, columns, rowSpanTracker );
76+
for (Map.Entry<Integer,Integer> e : rowSpanTracker.entrySet()) {
77+
if (e.getValue() > 0) {
78+
result.getMessages().add( String.format( "Unfinished rowspan at column %s", e.getKey() ) );
79+
}
80+
}
81+
result.setResultCode( result.getMessages().size() );
82+
if ( result.getResultCode() == Result.RESULT_CODE_OK ) {
83+
log.debug( "Table Integrity Check OK" );
84+
} else {
85+
log.warn( "Table Integrity Check FAILED : {}", result.getResultCode() );
86+
result.getMessages().forEach(log::warn);
87+
resultConsumer.accept( result );
88+
}
89+
return result;
90+
}
91+
92+
public static void apply(DocBase docBase, DocTable docTable, FeatureConfig featureConfig) {
93+
String tableCheckIntegrityInfo = StringUtils.valueWithDefault(
94+
docBase.getStableInfoSafe().getProperty( GenericConsts.DOC_TABLE_CHECK_INTEGRITY ),
95+
featureConfig.getTableCheckIntegrity() );
96+
Consumer<DocTable> consumer = DOC_CONSUMER_MAP.get( tableCheckIntegrityInfo );
97+
consumer.accept( docTable );
98+
}
99+
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.fugerit.java.doc.base.feature.tableintegritycheck;
2+
3+
public class TableIntegrityCheckConstants {
4+
5+
private TableIntegrityCheckConstants() {}
6+
7+
public static final String TABLE_INTEGRITY_CHECK_DISABLED = "disabled";
8+
public static final String TABLE_INTEGRITY_CHECK_FAIL = "fail";
9+
public static final String TABLE_INTEGRITY_CHECK_WARN = "warn";
10+
public static final String TABLE_INTEGRITY_CHECK_DEFAULT = TABLE_INTEGRITY_CHECK_DISABLED;
11+
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.fugerit.java.doc.base.feature.tableintegritycheck;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
import org.fugerit.java.core.util.result.BasicResult;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
public class TableIntegrityCheckResult extends BasicResult {
11+
12+
public TableIntegrityCheckResult(int resultCode) {
13+
super(resultCode);
14+
this.messages = new ArrayList<>();
15+
}
16+
17+
@Getter @Setter
18+
private List<String> messages;
19+
20+
}

0 commit comments

Comments
 (0)