RED-6929: fix acceptance tests/rules

* implemented MatchedRuleHolder Interface to merge code from Image and RedactionEntity
* moved SectionIdentifier to default interface method
* implemented getSearchTextWithLineBreaks in TextBlock
* implemented getValueWithLineBreaks and applyWithLineBreaks
* added update to intersecting nodes in merging rules as well
This commit is contained in:
Kilian Schuettler 2023-07-05 17:51:48 +02:00
parent cc4ef506ea
commit 0dd4f9c2d9
27 changed files with 391 additions and 213 deletions

View File

@ -83,7 +83,7 @@ public class DocumentGraphFactory {
List<TextPageBlock> textBlocks = new ArrayList<>(textBlocksToMerge); List<TextPageBlock> textBlocks = new ArrayList<>(textBlocksToMerge);
textBlocks.add(originalTextBlock); textBlocks.add(originalTextBlock);
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page); AtomicTextBlock textBlock = context.textBlockFactory.fromContext(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page);
List<Integer> treeId = context.documentTree.createNewChildEntryAndReturnId(parentNode, node); List<Integer> treeId = context.documentTree.createNewChildEntryAndReturnId(parentNode, node);
node.setLeafTextBlock(textBlock); node.setLeafTextBlock(textBlock);
node.setTreeId(treeId); node.setTreeId(treeId);
@ -145,10 +145,7 @@ public class DocumentGraphFactory {
Page page = context.getPage(textBlocks.get(0).getPage()); Page page = context.getPage(textBlocks.get(0).getPage());
Footer footer = Footer.builder().documentTree(context.getDocumentTree()).build(); Footer footer = Footer.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), AtomicTextBlock textBlock = context.textBlockFactory.fromContext(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), footer, context, page);
footer,
context,
page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(footer); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(footer);
footer.setTreeId(tocId); footer.setTreeId(tocId);
footer.setLeafTextBlock(textBlock); footer.setLeafTextBlock(textBlock);
@ -160,7 +157,7 @@ public class DocumentGraphFactory {
Page page = context.getPage(textBlocks.get(0).getPage()); Page page = context.getPage(textBlocks.get(0).getPage());
Header header = Header.builder().documentTree(context.getDocumentTree()).build(); Header header = Header.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), header, 0, page); AtomicTextBlock textBlock = context.textBlockFactory.fromNumberOnPage(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), header, 0, page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(header); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(header);
header.setTreeId(tocId); header.setTreeId(tocId);
header.setLeafTextBlock(textBlock); header.setLeafTextBlock(textBlock);

View File

@ -33,7 +33,11 @@ public class TableNodeFactory {
Set<Page> pages = tablesToMerge.stream().map(AbstractPageBlock::getPage).map(context::getPage).collect(Collectors.toSet()); Set<Page> pages = tablesToMerge.stream().map(AbstractPageBlock::getPage).map(context::getPage).collect(Collectors.toSet());
List<List<Cell>> mergedRows = tablesToMerge.stream().map(TablePageBlock::getRows).flatMap(Collection::stream).toList(); List<List<Cell>> mergedRows = tablesToMerge.stream().map(TablePageBlock::getRows).flatMap(Collection::stream).toList();
Table table = Table.builder().documentTree(context.getDocumentTree()).numberOfCols(mergedRows.isEmpty() ? 0 :mergedRows.get(0).size()).numberOfRows(mergedRows.size()).build(); Table table = Table.builder()
.documentTree(context.getDocumentTree())
.numberOfCols(mergedRows.isEmpty() ? 0 : mergedRows.get(0).size())
.numberOfRows(mergedRows.size())
.build();
pages.forEach(page -> addTableToPage(page, parentNode, table)); pages.forEach(page -> addTableToPage(page, parentNode, table));
@ -109,13 +113,13 @@ public class TableNodeFactory {
if (cell.getTextBlocks().isEmpty()) { if (cell.getTextBlocks().isEmpty()) {
tableCell.setLeafTextBlock(context.getTextBlockFactory().emptyTextBlock(tableNode, context, page)); tableCell.setLeafTextBlock(context.getTextBlockFactory().emptyTextBlock(tableNode, context, page));
} else if (cell.getTextBlocks().size() == 1) { } else if (cell.getTextBlocks().size() == 1) {
textBlock = context.getTextBlockFactory().buildAtomicTextBlock(cell.getTextBlocks().get(0).getSequences(), tableCell, context, page); textBlock = context.getTextBlockFactory().fromContext(cell.getTextBlocks().get(0).getSequences(), tableCell, context, page);
tableCell.setLeafTextBlock(textBlock); tableCell.setLeafTextBlock(textBlock);
} else if (firstTextBlockIsHeadline(cell)) { } else if (firstTextBlockIsHeadline(cell)) {
SectionNodeFactory.addSection(tableCell, cell.getTextBlocks().stream().map(tb -> (AbstractPageBlock) tb).toList(), emptyList(), context); SectionNodeFactory.addSection(tableCell, cell.getTextBlocks().stream().map(tb -> (AbstractPageBlock) tb).toList(), emptyList(), context);
} else if (cellAreaIsSmallerThanPageAreaTimesThreshold(cell, page)) { } else if (cellAreaIsSmallerThanPageAreaTimesThreshold(cell, page)) {
List<TextPositionSequence> sequences = TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(cell.getTextBlocks()); List<TextPositionSequence> sequences = TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(cell.getTextBlocks());
textBlock = context.getTextBlockFactory().buildAtomicTextBlock(sequences, tableCell, context, page); textBlock = context.getTextBlockFactory().fromContext(sequences, tableCell, context, page);
tableCell.setLeafTextBlock(textBlock); tableCell.setLeafTextBlock(textBlock);
} else { } else {
cell.getTextBlocks().forEach(tb -> DocumentGraphFactory.addParagraphOrHeadline(tableCell, tb, context, emptyList())); cell.getTextBlocks().forEach(tb -> DocumentGraphFactory.addParagraphOrHeadline(tableCell, tb, context, emptyList()));

View File

@ -17,14 +17,14 @@ public class TextBlockFactory {
long textBlockIdx; long textBlockIdx;
public AtomicTextBlock buildAtomicTextBlock(List<TextPositionSequence> sequences, SemanticNode parent, DocumentGraphFactory.Context context, Page page) { public AtomicTextBlock fromContext(List<TextPositionSequence> sequences, SemanticNode parent, DocumentGraphFactory.Context context, Page page) {
Integer numberOnPage = context.getAndIncrementTextBlockNumberOnPage(page); Integer numberOnPage = context.getAndIncrementTextBlockNumberOnPage(page);
return buildAtomicTextBlock(sequences, parent, numberOnPage, page); return fromNumberOnPage(sequences, parent, numberOnPage, page);
} }
public AtomicTextBlock buildAtomicTextBlock(List<TextPositionSequence> sequences, SemanticNode parent, Integer numberOnPage, Page page) { public AtomicTextBlock fromNumberOnPage(List<TextPositionSequence> sequences, SemanticNode parent, Integer numberOnPage, Page page) {
SearchTextWithTextPositionDto searchTextWithTextPositionDto = SearchTextWithTextPositionFactory.buildSearchTextToTextPositionModel(sequences); SearchTextWithTextPositionDto searchTextWithTextPositionDto = SearchTextWithTextPositionFactory.buildSearchTextToTextPositionModel(sequences);
int offset = stringOffset; int offset = stringOffset;

View File

@ -4,18 +4,42 @@ import java.util.Collections;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
public record MatchedRule(RuleIdentifier ruleIdentifier, String reason, String legalBasis, boolean applied, Set<RedactionEntity> references) implements Comparable<MatchedRule> { import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@Builder
@AllArgsConstructor
@EqualsAndHashCode
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public final class MatchedRule implements Comparable<MatchedRule> {
@Builder.Default
RuleIdentifier ruleIdentifier = RuleIdentifier.empty();
@Builder.Default
String reason = "";
@Builder.Default
String legalBasis = "";
boolean applied;
boolean writeValueWithLineBreaks;
@Builder.Default
Set<RedactionEntity> references = Collections.emptySet();
public static MatchedRule empty() { public static MatchedRule empty() {
return new MatchedRule(RuleIdentifier.empty(), "", "", false, Collections.emptySet()); return MatchedRule.builder().build();
} }
@Override @Override
public int compareTo(MatchedRule matchedRule) { public int compareTo(MatchedRule matchedRule) {
RuleIdentifier otherRuleIdentifier = matchedRule.ruleIdentifier(); RuleIdentifier otherRuleIdentifier = matchedRule.getRuleIdentifier();
if (!Objects.equals(ruleIdentifier.type(), otherRuleIdentifier.type())) { if (!Objects.equals(ruleIdentifier.type(), otherRuleIdentifier.type())) {
if (Objects.equals(otherRuleIdentifier.type(), "MAN")) { if (Objects.equals(otherRuleIdentifier.type(), "MAN")) {
return 1; return 1;
@ -24,10 +48,17 @@ public record MatchedRule(RuleIdentifier ruleIdentifier, String reason, String l
return -1; return -1;
} }
} }
if (!Objects.equals(otherRuleIdentifier.unit(), ruleIdentifier().unit())) { if (!Objects.equals(otherRuleIdentifier.unit(), getRuleIdentifier().unit())) {
return otherRuleIdentifier.unit() - ruleIdentifier.unit(); return otherRuleIdentifier.unit() - ruleIdentifier.unit();
} }
return otherRuleIdentifier.id() - ruleIdentifier.id(); return otherRuleIdentifier.id() - ruleIdentifier.id();
} }
@Override
public String toString() {
return "MatchedRule[" + "ruleIdentifier=" + ruleIdentifier + ", " + "reason=" + reason + ", " + "legalBasis=" + legalBasis + ", " + "applied=" + applied + ", " + "writeValueWithLineBreaks=" + writeValueWithLineBreaks + ", " + "references=" + references + ']';
}
} }

View File

@ -0,0 +1,104 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity;
import java.util.Collection;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;
import lombok.NonNull;
public interface MatchedRuleHolder {
PriorityQueue<MatchedRule> getMatchedRuleList();
default boolean isApplied() {
return getMatchedRule().isApplied();
}
default Set<RedactionEntity> getReferences() {
return getMatchedRule().getReferences();
}
default void apply(@NonNull String ruleIdentifier, String reason, @NonNull String legalBasis) {
if (legalBasis.isBlank() || legalBasis.isEmpty()) {
throw new IllegalArgumentException("legal basis cannot be empty when redacting an entity");
}
getMatchedRuleList().add(MatchedRule.builder().ruleIdentifier(RuleIdentifier.fromString(ruleIdentifier)).reason(reason).legalBasis(legalBasis).applied(true).build());
}
default void applyWithLineBreaks(@NonNull String ruleIdentifier, String reason, @NonNull String legalBasis) {
if (legalBasis.isBlank() || legalBasis.isEmpty()) {
throw new IllegalArgumentException("legal basis cannot be empty when redacting an entity");
}
getMatchedRuleList().add(MatchedRule.builder()
.ruleIdentifier(RuleIdentifier.fromString(ruleIdentifier))
.reason(reason)
.legalBasis(legalBasis)
.applied(true)
.writeValueWithLineBreaks(true)
.build());
}
default void applyWithReferences(@NonNull String ruleIdentifier, String reason, @NonNull String legalBasis, Collection<RedactionEntity> references) {
if (legalBasis.isBlank() || legalBasis.isEmpty()) {
throw new IllegalArgumentException("legal basis cannot be empty when redacting an entity");
}
getMatchedRuleList().add(MatchedRule.builder()
.ruleIdentifier(RuleIdentifier.fromString(ruleIdentifier))
.reason(reason)
.legalBasis(legalBasis)
.applied(true)
.references(new HashSet<>(references))
.build());
}
default void skip(@NonNull String ruleIdentifier, String reason) {
getMatchedRuleList().add(MatchedRule.builder().ruleIdentifier(RuleIdentifier.fromString(ruleIdentifier)).reason(reason).build());
}
default void skipWithReferences(@NonNull String ruleIdentifier, String reason, Collection<RedactionEntity> references) {
getMatchedRuleList().add(MatchedRule.builder().ruleIdentifier(RuleIdentifier.fromString(ruleIdentifier)).reason(reason).references(new HashSet<>(references)).build());
}
default void addMatchedRule(MatchedRule matchedRule) {
getMatchedRuleList().add(matchedRule);
}
default void addMatchedRules(Collection<MatchedRule> matchedRules) {
getMatchedRuleList().addAll(matchedRules);
}
default int getMatchedRuleUnit() {
return getMatchedRule().getRuleIdentifier().unit();
}
default MatchedRule getMatchedRule() {
if (getMatchedRuleList().isEmpty()) {
return MatchedRule.empty();
}
return getMatchedRuleList().peek();
}
}

View File

@ -2,7 +2,6 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.e
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
@ -22,7 +21,6 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
@Data @Data
@ -30,7 +28,7 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor @AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class RedactionEntity { public class RedactionEntity implements MatchedRuleHolder {
// initial values // initial values
@EqualsAndHashCode.Include @EqualsAndHashCode.Include
@ -73,18 +71,6 @@ public class RedactionEntity {
} }
public boolean isApplied() {
return getMatchedRule().applied();
}
public Set<RedactionEntity> getReferences() {
return getMatchedRule().references();
}
public boolean occursInNodeOfType(Class<? extends SemanticNode> clazz) { public boolean occursInNodeOfType(Class<? extends SemanticNode> clazz) {
return intersectingNodes.stream().anyMatch(clazz::isInstance); return intersectingNodes.stream().anyMatch(clazz::isInstance);
@ -121,6 +107,12 @@ public class RedactionEntity {
} }
public String getValueWithLineBreaks() {
return getDeepestFullyContainingNode().getTextBlock().subSequenceWithLineBreaks(getBoundary());
}
public void removeFromGraph() { public void removeFromGraph() {
intersectingNodes.forEach(node -> node.getEntities().remove(this)); intersectingNodes.forEach(node -> node.getEntities().remove(this));
@ -133,63 +125,6 @@ public class RedactionEntity {
} }
public void apply(@NonNull String ruleIdentifier, String comment, @NonNull String legalBasis) {
if (legalBasis.isBlank() || legalBasis.isEmpty()) {
throw new IllegalArgumentException("legal basis cannot be empty when redacting an entity");
}
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, legalBasis, true, Collections.emptySet()));
}
public void applyWithReferences(@NonNull String ruleIdentifier, String comment, @NonNull String legalBasis, Collection<RedactionEntity> references) {
if (legalBasis.isBlank() || legalBasis.isEmpty()) {
throw new IllegalArgumentException("legal basis cannot be empty when redacting an entity");
}
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, legalBasis, true, new HashSet<>(references)));
}
public void skip(@NonNull String ruleIdentifier, String comment) {
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, "", false, Collections.emptySet()));
}
public void skipWithReferences(@NonNull String ruleIdentifier, String comment, Collection<RedactionEntity> references) {
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, "", false, new HashSet<>(references)));
}
public void addMatchedRule(MatchedRule matchedRule) {
matchedRuleList.add(matchedRule);
}
public void addMatchedRules(Collection<MatchedRule> matchedRules) {
matchedRuleList.addAll(matchedRules);
}
public int getMatchedRuleUnit() {
return getMatchedRule().ruleIdentifier().unit();
}
public MatchedRule getMatchedRule() {
if (matchedRuleList.isEmpty()) {
return MatchedRule.empty();
}
return matchedRuleList.peek();
}
public List<RedactionPosition> getRedactionPositionsPerPage() { public List<RedactionPosition> getRedactionPositionsPerPage() {
if (redactionPositionsPerPage == null || redactionPositionsPerPage.isEmpty()) { if (redactionPositionsPerPage == null || redactionPositionsPerPage.isEmpty()) {

View File

@ -12,7 +12,6 @@ import java.util.stream.Stream;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.sectionidentifiers.SectionIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;

View File

@ -6,7 +6,6 @@ import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.sectionidentifiers.SectionIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import lombok.AccessLevel; import lombok.AccessLevel;

View File

@ -6,7 +6,6 @@ import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.sectionidentifiers.SectionIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import lombok.AccessLevel; import lombok.AccessLevel;

View File

@ -6,7 +6,6 @@ import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.sectionidentifiers.SectionIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes; package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -12,8 +11,8 @@ import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.MatchedRule; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.MatchedRule;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.MatchedRuleHolder;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RuleIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
@ -23,7 +22,6 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
@Data @Data
@ -31,7 +29,7 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
public class Image implements GenericSemanticNode { public class Image implements GenericSemanticNode, MatchedRuleHolder {
List<Integer> treeId; List<Integer> treeId;
String id; String id;
@ -56,75 +54,6 @@ public class Image implements GenericSemanticNode {
Set<RedactionEntity> entities = new HashSet<>(); Set<RedactionEntity> entities = new HashSet<>();
public void apply(@NonNull String ruleIdentifier, String comment, @NonNull String legalBasis) {
if (legalBasis.isBlank() || legalBasis.isEmpty()) {
throw new IllegalArgumentException("legal basis cannot be empty when redacting an entity");
}
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, legalBasis, true, Collections.emptySet()));
}
public void applyWithReferences(@NonNull String ruleIdentifier, String comment, @NonNull String legalBasis, Collection<RedactionEntity> references) {
if (legalBasis.isBlank() || legalBasis.isEmpty()) {
throw new IllegalArgumentException("legal basis cannot be empty when redacting an entity");
}
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, legalBasis, true, new HashSet<>(references)));
}
public void skip(@NonNull String ruleIdentifier, String comment) {
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, "", false, Collections.emptySet()));
}
public void skipWithReferences(@NonNull String ruleIdentifier, String comment, Collection<RedactionEntity> references) {
matchedRuleList.add(new MatchedRule(RuleIdentifier.fromString(ruleIdentifier), comment, "", false, new HashSet<>(references)));
}
public void addMatchedRule(MatchedRule matchedRule) {
matchedRuleList.add(matchedRule);
}
public void addMatchedRules(Collection<MatchedRule> matchedRules) {
matchedRuleList.addAll(matchedRules);
}
public boolean isApplied() {
return getMatchedRule().applied();
}
public Set<RedactionEntity> getReferences() {
return getMatchedRule().references();
}
public int getMatchedRuleUnit() {
return getMatchedRule().ruleIdentifier().unit();
}
public MatchedRule getMatchedRule() {
if (matchedRuleList.isEmpty()) {
return MatchedRule.empty();
}
return matchedRuleList.peek();
}
@Override @Override
public NodeType getType() { public NodeType getType() {

View File

@ -6,7 +6,6 @@ import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.sectionidentifiers.SectionIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.sectionidentifiers; package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;

View File

@ -13,7 +13,6 @@ import java.util.stream.Stream;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.sectionidentifiers.SectionIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;

View File

@ -9,6 +9,8 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicPositionBlockData; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicPositionBlockData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicTextBlockData; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicTextBlockData;
@ -200,6 +202,38 @@ public class AtomicTextBlock implements TextBlock {
} }
@Override
public String subSequenceWithLineBreaks(Boundary boundary) {
if (boundary.length() == 0 || !getBoundary().contains(boundary)) {
return "";
}
CharSequence subSequence = subSequence(boundary);
Set<Integer> lbInBoundary = lineBreaks.stream().filter(boundary::contains).collect(Collectors.toSet());
if (boundary.end() == getBoundary().end()) {
lbInBoundary.add(getBoundary().length());
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < subSequence.length(); i++) {
char character = subSequence.charAt(i);
if (lbInBoundary.contains(i + 1)) {
// always plus one, due to the linebreaks being an exclusive end index
if (!Character.isWhitespace(character)) {
lbInBoundary.remove(i + 1);
lbInBoundary.add(i + 2);
sb.append(character);
continue;
}
sb.append("\n");
} else {
sb.append(character);
}
}
return sb.toString();
}
private List<Integer> getAllLineBreaksInBoundary(Boundary boundary) { private List<Integer> getAllLineBreaksInBoundary(Boundary boundary) {
return getLineBreaks().stream().map(linebreak -> linebreak + this.boundary.start()).filter(boundary::contains).toList(); return getLineBreaks().stream().map(linebreak -> linebreak + this.boundary.start()).filter(boundary::contains).toList();

View File

@ -172,6 +172,34 @@ public class ConcatenatedTextBlock implements TextBlock {
} }
@Override
public String subSequenceWithLineBreaks(Boundary boundary) {
if (boundary.length() == 0 || !getBoundary().contains(boundary)) {
return "";
}
List<AtomicTextBlock> textBlocks = getAllAtomicTextBlocksPartiallyInStringBoundary(boundary);
if (textBlocks.size() == 1) {
return textBlocks.get(0).subSequenceWithLineBreaks(boundary);
}
StringBuilder sb = new StringBuilder();
AtomicTextBlock firstTextBlock = textBlocks.get(0);
sb.append(firstTextBlock.subSequenceWithLineBreaks(new Boundary(boundary.start(), firstTextBlock.getBoundary().end())));
for (AtomicTextBlock textBlock : textBlocks.subList(1, textBlocks.size() - 1)) {
sb.append(textBlock.searchTextWithLineBreaks());
}
var lastTextBlock = textBlocks.get(textBlocks.size() - 1);
sb.append(lastTextBlock.subSequenceWithLineBreaks(new Boundary(lastTextBlock.getBoundary().start(), boundary.end())));
return sb.toString();
}
private Map<Page, List<Rectangle2D>> mergeEntityPositionsWithSamePageNode(Map<Page, List<Rectangle2D>> map1, Map<Page, List<Rectangle2D>> map2) { private Map<Page, List<Rectangle2D>> mergeEntityPositionsWithSamePageNode(Map<Page, List<Rectangle2D>> map1, Map<Page, List<Rectangle2D>> map2) {
Map<Page, List<Rectangle2D>> mergedMap = new HashMap<>(map1); Map<Page, List<Rectangle2D>> mergedMap = new HashMap<>(map1);

View File

@ -42,9 +42,18 @@ public interface TextBlock extends CharSequence {
Map<Page, List<Rectangle2D>> getPositionsPerPage(Boundary stringBoundary); Map<Page, List<Rectangle2D>> getPositionsPerPage(Boundary stringBoundary);
String subSequenceWithLineBreaks(Boundary boundary);
int numberOfLines(); int numberOfLines();
default String searchTextWithLineBreaks() {
return subSequenceWithLineBreaks(getBoundary());
}
default int indexOf(String searchTerm) { default int indexOf(String searchTerm) {
return indexOf(searchTerm, getBoundary().start()); return indexOf(searchTerm, getBoundary().start());

View File

@ -23,6 +23,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Do
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCell; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCell;
@ -155,16 +156,19 @@ public class EntityCreationService {
.map(boundary -> byBoundary(boundary, type, entityType, node)); .map(boundary -> byBoundary(boundary, type, entityType, node));
} }
public Stream<RedactionEntity> byRegexWithLinebreaks(String regexPattern, String type, EntityType entityType, SemanticNode node) {
return byRegexWithLinebreaks(regexPattern, type, entityType, 0, node); public Stream<RedactionEntity> byRegexWithLineBreaks(String regexPattern, String type, EntityType entityType, SemanticNode node) {
return byRegexWithLineBreaks(regexPattern, type, entityType, 0, node);
} }
public Stream<RedactionEntity> byRegexWithLinebreaksIgnoreCase(String regexPattern, String type, EntityType entityType, SemanticNode node) {
return byRegexWithLinebreaksIgnoreCase(regexPattern, type, entityType, 0, node); public Stream<RedactionEntity> byRegexWithLineBreaksIgnoreCase(String regexPattern, String type, EntityType entityType, SemanticNode node) {
return byRegexWithLineBreaksIgnoreCase(regexPattern, type, entityType, 0, node);
} }
public Stream<RedactionEntity> byRegex(String regexPattern, String type, EntityType entityType, SemanticNode node) { public Stream<RedactionEntity> byRegex(String regexPattern, String type, EntityType entityType, SemanticNode node) {
return byRegex(regexPattern, type, entityType, 0, node); return byRegex(regexPattern, type, entityType, 0, node);
@ -177,17 +181,22 @@ public class EntityCreationService {
} }
public Stream<RedactionEntity> byRegexWithLinebreaks(String regexPattern, String type, EntityType entityType, int group, SemanticNode node) { public Stream<RedactionEntity> byRegexWithLineBreaks(String regexPattern, String type, EntityType entityType, int group, SemanticNode node) {
return RedactionSearchUtility.findBoundariesByRegexWithLinebreaks(regexPattern, group, node.getTextBlock()).stream().map(boundary -> byBoundary(boundary, type, entityType, node)); return RedactionSearchUtility.findBoundariesByRegexWithLineBreaks(regexPattern, group, node.getTextBlock())
.stream()
.map(boundary -> byBoundary(boundary, type, entityType, node));
} }
public Stream<RedactionEntity> byRegexWithLinebreaksIgnoreCase(String regexPattern, String type, EntityType entityType, int group, SemanticNode node) { public Stream<RedactionEntity> byRegexWithLineBreaksIgnoreCase(String regexPattern, String type, EntityType entityType, int group, SemanticNode node) {
return RedactionSearchUtility.findBoundariesByRegexWithLinebreaksIgnoreCase(regexPattern, group, node.getTextBlock()).stream().map(boundary -> byBoundary(boundary, type, entityType, node)); return RedactionSearchUtility.findBoundariesByRegexWithLineBreaksIgnoreCase(regexPattern, group, node.getTextBlock())
.stream()
.map(boundary -> byBoundary(boundary, type, entityType, node));
} }
public Stream<RedactionEntity> byRegex(String regexPattern, String type, EntityType entityType, int group, SemanticNode node) { public Stream<RedactionEntity> byRegex(String regexPattern, String type, EntityType entityType, int group, SemanticNode node) {
return RedactionSearchUtility.findBoundariesByRegex(regexPattern, group, node.getTextBlock()).stream().map(boundary -> byBoundary(boundary, type, entityType, node)); return RedactionSearchUtility.findBoundariesByRegex(regexPattern, group, node.getTextBlock()).stream().map(boundary -> byBoundary(boundary, type, entityType, node));
@ -208,6 +217,12 @@ public class EntityCreationService {
} }
public Stream<RedactionEntity> bySemanticNodeParagraphsOnly(SemanticNode node, String type, EntityType entityType) {
return node.streamAllSubNodesOfType(NodeType.PARAGRAPH).map(semanticNode -> bySemanticNode(semanticNode, type, entityType)).filter(Optional::isPresent).map(Optional::get);
}
public Optional<RedactionEntity> bySemanticNode(SemanticNode node, String type, EntityType entityType) { public Optional<RedactionEntity> bySemanticNode(SemanticNode node, String type, EntityType entityType) {
Boundary boundary = node.getTextBlock().getBoundary(); Boundary boundary = node.getTextBlock().getBoundary();

View File

@ -102,17 +102,17 @@ public class RedactionSearchUtility {
} }
public static List<Boundary> findBoundariesByRegexWithLinebreaks(String regexPattern, int group, TextBlock textBlock) { public static List<Boundary> findBoundariesByRegexWithLineBreaks(String regexPattern, int group, TextBlock textBlock) {
Pattern pattern = Patterns.getCompiledMultilinePattern(regexPattern, false); Pattern pattern = Patterns.getCompiledMultilinePattern(regexPattern, false);
return getBoundariesByPatternWithLinebreaks(textBlock, group, pattern); return getBoundariesByPatternWithLineBreaks(textBlock, group, pattern);
} }
public static List<Boundary> findBoundariesByRegexWithLinebreaksIgnoreCase(String regexPattern, int group, TextBlock textBlock) { public static List<Boundary> findBoundariesByRegexWithLineBreaksIgnoreCase(String regexPattern, int group, TextBlock textBlock) {
Pattern pattern = Patterns.getCompiledMultilinePattern(regexPattern, true); Pattern pattern = Patterns.getCompiledMultilinePattern(regexPattern, true);
return getBoundariesByPatternWithLinebreaks(textBlock, group, pattern); return getBoundariesByPatternWithLineBreaks(textBlock, group, pattern);
} }
@ -134,21 +134,10 @@ public class RedactionSearchUtility {
} }
private static List<Boundary> getBoundariesByPatternWithLinebreaks(TextBlock textBlock, int group, Pattern pattern) { private static List<Boundary> getBoundariesByPatternWithLineBreaks(TextBlock textBlock, int group, Pattern pattern) {
StringBuilder stringBuilder = new StringBuilder(); String searchTextWithLineBreaks = textBlock.searchTextWithLineBreaks();
textBlock.getAtomicTextBlocks().forEach(at -> { Matcher matcher = pattern.matcher(searchTextWithLineBreaks);
if (at.numberOfLines() > 1) {
for (int i = 0; i < at.numberOfLines(); i++) {
stringBuilder.append(at.getLine(i));
stringBuilder.setCharAt(stringBuilder.length() - 1, '\n');
}
} else {
stringBuilder.append(at.getSearchText()).setCharAt(stringBuilder.length() - 1, '\n');
}
});
Matcher matcher = pattern.matcher(stringBuilder.toString());
List<Boundary> boundaries = new LinkedList<>(); List<Boundary> boundaries = new LinkedList<>();
while (matcher.find()) { while (matcher.find()) {
boundaries.add(new Boundary(matcher.start(group) + textBlock.getBoundary().start(), matcher.end(group) + textBlock.getBoundary().start())); boundaries.add(new Boundary(matcher.start(group) + textBlock.getBoundary().start(), matcher.end(group) + textBlock.getBoundary().start()));

View File

@ -85,9 +85,9 @@ public class RedactionLogCreatorService {
return RedactionLogEntry.builder() return RedactionLogEntry.builder()
.color(getColor(entity.getType(), dossierTemplateId, entity.isApplied())) .color(getColor(entity.getType(), dossierTemplateId, entity.isApplied()))
.reason(entity.getMatchedRule().reason()) .reason(entity.getMatchedRule().getReason())
.legalBasis(entity.getMatchedRule().legalBasis()) .legalBasis(entity.getMatchedRule().getLegalBasis())
.value(entity.getValue()) .value(entity.getMatchedRule().isWriteValueWithLineBreaks() ? entity.getValueWithLineBreaks() : entity.getValue())
.type(entity.getType()) .type(entity.getType())
.redacted(entity.isApplied()) .redacted(entity.isApplied())
.isHint(isHint(entity.getType(), dossierTemplateId)) .isHint(isHint(entity.getType(), dossierTemplateId))
@ -95,7 +95,7 @@ public class RedactionLogCreatorService {
.isFalsePositive(entity.getEntityType().equals(EntityType.FALSE_POSITIVE) || entity.getEntityType().equals(EntityType.FALSE_RECOMMENDATION)) .isFalsePositive(entity.getEntityType().equals(EntityType.FALSE_POSITIVE) || entity.getEntityType().equals(EntityType.FALSE_RECOMMENDATION))
.section(entity.getDeepestFullyContainingNode().toString()) .section(entity.getDeepestFullyContainingNode().toString())
.sectionNumber(sectionNumber) .sectionNumber(sectionNumber)
.matchedRule(entity.getMatchedRule().ruleIdentifier().toString()) .matchedRule(entity.getMatchedRule().getRuleIdentifier().toString())
.isDictionaryEntry(entity.isDictionaryEntry()) .isDictionaryEntry(entity.isDictionaryEntry())
.textAfter(entity.getTextAfter()) .textAfter(entity.getTextAfter())
.textBefore(entity.getTextBefore()) .textBefore(entity.getTextBefore())
@ -117,9 +117,9 @@ public class RedactionLogCreatorService {
.isImage(true) .isImage(true)
.type(imageType) .type(imageType)
.redacted(image.isApplied()) .redacted(image.isApplied())
.reason(image.getMatchedRule().reason()) .reason(image.getMatchedRule().getReason())
.legalBasis(image.getMatchedRule().legalBasis()) .legalBasis(image.getMatchedRule().getLegalBasis())
.matchedRule(image.getMatchedRule().ruleIdentifier().toString()) .matchedRule(image.getMatchedRule().getRuleIdentifier().toString())
.isHint(dictionaryService.isHint(image.getImageType().toString(), dossierTemplateId)) .isHint(dictionaryService.isHint(image.getImageType().toString(), dossierTemplateId))
.isDictionaryEntry(false) .isDictionaryEntry(false)
.isRecommendation(false) .isRecommendation(false)

View File

@ -13,7 +13,6 @@ import java.util.Set;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -35,7 +34,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest; import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest;
import com.iqser.red.service.redaction.v1.server.annotate.AnnotateRequest; import com.iqser.red.service.redaction.v1.server.annotate.AnnotateRequest;
import com.iqser.red.service.redaction.v1.server.annotate.AnnotateResponse; import com.iqser.red.service.redaction.v1.server.annotate.AnnotateResponse;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
import com.iqser.red.service.redaction.v1.server.redaction.utils.OsUtils; import com.iqser.red.service.redaction.v1.server.redaction.utils.OsUtils;
import com.iqser.red.storage.commons.StorageAutoConfiguration; import com.iqser.red.storage.commons.StorageAutoConfiguration;
import com.iqser.red.storage.commons.service.StorageService; import com.iqser.red.storage.commons.service.StorageService;
@ -48,9 +46,6 @@ public class RedactionAcceptanceTest extends AbstractRedactionIntegrationTest {
private static final String RULES = loadFromClassPath("drools/acceptance_rules.drl"); private static final String RULES = loadFromClassPath("drools/acceptance_rules.drl");
@Autowired
private EntityCreationService entityCreationService;
@Configuration @Configuration
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class}) @EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StorageAutoConfiguration.class)}) @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StorageAutoConfiguration.class)})

View File

@ -20,7 +20,7 @@ public class RedactionEntityTest {
entity.skip("CBI.3.0", ""); entity.skip("CBI.3.0", "");
entity.skip("CBI.4.1", ""); entity.skip("CBI.4.1", "");
entity.skip("CBI.4.0", ""); entity.skip("CBI.4.0", "");
assertThat(entity.getMatchedRule().ruleIdentifier().toString()).isEqualTo("CBI.4.1"); assertThat(entity.getMatchedRule().getRuleIdentifier().toString()).isEqualTo("CBI.4.1");
assertThat(entity.getMatchedRuleUnit()).isEqualTo(4); assertThat(entity.getMatchedRuleUnit()).isEqualTo(4);
} }
@ -34,7 +34,7 @@ public class RedactionEntityTest {
entity.skip("CBI.3.0", ""); entity.skip("CBI.3.0", "");
entity.skip("CBI.4.1", ""); entity.skip("CBI.4.1", "");
entity.skip("CBI.4.0", ""); entity.skip("CBI.4.0", "");
assertThat(entity.getMatchedRule().ruleIdentifier().toString()).isEqualTo("MAN.2.0"); assertThat(entity.getMatchedRule().getRuleIdentifier().toString()).isEqualTo("MAN.2.0");
assertThat(entity.getMatchedRuleUnit()).isEqualTo(2); assertThat(entity.getMatchedRuleUnit()).isEqualTo(2);
} }
@ -59,7 +59,7 @@ public class RedactionEntityTest {
entity.apply("CBI.0.0", "", ""); entity.apply("CBI.0.0", "", "");
}); });
entity.skip("CBI.2.0", ""); entity.skip("CBI.2.0", "");
assertThat(entity.getMatchedRule().ruleIdentifier().toString()).isEqualTo("CBI.2.0"); assertThat(entity.getMatchedRule().getRuleIdentifier().toString()).isEqualTo("CBI.2.0");
assertThat(entity.getMatchedRuleUnit()).isEqualTo(2); assertThat(entity.getMatchedRuleUnit()).isEqualTo(2);
} }

View File

@ -138,7 +138,7 @@ public class ManualResizeRedactionIntegrationTest extends BuildDocumentIntegrati
assertFalse(entity.getIntersectingNodes().isEmpty()); assertFalse(entity.getIntersectingNodes().isEmpty());
assertEquals(1, entity.getPages().size()); assertEquals(1, entity.getPages().size());
assertEquals("David Ksenia", entity.getValue()); assertEquals("David Ksenia", entity.getValue());
assertEquals("Something", entity.getMatchedRule().legalBasis()); assertEquals("Something", entity.getMatchedRule().getLegalBasis());
assertEquals(initialId, entity.getRedactionPositionsPerPage().get(0).getId()); assertEquals(initialId, entity.getRedactionPositionsPerPage().get(0).getId());
assertFalse(entity.isRemoved()); assertFalse(entity.isRemoved());
assertTrue(entity.isSkipRemoveEntitiesContainedInLarger()); assertTrue(entity.isSkipRemoveEntitiesContainedInLarger());

View File

@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionIdentifier;
class SectionIdentifierTest { class SectionIdentifierTest {
@Test @Test

View File

@ -0,0 +1,93 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
class AtomicTextBlockTest {
@Test
void subSequenceWithLineBreaks1() {
String searchText = "1234 6789 ";
var atb = AtomicTextBlock.builder().searchText(searchText).lineBreaks(List.of(5, 7)).boundary(new Boundary(0, searchText.length())).build();
String searchTextWithLineBreaks = atb.searchTextWithLineBreaks();
assertEquals("1234\n6789\n", searchTextWithLineBreaks);
}
@Test
void subSequenceWithLineBreaks2() {
String searchText = "1234 6789 ";
var atb = AtomicTextBlock.builder().searchText(searchText).lineBreaks(List.of(5, 7, 8, 9)).boundary(new Boundary(0, searchText.length())).build();
String searchTextWithLineBreaks = atb.searchTextWithLineBreaks();
assertEquals("1234\n6789\n", searchTextWithLineBreaks);
}
@Test
void subSequenceWithLineBreaks3() {
String searchText = "1234 6789 1234 ";
var atb = AtomicTextBlock.builder().searchText(searchText).lineBreaks(List.of(5, 7)).boundary(new Boundary(0, searchText.length())).build();
String searchTextWithLineBreaks = atb.searchTextWithLineBreaks();
assertEquals("1234\n6789\n1234\n", searchTextWithLineBreaks);
}
@Test
void subSequenceWithLineBreaks4() {
String searchText = "1234 6789 1234 ";
var atb = AtomicTextBlock.builder().searchText(searchText).lineBreaks(List.of(5, 7)).boundary(new Boundary(0, searchText.length())).build();
var textBlock = new ConcatenatedTextBlock(List.of(atb));
String searchTextWithLineBreaks = textBlock.searchTextWithLineBreaks();
assertEquals("1234\n6789\n1234\n", searchTextWithLineBreaks);
}
@Test
void subSequenceWithLineBreaks5() {
String searchText1 = "1234 6789 ";
String searchText2 = "1234 ";
var atb1 = AtomicTextBlock.builder().searchText(searchText1).lineBreaks(List.of(5, 7)).boundary(new Boundary(0, searchText1.length())).build();
var atb2 = AtomicTextBlock.builder()
.searchText(searchText2)
.lineBreaks(List.of())
.boundary(new Boundary(searchText1.length(), searchText1.length() + searchText2.length()))
.build();
var textBlock = new ConcatenatedTextBlock(List.of(atb1, atb2));
String searchTextWithLineBreaks = textBlock.searchTextWithLineBreaks();
assertEquals("1234\n6789\n1234\n", searchTextWithLineBreaks);
}
@Test
void subSequenceWithLineBreaks6() {
String searchText1 = "1234 6789 ";
String searchText2 = "1234 ";
String searchText3 = "1234 8475678900 ";
var atb1 = AtomicTextBlock.builder().searchText(searchText1).lineBreaks(List.of(5, 7)).boundary(new Boundary(0, searchText1.length())).build();
var atb2 = AtomicTextBlock.builder()
.searchText(searchText2)
.lineBreaks(List.of())
.boundary(new Boundary(searchText1.length(), searchText1.length() + searchText2.length()))
.build();
var atb3 = AtomicTextBlock.builder()
.searchText(searchText3)
.lineBreaks(List.of(atb2.getBoundary().end() + 6))
.boundary(new Boundary(atb2.getBoundary().end(), atb2.getBoundary().end() + searchText3.length()))
.build();
var textBlock = new ConcatenatedTextBlock(List.of(atb1, atb2, atb3));
String searchTextWithLineBreaks = textBlock.searchTextWithLineBreaks();
assertEquals("1234\n6789\n1234\n1234 8475678900\n", searchTextWithLineBreaks);
}
}

View File

@ -667,6 +667,7 @@ rule "X.1.0: merge intersecting Entities of same type"
retract($first); retract($first);
retract($second); retract($second);
insert(mergedEntity); insert(mergedEntity);
mergedEntity.getIntersectingNodes().forEach(node -> update(node));
end end
@ -677,6 +678,7 @@ rule "X.2.0: remove Entity of type ENTITY when contained by FALSE_POSITIVE"
$falsePositive: RedactionEntity($type: type, entityType == EntityType.FALSE_POSITIVE) $falsePositive: RedactionEntity($type: type, entityType == EntityType.FALSE_POSITIVE)
$entity: RedactionEntity(containedBy($falsePositive), type == $type, entityType == EntityType.ENTITY, !resized, !skipRemoveEntitiesContainedInLarger) $entity: RedactionEntity(containedBy($falsePositive), type == $type, entityType == EntityType.ENTITY, !resized, !skipRemoveEntitiesContainedInLarger)
then then
$entity.getIntersectingNodes().forEach(node -> update(node));
$entity.removeFromGraph(); $entity.removeFromGraph();
retract($entity) retract($entity)
end end
@ -726,6 +728,7 @@ rule "X.6.0: remove Entity of lower rank, when intersected by entity of type ENT
$higherRank: RedactionEntity($type: type, entityType == EntityType.ENTITY) $higherRank: RedactionEntity($type: type, entityType == EntityType.ENTITY)
$lowerRank: RedactionEntity(intersects($higherRank), type != $type, dictionary.getDictionaryRank(type) < dictionary.getDictionaryRank($type), !resized, !skipRemoveEntitiesContainedInLarger) $lowerRank: RedactionEntity(intersects($higherRank), type != $type, dictionary.getDictionaryRank(type) < dictionary.getDictionaryRank($type), !resized, !skipRemoveEntitiesContainedInLarger)
then then
$lowerRank.getIntersectingNodes().forEach(node -> update(node));
$lowerRank.removeFromGraph(); $lowerRank.removeFromGraph();
retract($lowerRank); retract($lowerRank);
end end

View File

@ -14,6 +14,11 @@ import java.util.Optional;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.*; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.*;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.*; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.*;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionIdentifier;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Table;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Paragraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Headline;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.*; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.*;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.*; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.*;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
@ -308,7 +313,18 @@ rule "DOC.5.0: Strain"
entity.apply("DOC.5.0", "Strain found.", "n-a"); entity.apply("DOC.5.0", "Strain found.", "n-a");
}); });
end end
rule "DOC.6.0"
when
Headline(containsStringIgnoreCase("materials and methods"), $sectionIdentifierMaterials: getSectionIdentifier())
Headline(containsStringIgnoreCase("controls"), getSectionIdentifier().isChildOf($sectionIdentifierMaterials), $sectionIdentifierControls: getSectionIdentifier())
$headline: Headline(containsStringIgnoreCase("positive control substances"), getSectionIdentifier().isChildOf($sectionIdentifierControls))
then
System.out.println($headline);
entityCreationService.bySemanticNodeParagraphsOnly($headline.getParent(), "irgendwas", EntityType.ENTITY)
.forEach(entity -> {
entity.apply("DOC.6.0", "positive control substance found", "n-a");
});
end
//rule "DOC.7.0: study title by document structure" //rule "DOC.7.0: study title by document structure"
// when // when
@ -328,7 +344,7 @@ rule "DOC.7.0: study title"
when when
$section: Section(isOnPage(1) && (containsString("Final Report") || containsString("SPL"))) $section: Section(isOnPage(1) && (containsString("Final Report") || containsString("SPL")))
then then
entityCreationService.byRegexWithLinebreaks("(?<=\\n)[\\w\\W]{1,300}(?=\\nFinal Report)", "title", EntityType.ENTITY, $section).findFirst().ifPresent(entity -> { entityCreationService.byRegexWithLineBreaks("(?<=\\n)[\\w\\W]{1,300}(?=\\nFinal Report)", "title", EntityType.ENTITY, $section).findFirst().ifPresent(entity -> {
entity.apply("DOC.7.0", "Title found", "n-a"); entity.apply("DOC.7.0", "Title found", "n-a");
}); });
entityCreationService.betweenStrings("TITLE", "DATA REQUIREMENT", "title", EntityType.ENTITY, $section).findFirst().ifPresent(entity -> { entityCreationService.betweenStrings("TITLE", "DATA REQUIREMENT", "title", EntityType.ENTITY, $section).findFirst().ifPresent(entity -> {
@ -591,7 +607,7 @@ rule "DOC.14.0: Dosages"
entityCreationService.betweenStrings("Animals were treated", ".", "dosages", EntityType.ENTITY, $section).forEach(entity -> { entityCreationService.betweenStrings("Animals were treated", ".", "dosages", EntityType.ENTITY, $section).forEach(entity -> {
entity.apply("DOC.14.0", "Dosage found", "n-a"); entity.apply("DOC.14.0", "Dosage found", "n-a");
}); });
entityCreationService.byRegexWithLinebreaks("(?:\\.[\\s|\\n]|^.{5,20}\\n)([^\\.]{1,200}(?:animal|given|received)[^\\.]{1,200}dose\\s(?:levels?\\s)?(?:of|at)[^\\.]{1,200})(?:\\.[\\s|\\n|$])", "dosages", EntityType.ENTITY,1, $section).forEach(entity -> { entityCreationService.byRegexWithLineBreaks("(?:\\.[\\s|\\n]|^.{5,20}\\n)([^\\.]{1,200}(?:animal|given|received)[^\\.]{1,200}dose\\s(?:levels?\\s)?(?:of|at)[^\\.]{1,200})(?:\\.[\\s|\\n|$])", "dosages", EntityType.ENTITY,1, $section).forEach(entity -> {
entity.apply("DOC.14.0", "Dosage found", "n-a"); entity.apply("DOC.14.0", "Dosage found", "n-a");
}); });
end end