From 81e2d4ee39f629b899e0c53a729e97d50cf05227 Mon Sep 17 00:00:00 2001 From: Kilian Schuettler Date: Thu, 23 Mar 2023 15:05:07 +0100 Subject: [PATCH] RED-6369: Rules Refactor *fixed DocumentData Mapping --- .../document/data/AtomicTextBlockData.java | 3 + .../document/data/TableOfContentsData.java | 42 ++++- .../data/mapper/DocumentDataMapper.java | 62 ++++++- .../data/mapper/DocumentGraphMapper.java | 151 +++++++++++++----- .../data/mapper/PropertiesMapper.java | 2 + .../factory/RectangleTransformations.java | 16 ++ .../graph/factory/TextBlockFactory.java | 3 +- .../document/graph/nodes/SemanticNode.java | 6 + .../textblock/ConcatenatedTextBlock.java | 2 +- 9 files changed, 233 insertions(+), 54 deletions(-) create mode 100644 redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/PropertiesMapper.java diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/AtomicTextBlockData.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/AtomicTextBlockData.java index f9103d2c..e4bb25ec 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/AtomicTextBlockData.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/AtomicTextBlockData.java @@ -11,11 +11,14 @@ import lombok.experimental.FieldDefaults; @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class AtomicTextBlockData { + Long id; + Long page; String searchText; int start; int end; int[] lineBreaks; int[] stringIdxToPositionIdx; float[][] positions; + } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/TableOfContentsData.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/TableOfContentsData.java index fcfd97c9..f842b073 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/TableOfContentsData.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/TableOfContentsData.java @@ -4,6 +4,8 @@ import static java.lang.String.format; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import javax.management.openmbean.InvalidKeyException; @@ -26,7 +28,6 @@ public class TableOfContentsData { public EntryData get(List tocId) { - if (tocId.size() < 1) { throw new InvalidKeyException(format("Section Identifier: \"%s\" is not valid.", tocId)); } @@ -38,14 +39,51 @@ public class TableOfContentsData { } + public Stream streamAllEntries() { + + return entries.stream().flatMap(TableOfContentsData::flatten); + } + + private static List getIds(String idsAsString) { return Arrays.stream(idsAsString.split("\\.")).map(Integer::valueOf).toList(); } + public String toString() { + + return String.join("\n", streamAllEntries().map(EntryData::toString).toList()); + } + + + private static Stream flatten(EntryData entry) { + + return Stream.concat(Stream.of(entry), entry.subEntries().stream().flatMap(TableOfContentsData::flatten)); + } + + @Builder - public record EntryData(int[] tocId, List subEntries, NodeType type, Long[] atomicTextBlocks, Long[] pages, int numberOnPage) { + public record EntryData(int[] tocId, List subEntries, NodeType type, Long[] atomicTextBlocks, Long[] pages, int numberOnPage, Map properties) { + + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i : tocId) { + sb.append(i); + sb.append(","); + } + sb.delete(sb.length() - 1, sb.length()); + sb.append("]: "); + + sb.append(type); + sb.append(" atbs = "); + sb.append(atomicTextBlocks.length); + + return sb.toString(); + } } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentDataMapper.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentDataMapper.java index d1a98092..81323125 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentDataMapper.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentDataMapper.java @@ -1,7 +1,9 @@ package com.iqser.red.service.redaction.v1.server.document.data.mapper; import java.awt.geom.Rectangle2D; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.springframework.stereotype.Service; @@ -11,7 +13,11 @@ import com.iqser.red.service.redaction.v1.server.document.data.PageData; import com.iqser.red.service.redaction.v1.server.document.data.TableOfContentsData; import com.iqser.red.service.redaction.v1.server.document.graph.DocumentGraph; import com.iqser.red.service.redaction.v1.server.document.graph.TableOfContents; +import com.iqser.red.service.redaction.v1.server.document.graph.entity.ImageNode; +import com.iqser.red.service.redaction.v1.server.document.graph.factory.RectangleTransformations; import com.iqser.red.service.redaction.v1.server.document.graph.nodes.PageNode; +import com.iqser.red.service.redaction.v1.server.document.graph.nodes.TableCellNode; +import com.iqser.red.service.redaction.v1.server.document.graph.nodes.TableNode; import com.iqser.red.service.redaction.v1.server.document.graph.textblock.AtomicTextBlock; import com.iqser.red.service.redaction.v1.server.document.graph.textblock.TextBlock; @@ -40,23 +46,68 @@ public class DocumentDataMapper { private TableOfContentsData.EntryData toEntryData(TableOfContents.Entry entry) { Long[] atomicTextBlocks; + if (entry.node().isTerminal()) { atomicTextBlocks = toAtomicTextBlockIds(entry.node().getTerminalTextBlock()); } else { - atomicTextBlocks = new Long[]{-1L}; + atomicTextBlocks = new Long[]{}; } + Map properties = switch (entry.type()) { + case TABLE -> buildTableProperties((TableNode) entry.node()); + case TABLE_CELL -> buildTableCellProperties((TableCellNode) entry.node()); + case IMAGE -> buildImageProperties((ImageNode) entry.node()); + default -> new HashMap<>(); + }; + return TableOfContentsData.EntryData.builder() .tocId(toPrimitiveIntArray(entry.tocId())) .subEntries(entry.children().stream().map(this::toEntryData).toList()) .type(entry.type()) .atomicTextBlocks(atomicTextBlocks) - .pages(entry.node().getPages().stream().map(PageNode::getNumber).toArray(Long[]::new)) + .pages(entry.node().getPages().stream().map(PageNode::getNumber).map(Integer::longValue).toArray(Long[]::new)) .numberOnPage(entry.node().getNumberOnPage()) + .properties(properties) .build(); } + private Map buildImageProperties(ImageNode image) { + + Map properties = new HashMap<>(); + properties.put("imageType", image.getImageType().toString()); + properties.put("transparency", String.valueOf(image.isTransparency())); + properties.put("position", RectangleTransformations.toString(image.getPosition())); + return properties; + } + + + private Map buildTableCellProperties(TableCellNode tableCell) { + + Map properties = new HashMap<>(); + properties.put("row", String.valueOf(tableCell.getRow())); + properties.put("col", String.valueOf(tableCell.getCol())); + properties.put("header", String.valueOf(tableCell.isHeader())); + + if (tableCell.getPages().size() > 1 || tableCell.getBBox().keySet().size() > 1) { + throw new IllegalArgumentException("TableCell can only occur on a single page!"); + } + String bBoxString = RectangleTransformations.toString(tableCell.getBBox().get(tableCell.getPages().stream().findFirst().get())); + properties.put("bBox", bBoxString); + + return properties; + } + + + private Map buildTableProperties(TableNode table) { + + Map properties = new HashMap<>(); + properties.put("numberOfRows", String.valueOf(table.getNumberOfRows())); + properties.put("numberOfCols", String.valueOf(table.getNumberOfCols())); + return properties; + } + + private Long[] toAtomicTextBlockIds(TextBlock textBlock) { return textBlock.getAtomicTextBlocks().stream().map(AtomicTextBlock::getId).toArray(Long[]::new); @@ -65,11 +116,7 @@ public class DocumentDataMapper { private PageData toPageData(PageNode pageNode) { - return PageData.builder() - .height(pageNode.getHeight()) - .width(pageNode.getWidth()) - .number(pageNode.getNumber()) - .build(); + return PageData.builder().height(pageNode.getHeight()).width(pageNode.getWidth()).number(pageNode.getNumber()).build(); } @@ -77,6 +124,7 @@ public class DocumentDataMapper { return AtomicTextBlockData.builder() .id(atomicTextBlock.getId()) + .page(atomicTextBlock.getPage().getNumber().longValue()) .searchText(atomicTextBlock.getSearchText()) .start(atomicTextBlock.getBoundary().start()) .end(atomicTextBlock.getBoundary().end()) diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentGraphMapper.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentGraphMapper.java index 4e607630..32e4a278 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentGraphMapper.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/DocumentGraphMapper.java @@ -1,16 +1,15 @@ package com.iqser.red.service.redaction.v1.server.document.data.mapper; +import static com.iqser.red.service.redaction.v1.server.document.graph.factory.RectangleTransformations.parseRectangle2D; import static java.lang.Math.toIntExact; import static java.lang.String.format; import java.awt.geom.Rectangle2D; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Map; import org.apache.commons.lang3.NotImplementedException; import org.springframework.stereotype.Service; @@ -23,15 +22,22 @@ import com.iqser.red.service.redaction.v1.server.document.data.TableOfContentsDa import com.iqser.red.service.redaction.v1.server.document.graph.Boundary; import com.iqser.red.service.redaction.v1.server.document.graph.DocumentGraph; import com.iqser.red.service.redaction.v1.server.document.graph.TableOfContents; +import com.iqser.red.service.redaction.v1.server.document.graph.entity.ImageNode; +import com.iqser.red.service.redaction.v1.server.document.graph.nodes.FooterNode; +import com.iqser.red.service.redaction.v1.server.document.graph.nodes.HeaderNode; +import com.iqser.red.service.redaction.v1.server.document.graph.nodes.HeadlineNode; import com.iqser.red.service.redaction.v1.server.document.graph.nodes.NodeType; import com.iqser.red.service.redaction.v1.server.document.graph.nodes.PageNode; import com.iqser.red.service.redaction.v1.server.document.graph.nodes.ParagraphNode; import com.iqser.red.service.redaction.v1.server.document.graph.nodes.SectionNode; import com.iqser.red.service.redaction.v1.server.document.graph.nodes.SemanticNode; +import com.iqser.red.service.redaction.v1.server.document.graph.nodes.TableCellNode; +import com.iqser.red.service.redaction.v1.server.document.graph.nodes.TableNode; import com.iqser.red.service.redaction.v1.server.document.graph.textblock.AtomicTextBlock; import com.iqser.red.service.redaction.v1.server.document.graph.textblock.TextBlock; import com.iqser.red.service.redaction.v1.server.document.graph.textblock.TextBlockCollector; import com.iqser.red.service.redaction.v1.server.exception.NotFoundException; +import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType; @Service public class DocumentGraphMapper { @@ -42,7 +48,7 @@ public class DocumentGraphMapper { context.pages.addAll(documentData.getPages().stream().map(this::buildPage).toList()); - buildNodesFromTableOfContents(Collections.emptyList(), context); + context.tableOfContents.setEntries(buildEntries(documentData.getTableOfContents().getEntries(), context)); DocumentGraph documentGraph = DocumentGraph.builder() .numberOfPages(documentData.getPages().size()) @@ -54,67 +60,127 @@ public class DocumentGraphMapper { } - private void buildNodesFromTableOfContents(List currentTocId, Context context) { + private List buildEntries(List entries, Context context) { - List entries; - if (currentTocId.size() == 0) { - entries = context.documentData().getTableOfContents().getEntries(); - } else { - entries = context.documentData().getTableOfContents().get(currentTocId).subEntries(); - } + List newEntries = new LinkedList<>(); for (TableOfContentsData.EntryData entryData : entries) { - switch (entryData.type()) { - case SECTION -> buildSection(entryData, currentTocId, context); - case PARAGRAPH -> buildParagraph(entryData, currentTocId, context); + boolean terminal = isTerminal(entryData); + List pages = Arrays.stream(entryData.pages()).map(pageNumber -> getPage(pageNumber, context)).toList(); + + SemanticNode node = switch (entryData.type()) { + case SECTION -> buildSection(context); + case PARAGRAPH -> buildParagraph(context, terminal); + case HEADLINE -> buildHeadline(context, terminal); + case HEADER -> buildHeader(context, terminal); + case FOOTER -> buildFooter(context, terminal); + case TABLE -> buildTable(context, entryData.properties()); + case TABLE_CELL -> buildTableCell(context, entryData.properties(), terminal); + case IMAGE -> buildImage(context, entryData.properties()); default -> throw new NotImplementedException("Not yet implemented for type " + entryData.type()); + }; + + if (node.isTerminal()) { + TextBlock textBlock = toTextBlock(entryData.atomicTextBlocks(), context, node); + node.setTerminalTextBlock(textBlock); } + List tocId = Arrays.stream(entryData.tocId()).boxed().toList(); + node.setTocId(tocId); + + if (entryData.type() == NodeType.HEADER) { + pages.forEach(page -> page.setHeader((HeaderNode) node)); + } else if (entryData.type() == NodeType.FOOTER) { + pages.forEach(page -> page.setFooter((FooterNode) node)); + } else { + pages.forEach(page -> page.getMainBody().add(node)); + } + newEntries.add(TableOfContents.Entry.builder().tocId(tocId).type(entryData.type()).children(buildEntries(entryData.subEntries(), context)).node(node).build()); } + return newEntries; } - private void buildSection(TableOfContentsData.EntryData entryData, List currentTocId, Context context) { + private HeadlineNode buildHeadline(Context context, boolean terminal) { - SectionNode section = SectionNode.builder().entities(new HashSet<>()).tableOfContents(context.tableOfContents()).build(); - - context.sections().add(section); - - List pages = Arrays.stream(entryData.pages()).map(pageNumber -> getPage(pageNumber, context)).toList(); - pages.forEach(page -> page.getMainBody().add(section)); - section.getPages().addAll(pages); - - List sectionId = context.tableOfContents.createNewChildEntryAndReturnId(currentTocId, NodeType.SECTION, section); - section.setTocId(sectionId); - buildNodesFromTableOfContents(sectionId, context); + return HeadlineNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build(); } - private void buildParagraph(TableOfContentsData.EntryData entryData, List currentTocId, Context context) { + private static boolean isTerminal(TableOfContentsData.EntryData entryData) { - Set pages = Arrays.stream(entryData.pages()).map(pageNumber -> getPage(pageNumber, context)).collect(Collectors.toSet()); + return entryData.atomicTextBlocks().length > 0; + } - SectionNode parentSection = (SectionNode) context.tableOfContents().getEntryById(currentTocId).node(); - ParagraphNode paragraph = ParagraphNode.builder().build(); - TextBlock textBlock = toTextBlock(entryData.atomicTextBlocks(), context, paragraph); - paragraph.setTerminalTextBlock(textBlock); + private ImageNode buildImage(Context context, Map properties) { - pages.forEach(page -> page.getMainBody().add(paragraph)); - for (PageNode page : pages) { - if (!parentSection.getPages().contains(page)) { - parentSection.getPages().add(page); - } - } + ImageType imageType = parseImageType(properties.get("imageType")); + boolean transparency = Boolean.parseBoolean(properties.get("transparency")); + Rectangle2D position = parseRectangle2D(properties.get("position")); - List tocId = context.tableOfContents.createNewChildEntryAndReturnId(currentTocId, NodeType.PARAGRAPH, paragraph); - paragraph.setTocId(tocId); + return ImageNode.builder().tableOfContents(context.tableOfContents()).imageType(imageType).transparency(transparency).position(position).build(); + } + + + private TableCellNode buildTableCell(Context context, Map properties, boolean terminal) { + + int row = Integer.parseInt(properties.get("row")); + int col = Integer.parseInt(properties.get("col")); + boolean header = Boolean.parseBoolean(properties.get("header")); + Rectangle2D bBox = parseRectangle2D(properties.get("bBox")); + return TableCellNode.builder().terminal(terminal).bBox(bBox).header(header).col(col).row(row).tableOfContents(context.tableOfContents()).build(); + } + + + private TableNode buildTable(Context context, Map properties) { + + int numberOfRows = Integer.parseInt(properties.get("numberOfRows")); + int numberOfCols = Integer.parseInt(properties.get("numberOfCols")); + return TableNode.builder().tableOfContents(context.tableOfContents()).numberOfRows(numberOfRows).numberOfCols(numberOfCols).build(); + } + + + private FooterNode buildFooter(Context context, boolean terminal) { + + return FooterNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build(); + } + + + private HeaderNode buildHeader(Context context, boolean terminal) { + + return HeaderNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build(); + } + + + private SectionNode buildSection(Context context) { + + return SectionNode.builder().tableOfContents(context.tableOfContents()).build(); + + } + + + private ParagraphNode buildParagraph(Context context, boolean terminal) { + + return ParagraphNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build(); + } + + + private static ImageType parseImageType(String imageType) { + + return switch (imageType) { + case "LOGO" -> ImageType.LOGO; + case "FORMULA" -> ImageType.FORMULA; + case "SIGNATURE" -> ImageType.SIGNATURE; + case "OCR" -> ImageType.OCR; + default -> ImageType.OTHER; + }; } private TextBlock toTextBlock(Long[] atomicTextBlockIds, Context context, SemanticNode parent) { return Arrays.stream(atomicTextBlockIds) - .map(atomicTextBlockId -> toAtomicTextBlock(context.atomicTextBlockData.get(toIntExact(atomicTextBlockId)), parent)) + .map(atomicTextBlockId -> toAtomicTextBlock(context.atomicTextBlockData.get(toIntExact(atomicTextBlockId)), parent, context)) .collect(new TextBlockCollector()); } @@ -125,10 +191,11 @@ public class DocumentGraphMapper { } - private AtomicTextBlock toAtomicTextBlock(AtomicTextBlockData atomicTextBlockData, SemanticNode parent) { + private AtomicTextBlock toAtomicTextBlock(AtomicTextBlockData atomicTextBlockData, SemanticNode parent, Context context) { return AtomicTextBlock.builder() .id(atomicTextBlockData.getId()) + .page(getPage(atomicTextBlockData.getPage(), context)) .searchText(atomicTextBlockData.getSearchText()) .boundary(new Boundary(atomicTextBlockData.getStart(), atomicTextBlockData.getEnd())) .lineBreaks(Ints.asList(atomicTextBlockData.getLineBreaks())) diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/PropertiesMapper.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/PropertiesMapper.java new file mode 100644 index 00000000..3203a072 --- /dev/null +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/data/mapper/PropertiesMapper.java @@ -0,0 +1,2 @@ +package com.iqser.red.service.redaction.v1.server.document.data.mapper;public class PropertiesMapper { +} diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/RectangleTransformations.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/RectangleTransformations.java index 77f08036..33f4fa64 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/RectangleTransformations.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/RectangleTransformations.java @@ -1,7 +1,10 @@ package com.iqser.red.service.redaction.v1.server.document.graph.factory; +import static java.lang.String.format; + import java.awt.geom.Area; import java.awt.geom.Rectangle2D; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.function.BiConsumer; @@ -60,6 +63,19 @@ public class RectangleTransformations { } + public static String toString(Rectangle2D rectangle2D) { + + return format("%f,%f,%f,%f", rectangle2D.getX(), rectangle2D.getY(), rectangle2D.getWidth(), rectangle2D.getHeight()); + } + + + public static Rectangle2D parseRectangle2D(String bBox) { + + List floats = Arrays.stream(bBox.split(",")).map(Float::parseFloat).toList(); + return new Rectangle2D.Float(floats.get(0), floats.get(1), floats.get(2), floats.get(3)); + } + + private static class Rectangle2DUnion implements Collector { @Override diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/TextBlockFactory.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/TextBlockFactory.java index 0d626763..408ee513 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/TextBlockFactory.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/factory/TextBlockFactory.java @@ -21,7 +21,6 @@ public class TextBlockFactory { stringOffset = new AtomicInteger(); textBlockIdx = new AtomicLong(); - } @@ -64,7 +63,7 @@ public class TextBlockFactory { public AtomicTextBlock emptyTextBlock(SemanticNode parent, Integer numberOnPage, PageNode page) { return AtomicTextBlock.builder() - .id(textBlockIdx.get()) + .id(textBlockIdx.getAndIncrement()) .boundary(new Boundary(stringOffset.get(), stringOffset.get())) .searchText("") .lineBreaks(Collections.emptyList()) diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/nodes/SemanticNode.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/nodes/SemanticNode.java index 3f15a0c5..59e75484 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/nodes/SemanticNode.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/nodes/SemanticNode.java @@ -126,6 +126,12 @@ public interface SemanticNode { } + default void setTerminalTextBlock(TextBlock textBlock) { + + throw new UnsupportedOperationException(); + } + + /** * Each AtomicTextBlock has an index on its page, this returns the number of the first AtomicTextBlock underneath this node. * If this node does not have any AtomicTexBlocks underneath it, e.g. an empty TableCell. It returns -1. diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/textblock/ConcatenatedTextBlock.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/textblock/ConcatenatedTextBlock.java index 8745f271..7e3de968 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/textblock/ConcatenatedTextBlock.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/document/graph/textblock/ConcatenatedTextBlock.java @@ -48,7 +48,7 @@ public class ConcatenatedTextBlock implements TextBlock { boundary.setStart(textBlock.getBoundary().start()); boundary.setEnd(textBlock.getBoundary().end()); } else if (boundary.end() != textBlock.getBoundary().start()) { - throw new UnsupportedOperationException(format("Can only concat consecutive TextBlocks, trying to concat %s to %s", textBlock.getBoundary(), boundary)); + throw new UnsupportedOperationException(format("Can only concat consecutive TextBlocks, trying to concat %s and %s", boundary, textBlock.getBoundary())); } this.atomicTextBlocks.addAll(textBlock.getAtomicTextBlocks()); boundary.setEnd(textBlock.getBoundary().end());