RED-6009 - Document Tree Structure

* renamed terminal to leaf
This commit is contained in:
Kilian Schuettler 2023-05-09 14:32:53 +02:00
parent e31a7b1a2f
commit c151b4a94b
16 changed files with 66 additions and 66 deletions

View File

@ -60,8 +60,8 @@ public class DocumentDataMapper {
Long[] atomicTextBlocks; Long[] atomicTextBlocks;
if (entry.getNode().isTerminal()) { if (entry.getNode().isLeaf()) {
atomicTextBlocks = toAtomicTextBlockIds(entry.getNode().getTerminalTextBlock()); atomicTextBlocks = toAtomicTextBlockIds(entry.getNode().getLeafTextBlock());
} else { } else {
atomicTextBlocks = new Long[]{}; atomicTextBlocks = new Long[]{};
} }

View File

@ -82,9 +82,9 @@ public class DocumentGraphMapper {
default -> throw new UnsupportedOperationException("Not yet implemented for type " + entryData.getType()); default -> throw new UnsupportedOperationException("Not yet implemented for type " + entryData.getType());
}; };
if (node.isTerminal()) { if (node.isLeaf()) {
TextBlock textBlock = toTextBlock(entryData.getAtomicBlocks(), context, node); TextBlock textBlock = toTextBlock(entryData.getAtomicBlocks(), context, node);
node.setTerminalTextBlock(textBlock); node.setLeafTextBlock(textBlock);
} }
List<Integer> tocId = Arrays.stream(entryData.getTreeId()).boxed().toList(); List<Integer> tocId = Arrays.stream(entryData.getTreeId()).boxed().toList();
node.setTreeId(tocId); node.setTreeId(tocId);
@ -104,7 +104,7 @@ public class DocumentGraphMapper {
private Headline buildHeadline(Context context, boolean terminal) { private Headline buildHeadline(Context context, boolean terminal) {
return Headline.builder().terminal(terminal).documentTree(context.documentTree()).build(); return Headline.builder().leaf(terminal).documentTree(context.documentTree()).build();
} }
@ -128,7 +128,7 @@ public class DocumentGraphMapper {
TableCell.TableCellBuilder builder = TableCell.builder(); TableCell.TableCellBuilder builder = TableCell.builder();
PropertiesMapper.parseTableCellProperties(properties, builder); PropertiesMapper.parseTableCellProperties(properties, builder);
return builder.terminal(terminal).documentTree(context.documentTree()).build(); return builder.leaf(terminal).documentTree(context.documentTree()).build();
} }
@ -142,13 +142,13 @@ public class DocumentGraphMapper {
private Footer buildFooter(Context context, boolean terminal) { private Footer buildFooter(Context context, boolean terminal) {
return Footer.builder().terminal(terminal).documentTree(context.documentTree()).build(); return Footer.builder().leaf(terminal).documentTree(context.documentTree()).build();
} }
private Header buildHeader(Context context, boolean terminal) { private Header buildHeader(Context context, boolean terminal) {
return Header.builder().terminal(terminal).documentTree(context.documentTree()).build(); return Header.builder().leaf(terminal).documentTree(context.documentTree()).build();
} }
@ -162,7 +162,7 @@ public class DocumentGraphMapper {
private Paragraph buildParagraph(Context context, boolean terminal) { private Paragraph buildParagraph(Context context, boolean terminal) {
return Paragraph.builder().terminal(terminal).documentTree(context.documentTree()).build(); return Paragraph.builder().leaf(terminal).documentTree(context.documentTree()).build();
} }

View File

@ -94,12 +94,12 @@ public class DocumentGraphFactory {
if (node instanceof Headline headline) { if (node instanceof Headline headline) {
List<Integer> tocId = context.documentTree.createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.HEADLINE, node); List<Integer> tocId = context.documentTree.createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.HEADLINE, node);
headline.setTerminalTextBlock(textBlock); headline.setLeafTextBlock(textBlock);
headline.setTreeId(tocId); headline.setTreeId(tocId);
} }
if (node instanceof Paragraph paragraph) { if (node instanceof Paragraph paragraph) {
List<Integer> tocId = context.documentTree.createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.PARAGRAPH, node); List<Integer> tocId = context.documentTree.createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.PARAGRAPH, node);
paragraph.setTerminalTextBlock(textBlock); paragraph.setLeafTextBlock(textBlock);
paragraph.setTreeId(tocId); paragraph.setTreeId(tocId);
} }
} }
@ -166,7 +166,7 @@ public class DocumentGraphFactory {
page); page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer);
footer.setTreeId(tocId); footer.setTreeId(tocId);
footer.setTerminalTextBlock(textBlock); footer.setLeafTextBlock(textBlock);
page.setFooter(footer); page.setFooter(footer);
} }
@ -182,7 +182,7 @@ public class DocumentGraphFactory {
page); page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header);
header.setTreeId(tocId); header.setTreeId(tocId);
header.setTerminalTextBlock(textBlock); header.setLeafTextBlock(textBlock);
page.setHeader(header); page.setHeader(header);
} }
@ -194,7 +194,7 @@ public class DocumentGraphFactory {
AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(footer, context, page); AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(footer, context, page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer);
footer.setTreeId(tocId); footer.setTreeId(tocId);
footer.setTerminalTextBlock(textBlock); footer.setLeafTextBlock(textBlock);
page.setFooter(footer); page.setFooter(footer);
} }
@ -206,7 +206,7 @@ public class DocumentGraphFactory {
AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(header, 0, page); AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(header, 0, page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header);
header.setTreeId(tocId); header.setTreeId(tocId);
header.setTerminalTextBlock(textBlock); header.setLeafTextBlock(textBlock);
page.setHeader(header); page.setHeader(header);
} }

View File

@ -95,27 +95,27 @@ public class TableNodeFactory {
tableCell.setTreeId(tocId); tableCell.setTreeId(tocId);
if (cell.getTextBlocks().isEmpty()) { if (cell.getTextBlocks().isEmpty()) {
tableCell.setTerminalTextBlock(context.getTextBlockFactory().emptyTextBlock(parentNode, context, page)); tableCell.setLeafTextBlock(context.getTextBlockFactory().emptyTextBlock(parentNode, context, page));
tableCell.setTerminal(true); tableCell.setLeaf(true);
} 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().buildAtomicTextBlock(cell.getTextBlocks().get(0).getSequences(), tableCell, context, page);
tableCell.setTerminalTextBlock(textBlock); tableCell.setLeafTextBlock(textBlock);
tableCell.setTerminal(true); tableCell.setLeaf(true);
} 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);
tableCell.setTerminal(false); tableCell.setLeaf(false);
} 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().buildAtomicTextBlock(sequences, tableCell, context, page);
tableCell.setTerminalTextBlock(textBlock); tableCell.setLeafTextBlock(textBlock);
tableCell.setTerminal(true); tableCell.setLeaf(true);
} else { } else {
cell.getTextBlocks().forEach(tb -> DocumentGraphFactory.addParagraphOrHeadline(tableCell, tb, context, emptyList())); cell.getTextBlocks().forEach(tb -> DocumentGraphFactory.addParagraphOrHeadline(tableCell, tb, context, emptyList()));
tableCell.setTerminal(false); tableCell.setLeaf(false);
} }
} }

View File

@ -36,7 +36,7 @@ public class DocumentTree {
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
return streamAllEntriesInOrder().map(Entry::getNode).filter(SemanticNode::isTerminal).map(SemanticNode::getTerminalTextBlock).collect(new TextBlockCollector()); return streamAllEntriesInOrder().map(Entry::getNode).filter(SemanticNode::isLeaf).map(SemanticNode::getLeafTextBlock).collect(new TextBlockCollector());
} }

View File

@ -52,7 +52,7 @@ public class Document implements SemanticNode {
public Stream<TextBlock> streamTerminalTextBlocksInOrder() { public Stream<TextBlock> streamTerminalTextBlocksInOrder() {
return streamAllNodes().filter(SemanticNode::isTerminal).map(SemanticNode::getTerminalTextBlock); return streamAllNodes().filter(SemanticNode::isLeaf).map(SemanticNode::getLeafTextBlock);
} }

View File

@ -24,10 +24,10 @@ import lombok.experimental.FieldDefaults;
public class Footer implements SemanticNode { public class Footer implements SemanticNode {
List<Integer> treeId; List<Integer> treeId;
TextBlock terminalTextBlock; TextBlock leafTextBlock;
@Builder.Default @Builder.Default
boolean terminal = true; boolean leaf = true;
@EqualsAndHashCode.Exclude @EqualsAndHashCode.Exclude
DocumentTree documentTree; DocumentTree documentTree;
@ -40,14 +40,14 @@ public class Footer implements SemanticNode {
@Override @Override
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
return terminalTextBlock; return leafTextBlock;
} }
@Override @Override
public String toString() { public String toString() {
return treeId + ": " + NodeType.FOOTER + ": " + terminalTextBlock.buildSummary(); return treeId + ": " + NodeType.FOOTER + ": " + leafTextBlock.buildSummary();
} }
} }

View File

@ -24,10 +24,10 @@ import lombok.experimental.FieldDefaults;
public class Header implements SemanticNode { public class Header implements SemanticNode {
List<Integer> treeId; List<Integer> treeId;
TextBlock terminalTextBlock; TextBlock leafTextBlock;
@Builder.Default @Builder.Default
boolean terminal = true; boolean leaf = true;
@EqualsAndHashCode.Exclude @EqualsAndHashCode.Exclude
DocumentTree documentTree; DocumentTree documentTree;
@ -40,14 +40,14 @@ public class Header implements SemanticNode {
@Override @Override
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
return terminalTextBlock; return leafTextBlock;
} }
@Override @Override
public String toString() { public String toString() {
return treeId + ": " + NodeType.HEADER + ": " + terminalTextBlock.buildSummary(); return treeId + ": " + NodeType.HEADER + ": " + leafTextBlock.buildSummary();
} }
} }

View File

@ -24,10 +24,10 @@ import lombok.experimental.FieldDefaults;
public class Headline implements SemanticNode { public class Headline implements SemanticNode {
List<Integer> treeId; List<Integer> treeId;
TextBlock terminalTextBlock; TextBlock leafTextBlock;
@Builder.Default @Builder.Default
boolean terminal = true; boolean leaf = true;
@EqualsAndHashCode.Exclude @EqualsAndHashCode.Exclude
DocumentTree documentTree; DocumentTree documentTree;
@ -40,14 +40,14 @@ public class Headline implements SemanticNode {
@Override @Override
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
return terminalTextBlock; return leafTextBlock;
} }
@Override @Override
public String toString() { public String toString() {
return treeId + ": " + NodeType.HEADLINE + ": " + terminalTextBlock.buildSummary(); return treeId + ": " + NodeType.HEADLINE + ": " + leafTextBlock.buildSummary();
} }

View File

@ -58,7 +58,7 @@ public class Image implements SemanticNode {
@Override @Override
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
return streamAllSubNodes().filter(SemanticNode::isTerminal).map(SemanticNode::getTerminalTextBlock).collect(new TextBlockCollector()); return streamAllSubNodes().filter(SemanticNode::isLeaf).map(SemanticNode::getLeafTextBlock).collect(new TextBlockCollector());
} }

View File

@ -46,7 +46,7 @@ public class Page {
public TextBlock getMainBodyTextBlock() { public TextBlock getMainBodyTextBlock() {
return mainBody.stream().filter(SemanticNode::isTerminal).map(SemanticNode::getTerminalTextBlock).collect(new TextBlockCollector()); return mainBody.stream().filter(SemanticNode::isLeaf).map(SemanticNode::getLeafTextBlock).collect(new TextBlockCollector());
} }

View File

@ -22,10 +22,10 @@ import lombok.experimental.FieldDefaults;
public class Paragraph implements SemanticNode { public class Paragraph implements SemanticNode {
List<Integer> treeId; List<Integer> treeId;
TextBlock terminalTextBlock; TextBlock leafTextBlock;
@Builder.Default @Builder.Default
boolean terminal = true; boolean leaf = true;
@EqualsAndHashCode.Exclude @EqualsAndHashCode.Exclude
DocumentTree documentTree; DocumentTree documentTree;
@ -38,14 +38,14 @@ public class Paragraph implements SemanticNode {
@Override @Override
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
return terminalTextBlock; return leafTextBlock;
} }
@Override @Override
public String toString() { public String toString() {
return treeId + ": " + NodeType.PARAGRAPH + ": " + terminalTextBlock.buildSummary(); return treeId + ": " + NodeType.PARAGRAPH + ": " + leafTextBlock.buildSummary();
} }
} }

View File

@ -40,7 +40,7 @@ public class Section implements SemanticNode {
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
if (textBlock == null) { if (textBlock == null) {
textBlock = streamAllSubNodes().filter(SemanticNode::isTerminal).map(SemanticNode::getTerminalTextBlock).collect(new TextBlockCollector()); textBlock = streamAllSubNodes().filter(SemanticNode::isLeaf).map(SemanticNode::getLeafTextBlock).collect(new TextBlockCollector());
} }
return textBlock; return textBlock;
} }

View File

@ -22,7 +22,7 @@ public interface SemanticNode {
/** /**
* Searches all Nodes located underneath this Node in the DocumentTree and concatenates their AtomicTextBlocks into a single TextBlockEntity. * Searches all Nodes located underneath this Node in the DocumentTree and concatenates their AtomicTextBlocks into a single TextBlockEntity.
* So, for a Section all TextBlocks of Subsections, Paragraphs, and Tables are concatenated into a single TextBlockEntity * So, for a Section all TextBlocks of Subsections, Paragraphs, and Tables are concatenated into a single TextBlockEntity
* If the Node is Terminal, the TerminalTextBlock will be returned instead. * If the Node is a Leaf, the LeafTextBlock will be returned instead.
* *
* @return TextBlock containing all AtomicTextBlocks that are located under this Node. * @return TextBlock containing all AtomicTextBlocks that are located under this Node.
*/ */
@ -131,36 +131,36 @@ public interface SemanticNode {
/** /**
* Terminal means a SemanticNode has direct access to a TextBlock, by default this is false and must be overridden. * Leaf means a SemanticNode has direct access to a TextBlock, by default this is false and must be overridden.
* Currently only Sections, Images, and Tables are not terminal. * Currently only Sections, Images, and Tables are not leaves.
* A TableCell might be Terminal depending on its area compared to the page. * A TableCell might be a leaf depending on its area compared to the page.
* *
* @return boolean, indicating if a Node has direct access to a TextBlock * @return boolean, indicating if a Node has direct access to a TextBlock
*/ */
default boolean isTerminal() { default boolean isLeaf() {
return false; return false;
} }
/** /**
* Terminal means a SemanticNode has direct access to a TextBlock, by default this is false and must be overridden. * Leaf means a SemanticNode has direct access to a TextBlock, by default this is false and must be overridden.
* Currently only Sections and Tables are not terminal. * Currently only Sections and Tables are no leaves.
* *
* @return AtomicTextBlock * @return AtomicTextBlock
*/ */
default TextBlock getTerminalTextBlock() { default TextBlock getLeafTextBlock() {
throw new UnsupportedOperationException("Only terminal Nodes have access to TerminalTextBlocks!"); throw new UnsupportedOperationException("Only leaf Nodes have access to LeafTextBlocks!");
} }
/** /**
* Should only be used during construction of the Graph. Sets the TerminalTextBlock of this SemanticNode. * Should only be used during construction of the Graph. Sets the LeafTextBlock of this SemanticNode.
* *
* @param textBlock the TextBlock to set as the terminalTextBlock of this SemanticNode * @param textBlock the TextBlock to set as the LeafTextBlock of this SemanticNode
*/ */
default void setTerminalTextBlock(TextBlock textBlock) { default void setLeafTextBlock(TextBlock textBlock) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -345,7 +345,7 @@ public interface SemanticNode {
/** /**
* If this Node is Terminal it will calculate the boundingBox of its TerminalTextBlock, otherwise it will calculate the Union of the BoundingBoxes of all its Children. * If this Node is a Leaf it will calculate the boundingBox of its LeafTextBlock, otherwise it will calculate the Union of the BoundingBoxes of all its Children.
* If called on the Document, it will return the cropbox of each page * If called on the Document, it will return the cropbox of each page
* *
* @return Rectangle2D fully encapsulating this Node for each page. * @return Rectangle2D fully encapsulating this Node for each page.
@ -353,8 +353,8 @@ public interface SemanticNode {
default Map<Page, Rectangle2D> getBBox() { default Map<Page, Rectangle2D> getBBox() {
Map<Page, Rectangle2D> bBoxPerPage = new HashMap<>(); Map<Page, Rectangle2D> bBoxPerPage = new HashMap<>();
if (isTerminal()) { if (isLeaf()) {
return getBBoxFromTerminalTextBlock(bBoxPerPage); return getBBoxFromLeafTextBlock(bBoxPerPage);
} }
return getBBoxFromChildren(bBoxPerPage); return getBBoxFromChildren(bBoxPerPage);
@ -397,7 +397,7 @@ public interface SemanticNode {
* @param bBoxPerPage initial empty BoundingBox * @param bBoxPerPage initial empty BoundingBox
* @return The union of all BoundingBoxes of the TextBlock of this node * @return The union of all BoundingBoxes of the TextBlock of this node
*/ */
private Map<Page, Rectangle2D> getBBoxFromTerminalTextBlock(Map<Page, Rectangle2D> bBoxPerPage) { private Map<Page, Rectangle2D> getBBoxFromLeafTextBlock(Map<Page, Rectangle2D> bBoxPerPage) {
Map<Page, List<AtomicTextBlock>> atomicTextBlockPerPage = buildTextBlock().getAtomicTextBlocks().stream().collect(Collectors.groupingBy(AtomicTextBlock::getPage)); Map<Page, List<AtomicTextBlock>> atomicTextBlockPerPage = buildTextBlock().getAtomicTextBlocks().stream().collect(Collectors.groupingBy(AtomicTextBlock::getPage));
atomicTextBlockPerPage.forEach((page, atbs) -> bBoxPerPage.put(page, RectangleTransformations.bBoxUnionAtomicTextBlock(atbs))); atomicTextBlockPerPage.forEach((page, atbs) -> bBoxPerPage.put(page, RectangleTransformations.bBoxUnionAtomicTextBlock(atbs)));

View File

@ -174,7 +174,7 @@ public class Table implements SemanticNode {
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
if (textBlock == null) { if (textBlock == null) {
textBlock = streamAllSubNodes().filter(SemanticNode::isTerminal).map(SemanticNode::getTerminalTextBlock).collect(new TextBlockCollector()); textBlock = streamAllSubNodes().filter(SemanticNode::isLeaf).map(SemanticNode::getLeafTextBlock).collect(new TextBlockCollector());
} }
return textBlock; return textBlock;
} }

View File

@ -34,8 +34,8 @@ public class TableCell implements SemanticNode {
Rectangle2D bBox; Rectangle2D bBox;
@Builder.Default @Builder.Default
boolean terminal = true; boolean leaf = true;
TextBlock terminalTextBlock; TextBlock leafTextBlock;
TextBlock textBlock; TextBlock textBlock;
@ -59,12 +59,12 @@ public class TableCell implements SemanticNode {
@Override @Override
public TextBlock buildTextBlock() { public TextBlock buildTextBlock() {
if (terminal) { if (leaf) {
return terminalTextBlock; return leafTextBlock;
} }
if (textBlock == null) { if (textBlock == null) {
textBlock = streamAllSubNodes().filter(SemanticNode::isTerminal).map(SemanticNode::getTerminalTextBlock).collect(new TextBlockCollector()); textBlock = streamAllSubNodes().filter(SemanticNode::isLeaf).map(SemanticNode::getLeafTextBlock).collect(new TextBlockCollector());
} }
return textBlock; return textBlock;
} }