Compare commits

..
Author SHA1 Message Date
Thierry Goeckel 564d74e39d Pull request #40: Test modified rules 8 and 10
Merge in RED/redaction-service from RED-288 to master

* commit 'cfb7554c618b9482f529b8183bc0f1f95df7d00f':
  Test modified rules 8 and 9
2020-09-23 11:44:35 +02:00
Thierry Göckel cfb7554c61 Test modified rules 8 and 9 2020-09-22 21:36:05 +02:00
Christoph  Schabert ee40064b32 Pull request #39: RED-316: Switch to new red base image
Merge in RED/redaction-service from RED-316 to master

* commit 'f2fafc3db2ab8825eb4a2136e8eab28a12dbc2e6':
  Switch to rootless base-image
  Upgrade bamboo-specs to 7.1.2
2020-09-16 08:59:45 +02:00
Christoph  Schabert f2fafc3db2 Switch to rootless base-image 2020-09-15 16:02:08 +02:00
Christoph  Schabert d5b0638f11 Upgrade bamboo-specs to 7.1.2 2020-09-15 16:01:26 +02:00
Dominique Eiflaender 130cac0b77 Pull request #38: RED-293: Add unkown Textblocks to paragraphs
Merge in RED/redaction-service from RED-293 to master

* commit 'de310e8a654b601cb4bf258675904a25d4233783':
  RED-293: Add unkown Textblocks to paragraphs
2020-09-14 14:39:36 +02:00
deiflaender de310e8a65 RED-293: Add unkown Textblocks to paragraphs 2020-09-14 13:52:16 +02:00
Dominique Eiflaender a0a78440d8 Pull request #37: RED-305: Added ids to manual redaction entries, RED-306: set redacet to false in redactionLog for manual deletions
Merge in RED/redaction-service from RED-305 to master

* commit 'e52a54b0a3eb8491ed3e79b3f514364a5cf22f76':
  RED-305: Added ids to manual redaction entries, RED-306: set redacet to false in redactionLog for manual deletions
2020-09-11 11:18:42 +02:00
deiflaender e52a54b0a3 RED-305: Added ids to manual redaction entries, RED-306: set redacet to false in redactionLog for manual deletions 2020-09-11 11:10:10 +02:00
deiflaender 65a29fb3a5 Added new files and missing dictionary entries 2020-09-07 16:06:25 +02:00
Dominique Eiflaender ff0a73c635 Pull request #36: Added filename to redactionLog
Merge in RED/redaction-service from filename to master

* commit 'c27c9b2f5902d66f654982d7e5f005ecc176e01b':
  Added missing constuctor for RedactionLog
  Added filename to redactionLog
2020-09-02 15:22:34 +02:00
deiflaender c27c9b2f59 Added missing constuctor for RedactionLog 2020-09-02 14:56:48 +02:00
deiflaender d95ff5e5cf Added filename to redactionLog 2020-09-02 14:47:58 +02:00
Thierry Goeckel 34e058c4e4 Pull request #35: Fix redaction in single cell tables
Merge in RED/redaction-service from RED-279 to master

* commit '3d5455d7297e5805ad30a578d00d6d1737820c7d':
  Fix redaction in single cell tables
2020-09-02 14:39:08 +02:00
Thierry Göckel 3d5455d729 Fix redaction in single cell tables 2020-09-02 14:31:38 +02:00
Thierry Goeckel b07ebf78d2 Pull request #34: RED-267: Return dictionary and rules version in redactionLog
Merge in RED/redaction-service from RED-267 to master

* commit 'b5fa771c285554132edc32e1df59836fd7748b9a':
  RED-267: Return dictionary and rules version in redactionLog
2020-09-02 08:54:43 +02:00
deiflaender b5fa771c28 RED-267: Return dictionary and rules version in redactionLog 2020-09-02 08:47:05 +02:00
Thierry Goeckel 81ea2e91ef Pull request #33: Fix entity span in table rows and detection of headers in rotated tables
Merge in RED/redaction-service from bugfix/rowspan-and-header-in-rotated-table-fix to master

* commit '4954aafed78e06484531d6264bdf215176ee0ef2':
  Reduce log level.
  Remove unused import
  Adjust test to added rule and fix vertical header propagation for row > 2
  Fix entity span in table rows and detection of headers in rotated tables
2020-08-25 17:30:57 +02:00
Thierry Göckel 4954aafed7 Reduce log level. 2020-08-25 17:21:56 +02:00
Thierry Göckel 0baf48698d Remove unused import 2020-08-25 15:45:24 +02:00
Thierry Göckel 3c590dcf1d Adjust test to added rule and fix vertical header propagation for row >
2
2020-08-25 15:41:44 +02:00
Thierry Göckel 848c506c3f Fix entity span in table rows and detection of headers in rotated tables 2020-08-25 13:50:34 +02:00
Thierry Goeckel 6483e637c6 Pull request #32: Remove '-' in headernames
Merge in RED/redaction-service from tablesHeader3 to master

* commit '272c7cc2287b146664b8bb277c3731262ac78a19':
  Remove '-' in headernames
2020-08-24 16:15:44 +02:00
deiflaender 272c7cc228 Remove '-' in headernames 2020-08-24 15:39:50 +02:00
Thierry Goeckel 5586531c33 Pull request #31: Made rules for table more stabil and flexible
Merge in RED/redaction-service from tablesRules2 to master

* commit '559c42154276ea861c09cf29b71d9bb9c17e6e87':
  Made rules for table more stabil and flexible
2020-08-24 14:52:29 +02:00
deiflaender 559c421542 Made rules for table more stabil and flexible 2020-08-24 14:45:30 +02:00
Dominique Eiflaender d026f4e1db Pull request #30: Fix test and logic of same page table merge for one row tables
Merge in RED/redaction-service from bugfix/fix-test-table-merge to master

* commit '38ddb1d9c8dbf0e83d40ec916f1ebeb6d0f86f42':
  Fix test and logic of same page table merge for one row tables
2020-08-24 14:22:41 +02:00
Thierry Göckel 38ddb1d9c8 Fix test and logic of same page table merge for one row tables 2020-08-24 14:06:34 +02:00
Thierry Goeckel 92f0af9d21 Pull request #28: Highlight N in vertebrate study tables and fixed rule order
Merge in RED/redaction-service from tableRules to master

* commit 'afa26895c8e78f261ec3700c515f412d8cf605ce':
  Fixed test problem
  Fixed pmd
  Highlight N in vertebrate study tables and fixed rule order
2020-08-24 13:15:11 +02:00
Dominique Eiflaender b24922aa60 Pull request #29: Fix multi-page table merge
Merge in RED/redaction-service from bugfix/multi-page-table-merge to master

* commit 'baa703928f50e53a66f925fcf72082df188482a1':
  Fix multi-page table merge
2020-08-24 13:09:43 +02:00
Thierry Göckel baa703928f Fix multi-page table merge 2020-08-24 13:03:03 +02:00
36 changed files with 16219 additions and 192 deletions
+2 -2
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.atlassian.bamboo</groupId>
<artifactId>bamboo-specs-parent</artifactId>
<version>7.0.4</version>
<version>7.1.2</version>
<relativePath/>
</parent>
@@ -34,4 +34,4 @@
<!-- run 'mvn test' to perform offline validation of the plan -->
<!-- run 'mvn -Ppublish-specs' to upload the plan to your Bamboo server -->
</project>
</project>
@@ -1,4 +1,4 @@
FROM gin5/platform-base:5.2.0
FROM red/base-image:1.0.0
ARG PLATFORM_JAR
@@ -6,4 +6,4 @@ ENV PLATFORM_JAR ${PLATFORM_JAR}
ENV USES_ELASTICSEARCH false
COPY ["${PLATFORM_JAR}", "/"]
COPY ["${PLATFORM_JAR}", "/"]
@@ -11,6 +11,19 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class RedactionLog {
public RedactionLog(List<RedactionLogEntry> redactionLogEntry, long dictionaryVersion, long rulesVersion) {
this.redactionLogEntry = redactionLogEntry;
this.dictionaryVersion = dictionaryVersion;
this.rulesVersion = rulesVersion;
}
private List<RedactionLogEntry> redactionLogEntry;
private long dictionaryVersion = -1;
private long rulesVersion = -1;
private String filename;
}
@@ -84,7 +84,7 @@ public class ClassificationService {
}
else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular() && textBlock.getMostPopularWordStyle().equals("italic") && !document.getFontStyleCounter().getMostPopular().equals("italic") && PositionUtils.getApproxLineCount(textBlock) < 2.9) {
textBlock.setClassification("TextBlock Italic");
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getSequences().size() > 3){
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock)){
textBlock.setClassification("TextBlock Unknown");
}
}
@@ -3,18 +3,21 @@ package com.iqser.red.service.redaction.v1.server.controller;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.model.RedactionRequest;
import com.iqser.red.service.redaction.v1.model.RedactionResult;
import com.iqser.red.service.redaction.v1.resources.RedactionResource;
import com.iqser.red.service.redaction.v1.server.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.exception.RedactionException;
import com.iqser.red.service.redaction.v1.server.redaction.service.DictionaryService;
import com.iqser.red.service.redaction.v1.server.redaction.service.DroolsExecutionService;
import com.iqser.red.service.redaction.v1.server.redaction.service.EntityRedactionService;
import com.iqser.red.service.redaction.v1.server.segmentation.PdfSegmentationService;
@@ -36,6 +39,7 @@ public class RedactionController implements RedactionResource {
private final EntityRedactionService entityRedactionService;
private final PdfFlattenService pdfFlattenService;
private final DroolsExecutionService droolsExecutionService;
private final DictionaryService dictionaryService;
@Override
public RedactionResult redact(@RequestBody RedactionRequest redactionRequest) {
@@ -49,10 +53,10 @@ public class RedactionController implements RedactionResource {
if (redactionRequest.isFlatRedaction()) {
PDDocument flatDocument = pdfFlattenService.flattenPDF(pdDocument);
return convert(flatDocument, classifiedDoc.getPages().size(), new RedactionLog(classifiedDoc.getRedactionLogEntities()));
return convert(flatDocument, classifiedDoc.getPages().size(), classifiedDoc.getRedactionLogEntities());
}
return convert(pdDocument, classifiedDoc.getPages().size(), new RedactionLog(classifiedDoc.getRedactionLogEntities()));
return convert(pdDocument, classifiedDoc.getPages().size(), classifiedDoc.getRedactionLogEntities());
} catch (IOException e) {
throw new RedactionException(e);
@@ -130,14 +134,14 @@ public class RedactionController implements RedactionResource {
return convert(document, numberOfPages, null);
}
private RedactionResult convert(PDDocument document, int numberOfPages, RedactionLog redactionLog) throws IOException {
private RedactionResult convert(PDDocument document, int numberOfPages, List<RedactionLogEntry> redactionLogEntities) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
document.save(byteArrayOutputStream);
return RedactionResult.builder()
.document(byteArrayOutputStream.toByteArray())
.numberOfPages(numberOfPages)
.redactionLog(redactionLog)
.redactionLog(new RedactionLog(redactionLogEntities, dictionaryService.getDictionaryVersion(), droolsExecutionService.getRulesVersion()))
.build();
}
@@ -0,0 +1,16 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@Value
@RequiredArgsConstructor
public class CellValue {
TextBlock textBlock;
int rowSpanStart;
}
@@ -9,8 +9,6 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@@ -32,24 +30,16 @@ public class Section {
private int sectionNumber;
private Map<String, TextBlock> tabularData;
private Map<String, CellValue> tabularData;
public boolean isVertebrateStudy() {
return tabularData != null
&& (tabularData.containsKey("Vertebrate study Y/N")
&& tabularData.get("Vertebrate study Y/N").getText().equals("Y")
|| tabularData.containsKey("Verte brate study Y/N")
&& tabularData.get("Verte brate study Y/N").getText().equals("Y"));
}
public boolean rowEquals(String headerName, String value){
String cleanHeaderName = headerName.replaceAll("\n", "")
.replaceAll(" ", "")
.replaceAll("-", "");
public boolean isNotVertebrateStudy() {
return tabularData != null
&& (tabularData.containsKey("Vertebrate study Y/N")
&& tabularData.get("Vertebrate study Y/N").getText().equals("N")
|| tabularData.containsKey("Verte brate study Y/N")
&& tabularData.get("Verte brate study Y/N").getText().equals("N"));
return tabularData != null && tabularData.containsKey(cleanHeaderName)
&& tabularData.get(cleanHeaderName).getTextBlock().getText().equals(value);
}
@@ -182,18 +172,25 @@ public class Section {
public void highlightCell(String cellHeader, int ruleNumber, String type) {
TextBlock value = tabularData.get(cellHeader);
String cleanHeaderName = cellHeader.replaceAll("\n", "")
.replaceAll(" ", "")
.replaceAll("-", "");
CellValue value = tabularData.get(cleanHeaderName);
if (value == null) {
log.warn("Could not find any data for {}.", cellHeader);
} else {
Entity entity = new Entity(value.getText(), type, 0, value.getText().length(), headline, sectionNumber);
Entity entity = new Entity(value.getTextBlock()
.getText(), type, value.getRowSpanStart(), value.getRowSpanStart() + value.getTextBlock()
.getText()
.length(), headline, sectionNumber);
entity.setRedaction(false);
entity.setMatchedRule(ruleNumber);
entity.setRedactionReason(cellHeader);
entity.setTargetSequences(value.getSequences()); // Make sure no other cells with same content are highlighted
entity.setTargetSequences(value.getTextBlock().getSequences()); // Make sure no other cells with same content are highlighted
entities.add(entity);
}
}
}
}
@@ -27,6 +27,7 @@ public class DictionaryService {
private final DictionaryClient dictionaryClient;
@Getter
private long dictionaryVersion = -1;
@Getter
@@ -18,6 +18,7 @@ import com.iqser.red.service.redaction.v1.server.client.RulesClient;
import com.iqser.red.service.redaction.v1.server.exception.RulesValidationException;
import com.iqser.red.service.redaction.v1.server.redaction.model.Section;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Service
@@ -29,6 +30,7 @@ public class DroolsExecutionService {
@Autowired
private KieContainer kieContainer;
@Getter
private long rulesVersion = -1;
public Section executeRules(Section section) {
@@ -18,6 +18,7 @@ import com.iqser.red.service.redaction.v1.model.Rectangle;
import com.iqser.red.service.redaction.v1.server.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.classification.model.Paragraph;
import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock;
import com.iqser.red.service.redaction.v1.server.redaction.model.CellValue;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
@@ -51,24 +52,30 @@ public class EntityRedactionService {
List<Table> tables = paragraph.getTables();
for (Table table : tables) {
boolean singleCellTable = table.getRowCount() == 1 && table.getColCount() == 1;
for (List<Cell> row : table.getRows()) {
SearchableText searchableRow = new SearchableText();
Map<String, TextBlock> tabularData = new HashMap<>();
Map<String, CellValue> tabularData = new HashMap<>();
int start = 0;
for (Cell cell : row) {
if (cell.isHeaderCell() || CollectionUtils.isEmpty(cell.getTextBlocks())) {
if (!singleCellTable && cell.isHeaderCell() || CollectionUtils.isEmpty(cell.getTextBlocks())) {
continue;
}
addSectionToManualRedactions(cell.getTextBlocks(), manualRedactions, table.getHeadline(), sectionNumber);
int cellStart = start;
cell.getHeaderCells().forEach(headerCell -> {
String headerName = headerCell.getTextBlocks().get(0).getText()
.replaceAll("\n", " ")
.replaceAll(" ", " ");
tabularData.put(headerName, cell.getTextBlocks().get(0));
StringBuilder headerBuilder = new StringBuilder();
headerCell.getTextBlocks().forEach(textBlock -> headerBuilder.append(textBlock.getText()));
String headerName = headerBuilder.toString()
.replaceAll("\n", "")
.replaceAll(" ", "")
.replaceAll("-", "");
tabularData.put(headerName, new CellValue(cell.getTextBlocks().get(0), cellStart));
});
start = start + cell.getTextBlocks().get(0).toString().length();
for (TextBlock textBlock : cell.getTextBlocks()) {
searchableRow.addAll(textBlock.getSequences());
}
}
Set<Entity> rowEntities = findEntities(searchableRow, table.getHeadline(), sectionNumber);
@@ -138,7 +145,7 @@ public class EntityRedactionService {
private Set<Entity> findEntities(SearchableText searchableText, String headline, int sectionNumber) {
Set<Entity> found = new HashSet<>();
if (StringUtils.isEmpty(searchableText.toString()) && StringUtils.isEmpty(headline)) {
if (StringUtils.isEmpty(searchableText.toString())) {
return found;
}
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
@@ -39,22 +40,27 @@ public class SectionsBuilderService {
current.setPage(page.getPageNumber());
if (prev != null && current.getClassification().startsWith("H ") || !document.isHeadlines()) {
Paragraph chunkBlock = buildTextBlock(chunkWords, lastHeadline, previousTable);
Paragraph chunkBlock = buildTextBlock(chunkWords, lastHeadline);
chunkBlock.setHeadline(lastHeadline);
lastHeadline = current.getText();
if (CollectionUtils.isNotEmpty(chunkBlock.getTables())) {
previousTable = chunkBlock.getTables().get(0);
}
chunkBlockList.add(chunkBlock);
chunkWords = new ArrayList<>();
if (CollectionUtils.isNotEmpty(chunkBlock.getTables())) {
previousTable = chunkBlock.getTables().get(chunkBlock.getTables().size() - 1);
}
}
if (current instanceof Table) {
Table table = (Table) current;
// Distribute header information for subsequent tables
mergeTableMetadata(table, previousTable);
previousTable = table;
}
chunkWords.add(current);
prev = current;
}
}
Paragraph chunkBlock = buildTextBlock(chunkWords, lastHeadline, previousTable);
Paragraph chunkBlock = buildTextBlock(chunkWords, lastHeadline);
chunkBlock.setHeadline(lastHeadline);
chunkBlockList.add(chunkBlock);
@@ -62,7 +68,38 @@ public class SectionsBuilderService {
}
private Paragraph buildTextBlock(List<AbstractTextContainer> wordBlockList, String lastHeadline, Table previousTable) {
private void mergeTableMetadata(Table currentTable, Table previousTable) {
// Distribute header information for subsequent tables
if (previousTable != null && hasInvalidHeaderInformation(currentTable) && hasValidHeaderInformation(previousTable)) {
List<Cell> previousTableNonHeaderRow = getRowWithNonHeaderCells(previousTable);
List<Cell> tableNonHeaderRow = getRowWithNonHeaderCells(currentTable);
// Allow merging of tables if header row is separated from first logical non-header row
if (previousTableNonHeaderRow.isEmpty() && previousTable.getRowCount() == 1 && previousTable.getRows()
.get(0)
.size() == tableNonHeaderRow.size()) {
previousTableNonHeaderRow = previousTable.getRows().get(0).stream().map(cell -> {
Cell fakeCell = new Cell(cell.getPoints()[0], cell.getPoints()[2]);
fakeCell.setHeaderCells(Collections.singletonList(cell));
return fakeCell;
}).collect(Collectors.toList());
}
if (previousTableNonHeaderRow.size() == tableNonHeaderRow.size()) {
for (int i = currentTable.getRowCount() - 1; i >= 0; i--) { // Non header rows are most likely at bottom of table
List<Cell> row = currentTable.getRows().get(i);
if (row.size() == tableNonHeaderRow.size() && row.stream()
.allMatch(cell -> cell.getHeaderCells().isEmpty())) {
for (int j = 0; j < row.size(); j++) {
row.get(j).setHeaderCells(previousTableNonHeaderRow.get(j).getHeaderCells());
}
}
}
}
}
}
private Paragraph buildTextBlock(List<AbstractTextContainer> wordBlockList, String lastHeadline) {
Paragraph paragraph = new Paragraph();
TextBlock textBlock = null;
@@ -85,27 +122,6 @@ public class SectionsBuilderService {
} else {
table.setHeadline("Table in: " + lastHeadline);
}
// Distribute header information for subsequent tables
if (previousTable != null && hasInvalidHeaderInformation(table) && hasValidHeaderInformation(previousTable)) {
List<Cell> previousTableNonHeaderRow = getRowWithNonHeaderCells(previousTable);
List<Cell> tableNonHeaderRow = getRowWithNonHeaderCells(table);
// Allow merging of tables if header row is separated from first logical non-header row
if (previousTableNonHeaderRow.isEmpty() && previousTable.getRowCount() == 1
&& previousTable.getRows().get(0).size() == tableNonHeaderRow.size()) {
previousTableNonHeaderRow = previousTable.getRows().get(0);
}
if (previousTableNonHeaderRow.size() == tableNonHeaderRow.size()) {
for (int i = table.getRows().size() - 1; i >= 0; i--) { // Non header rows are most likely at bottom of table
List<Cell> row = table.getRows().get(i);
if (row.size() == tableNonHeaderRow.size()
&& row.stream().allMatch(cell -> cell.getHeaderCells().isEmpty())) {
for (int j = 0; j < row.size(); j++) {
row.get(j).setHeaderCells(previousTableNonHeaderRow.get(j).getHeaderCells());
}
}
}
}
}
if (textBlock != null && !alreadyAdded) {
paragraph.getPageBlocks().add(textBlock);
@@ -168,7 +184,7 @@ public class SectionsBuilderService {
private List<Cell> getRowWithNonHeaderCells(Table table) {
for (int i = table.getRows().size() - 1; i >= 0; i--) { // Non header rows are most likely at bottom of table
for (int i = table.getRowCount() - 1; i >= 0; i--) { // Non header rows are most likely at bottom of table
List<Cell> row = table.getRows().get(i);
boolean allNonHeader = true;
for (Cell cell : row) {
@@ -29,11 +29,13 @@ public class Table extends AbstractTextContainer {
@Setter
private String headline;
@Getter
private int rowCount;
private int unrotatedRowCount;
@Getter
private int colCount;
private int unrotatedColCount;
private int rowCount = -1;
private int colCount = -1;
private final int rotation;
@@ -65,6 +67,25 @@ public class Table extends AbstractTextContainer {
}
public int getRowCount() {
if (rowCount == -1) {
rowCount = getRows().size();
}
return rowCount;
}
public int getColCount() {
if (colCount == -1) {
colCount = getRows().stream().mapToInt(List::size).max().orElse(0);
}
return colCount;
}
/**
* Detect header cells (either first row or first column):
* Column is marked as header if cell text is bold and row cell text is not bold.
@@ -72,100 +93,54 @@ public class Table extends AbstractTextContainer {
*/
private void computeHeaders() {
if (rows == null) {
rows = computeRows();
}
// A bold cell is a header cell as long as every cell to the left/top is bold, too
cells.forEach((position, cell) -> {
List<Cell> cellsToTheLeft = getCellsToTheLeft(position);
Cell lastHeaderCell = null;
for (Cell leftCell : cellsToTheLeft) {
if (CollectionUtils.isNotEmpty(leftCell.getTextBlocks()) && leftCell.getTextBlocks()
// we move from left to right and top to bottom
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
List<Cell> rowCells = rows.get(rowIndex);
for (int colIndex = 0; colIndex < rowCells.size(); colIndex++) {
Cell cell = rowCells.get(colIndex);
List<Cell> cellsToTheLeft = rowCells.subList(0, colIndex);
Cell lastHeaderCell = null;
for (Cell leftCell : cellsToTheLeft) {
if (leftCell.isHeaderCell()) {
lastHeaderCell = leftCell;
} else {
break;
}
}
if (lastHeaderCell != null) {
cell.getHeaderCells().add(lastHeaderCell);
}
List<Cell> cellsToTheTop = new ArrayList<>();
for (int i = 0; i < rowIndex; i++) {
try {
cellsToTheTop.add(rows.get(i).get(colIndex));
} catch (IndexOutOfBoundsException e) {
log.debug("No cell {} in row {}, ignoring.", colIndex, rowIndex);
}
}
for (Cell topCell : cellsToTheTop) {
if (topCell.isHeaderCell()) {
lastHeaderCell = topCell;
} else {
break;
}
}
if (lastHeaderCell != null) {
cell.getHeaderCells().add(lastHeaderCell);
}
if (CollectionUtils.isNotEmpty(cell.getTextBlocks()) && cell.getTextBlocks()
.get(0)
.getMostPopularWordStyle()
.equals("bold")) {
lastHeaderCell = leftCell;
} else {
break;
cell.setHeaderCell(true);
}
}
if (lastHeaderCell != null) {
cell.getHeaderCells().add(lastHeaderCell);
}
lastHeaderCell = null;
List<Cell> cellsToTheTop = getCellToTheTop(position);
for (Cell topCell : cellsToTheTop) {
if (CollectionUtils.isNotEmpty(topCell.getTextBlocks()) && topCell.getTextBlocks()
.get(0)
.getMostPopularWordStyle()
.equals("bold")) {
lastHeaderCell = topCell;
} else {
break;
}
}
if (lastHeaderCell != null) {
cell.getHeaderCells().add(lastHeaderCell);
}
if (CollectionUtils.isNotEmpty(cell.getTextBlocks()) && cell.getTextBlocks()
.get(0)
.getMostPopularWordStyle()
.equals("bold")) {
cell.setHeaderCell(true);
}
});
}
private List<Cell> getCellsToTheLeft(CellPosition cellPosition) {
List<Cell> result = new ArrayList<>();
if (cellPosition.getCol() == 0) {
return result;
}
int row = cellPosition.getRow();
for (int i = cellPosition.getCol() - 1; i >= 0; i--) {
if (cells.get(new CellPosition(row, i)) != null) {
result.add(cells.get(new CellPosition(row, i)));
} else {
Cell spanningCell = null;
while (spanningCell == null && row >= 0) {
row--;
spanningCell = cells.get(new CellPosition(row, i));
}
if (spanningCell != null) {
result.add(spanningCell);
}
row = cellPosition.getRow();
}
}
Collections.reverse(result);
return result;
}
private List<Cell> getCellToTheTop(CellPosition cellPosition) {
List<Cell> result = new ArrayList<>();
if (cellPosition.getRow() == 0) {
return result;
}
int col = cellPosition.getCol();
for (int i = cellPosition.getRow() - 1; i >= 0; i--) {
if (cells.get(new CellPosition(i, col)) != null) {
result.add(cells.get(new CellPosition(i, col)));
} else {
Cell spanningCell = null;
while (spanningCell == null && col >= 0) {
col--;
spanningCell = cells.get(new CellPosition(i, col));
}
if (spanningCell != null) {
result.add(spanningCell);
}
col = cellPosition.getCol();
}
}
Collections.reverse(result);
return result;
}
@@ -173,9 +148,9 @@ public class Table extends AbstractTextContainer {
List<List<Cell>> rows = new ArrayList<>();
if (rotation == 90) {
for (int i = 0; i < colCount; i++) { // rows
for (int i = 0; i < unrotatedColCount; i++) { // rows
List<Cell> lastRow = new ArrayList<>();
for (int j = rowCount - 1; j >= 0; j--) { // cols
for (int j = unrotatedRowCount - 1; j >= 0; j--) { // cols
Cell cell = cells.get(new CellPosition(j, i));
if (cell != null) {
lastRow.add(cell);
@@ -184,9 +159,9 @@ public class Table extends AbstractTextContainer {
rows.add(lastRow);
}
} else if (rotation == 270) {
for (int i = colCount - 1; i >= 0; i--) { // rows
for (int i = unrotatedColCount - 1; i >= 0; i--) { // rows
List<Cell> lastRow = new ArrayList<>();
for (int j = 0; j < rowCount; j++) { // cols
for (int j = 0; j < unrotatedRowCount; j++) { // cols
Cell cell = cells.get(new CellPosition(i, j));
if (cell != null) {
lastRow.add(cell);
@@ -195,9 +170,9 @@ public class Table extends AbstractTextContainer {
rows.add(lastRow);
}
} else {
for (int i = 0; i < rowCount; i++) {
for (int i = 0; i < unrotatedRowCount; i++) {
List<Cell> lastRow = new ArrayList<>();
for (int j = 0; j < colCount; j++) {
for (int j = 0; j < unrotatedColCount; j++) {
Cell cell = cells.get(new CellPosition(i, j)); // JAVA_8 use getOrDefault()
if (cell != null) {
lastRow.add(cell);
@@ -214,8 +189,8 @@ public class Table extends AbstractTextContainer {
private void add(Cell chunk, int row, int col) {
rowCount = Math.max(rowCount, row + 1);
colCount = Math.max(colCount, col + 1);
unrotatedRowCount = Math.max(unrotatedRowCount, row + 1);
unrotatedColCount = Math.max(unrotatedColCount, col + 1);
CellPosition cp = new CellPosition(row, col);
cells.put(cp, chunk);
@@ -2,7 +2,9 @@ package com.iqser.red.service.redaction.v1.server.visualization.service;
import java.awt.Color;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
@@ -45,22 +47,42 @@ public class AnnotationHighlightService {
public void highlight(PDDocument document, Document classifiedDoc, boolean flatRedaction, ManualRedactions manualRedactions) throws IOException {
Set<Integer> manualRedactionPages = getManualRedactionPages(manualRedactions);
for (int page = 1; page <= document.getNumberOfPages(); page++) {
PDPage pdPage = document.getPage(page - 1);
drawSectionFrames(document, classifiedDoc, flatRedaction, pdPage, page);
if (classifiedDoc.getEntities().get(page) == null) {
continue;
if (classifiedDoc.getEntities().get(page) != null) {
addAnnotations(pdPage, classifiedDoc, flatRedaction, manualRedactions, page);
}
addAnnotations(pdPage, classifiedDoc, flatRedaction, manualRedactions, page);
addManualAnnotations(pdPage, classifiedDoc, manualRedactions, page);
if (manualRedactionPages.contains(page)) {
addManualAnnotations(pdPage, classifiedDoc, manualRedactions, page);
}
}
}
private Set<Integer> getManualRedactionPages(ManualRedactions manualRedactions) {
Set<Integer> manualRedactionPages = new HashSet<>();
if (manualRedactions == null) {
return manualRedactionPages;
}
manualRedactions.getEntriesToAdd().forEach(entry -> {
entry.getPositions().forEach(pos -> {
manualRedactionPages.add(pos.getPage());
});
});
return manualRedactionPages;
}
private void addAnnotations(PDPage pdPage, Document classifiedDoc, boolean flatRedaction, ManualRedactions manualRedactions, int page) throws IOException {
List<PDAnnotation> annotations = pdPage.getAnnotations();
@@ -77,8 +99,11 @@ public class AnnotationHighlightService {
if (manualRedactions != null && manualRedactions.getIdsToRemove()
.contains(entityPositionSequence.getId())) {
String manualOverrideReason = entity.getRedactionReason() + ", removed by manual override";
entity.setRedaction(false);
entity.setRedactionReason(entity.getRedactionReason() + ", removed by manual override");
entity.setRedactionReason(manualOverrideReason);
redactionLogEntry.setRedacted(false);
redactionLogEntry.setReason(manualOverrideReason);
redactionLogEntry.setManual(true);
}
@@ -107,28 +132,34 @@ public class AnnotationHighlightService {
String id = IdBuilder.buildId(manualRedactionEntry);
RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry);
RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry, id);
boolean foundOnPage = false;
for (Rectangle rectangle : manualRedactionEntry.getPositions()) {
if (page != rectangle.getPage()) {
continue;
}
foundOnPage = true;
PDAnnotationTextMarkup highlight = createAnnotation(rectangle, id, createAnnotationContent(manualRedactionEntry), getColor(manualRedactionEntry
.getType()), true);
annotations.add(highlight);
redactionLogEntry.getPositions().add(rectangle);
}
classifiedDoc.getRedactionLogEntities().add(redactionLogEntry);
if (foundOnPage) {
classifiedDoc.getRedactionLogEntities().add(redactionLogEntry);
}
}
}
private RedactionLogEntry createRedactionLogEntry(ManualRedactionEntry manualRedactionEntry) {
private RedactionLogEntry createRedactionLogEntry(ManualRedactionEntry manualRedactionEntry, String id) {
return RedactionLogEntry.builder()
.id(id)
.color(getColor(manualRedactionEntry.getType()))
.reason(manualRedactionEntry.getReason())
.value(manualRedactionEntry.getValue())
@@ -269,7 +269,7 @@ public class RedactionIntegrationTest {
System.out.println("redactionTest");
long start = System.currentTimeMillis();
ClassPathResource pdfFileResource = new ClassPathResource("files/Trinexapac/96 Trinexapac-ethyl_RAR_09_Volume_3CA_B-7_2018-02-23.pdf");
ClassPathResource pdfFileResource = new ClassPathResource("files/new/Thiabendazole DAR Addendum for ED_April_2020.pdf");
RedactionRequest request = RedactionRequest.builder()
.document(IOUtils.toByteArray(pdfFileResource.getInputStream()))
@@ -57,6 +57,7 @@ public class EntityRedactionServiceTest {
private static final String ADDRESS_CODE = "address";
private static final AtomicLong DICTIONARY_VERSION = new AtomicLong();
private static final AtomicLong RULES_VERSION = new AtomicLong();
@MockBean
private DictionaryClient dictionaryClient;
@@ -69,6 +70,9 @@ public class EntityRedactionServiceTest {
@Autowired
private PdfSegmentationService pdfSegmentationService;
@Autowired
private DroolsExecutionService droolsExecutionService;
@TestConfiguration
public static class RedactionIntegrationTestConfiguration {
@@ -130,7 +134,7 @@ public class EntityRedactionServiceTest {
Document classifiedDoc = pdfSegmentationService.parseDocument(pdDocument);
entityRedactionService.processDocument(classifiedDoc, null);
assertThat(classifiedDoc.getEntities()).hasSize(1); // one page
assertThat(classifiedDoc.getEntities().get(1)).hasSize(5); // 4 out of 5 entities recognized on page 1
assertThat(classifiedDoc.getEntities().get(1)).hasSize(7);// 3 names, 1 address, 1 Y and 2 N entities
}
}
@@ -185,7 +189,7 @@ public class EntityRedactionServiceTest {
try (PDDocument pdDocument = PDDocument.load(pdfFileResource.getInputStream())) {
Document classifiedDoc = pdfSegmentationService.parseDocument(pdDocument);
entityRedactionService.processDocument(classifiedDoc, null);
assertThat(classifiedDoc.getEntities()).hasSize(1); // two pages
assertThat(classifiedDoc.getEntities()).hasSize(1); // one page
assertThat(classifiedDoc.getEntities().get(1).stream()
.filter(entity -> entity.getMatchedRule() == 9)
.count()).isEqualTo(10);
@@ -193,6 +197,61 @@ public class EntityRedactionServiceTest {
}
@Test
public void testApplicantInTableRedaction() throws IOException {
String tableRules = "package drools\n" +
"\n" +
"import com.iqser.red.service.redaction.v1.server.redaction.model.Section\n" +
"\n" +
"global Section section\n" +
"rule \"6: Redact contact information if applicant is found\"\n" +
" when\n" +
" eval(section.headlineContainsWord(\"applicant\") || section.getText().contains(\"Applicant\"));\n" +
" then\n" +
" section.redactLineAfter(\"Name:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactBetween(\"Address:\", \"Contact\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Contact point:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Phone:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Fax:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Tel.:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Tel:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"E-mail:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Email:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Contact:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Telephone number:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Fax number:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactLineAfter(\"Telephone:\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactBetween(\"No:\", \"Fax\", \"address\", 6, \"Applicant information was found\");\n" +
" section.redactBetween(\"Contact:\", \"Tel.:\", \"address\", 6, \"Applicant information was found\");\n" +
" end";
when(rulesClient.getVersion()).thenReturn(RULES_VERSION.incrementAndGet());
when(rulesClient.getRules()).thenReturn(new RulesResponse(tableRules));
droolsExecutionService.updateRules();
ClassPathResource pdfFileResource = new ClassPathResource("files/Minimal Examples/Applicant Producer Table.pdf");
when(dictionaryClient.getVersion()).thenReturn(DICTIONARY_VERSION.incrementAndGet());
DictionaryResponse dictionaryResponse = DictionaryResponse.builder()
.entries(new ArrayList<>(ResourceLoader.load("dictionaries/names.txt")))
.build();
when(dictionaryClient.getDictionaryForType(NAME_CODE)).thenReturn(dictionaryResponse);
DictionaryResponse addressResponse = DictionaryResponse.builder()
.entries(new ArrayList<>(ResourceLoader.load("dictionaries/addresses.txt")))
.build();
when(dictionaryClient.getDictionaryForType(ADDRESS_CODE)).thenReturn(addressResponse);
try (PDDocument pdDocument = PDDocument.load(pdfFileResource.getInputStream())) {
Document classifiedDoc = pdfSegmentationService.parseDocument(pdDocument);
entityRedactionService.processDocument(classifiedDoc, null);
assertThat(classifiedDoc.getEntities()).hasSize(1); // one page
assertThat(classifiedDoc.getEntities().get(1).stream()
.filter(entity -> entity.getMatchedRule() == 6)
.count()).isEqualTo(18);
}
}
@Test
public void headerPropagation() throws IOException {
@@ -216,6 +275,52 @@ public class EntityRedactionServiceTest {
assertThat(classifiedDoc.getEntities().get(1).stream().filter(entity -> entity.getMatchedRule() == 9).count()).isEqualTo(8);
assertThat(classifiedDoc.getEntities().get(2).stream().filter(entity -> entity.getMatchedRule() == 9).count()).isEqualTo(4);
}
pdfFileResource = new ClassPathResource("files/Minimal Examples/Header Propagation2.pdf");
dictionaryResponse = DictionaryResponse.builder()
.entries(Arrays.asList("Tribolet, R.", "Muir, G.", "Kühne-Thu, H.", "Close, C."))
.build();
when(dictionaryClient.getVersion()).thenReturn(DICTIONARY_VERSION.incrementAndGet());
when(dictionaryClient.getDictionaryForType(NAME_CODE)).thenReturn(dictionaryResponse);
addressResponse = DictionaryResponse.builder()
.entries(Collections.singletonList("Novartis Crop Protection AG, Basel, Switzerland"))
.build();
when(dictionaryClient.getDictionaryForType(ADDRESS_CODE)).thenReturn(addressResponse);
try (PDDocument pdDocument = PDDocument.load(pdfFileResource.getInputStream())) {
Document classifiedDoc = pdfSegmentationService.parseDocument(pdDocument);
entityRedactionService.processDocument(classifiedDoc, null);
assertThat(classifiedDoc.getEntities()).hasSize(1); // one page
assertThat(classifiedDoc.getEntities().get(1).stream().filter(entity -> entity.getMatchedRule() == 9).count()).isEqualTo(3);
assertThat(classifiedDoc.getEntities().get(1).stream().filter(entity -> entity.getMatchedRule() == 8).count()).isEqualTo(8);
}
}
@Test
public void testNGuideline() throws IOException {
ClassPathResource pdfFileResource = new ClassPathResource("files/Minimal Examples/Empty Tabular Data.pdf");
DictionaryResponse dictionaryResponse = DictionaryResponse.builder()
.entries(Collections.singletonList("Aldershof S."))
.build();
when(dictionaryClient.getVersion()).thenReturn(DICTIONARY_VERSION.incrementAndGet());
when(dictionaryClient.getDictionaryForType(NAME_CODE)).thenReturn(dictionaryResponse);
DictionaryResponse addressResponse = DictionaryResponse.builder()
.entries(Collections.singletonList("Novartis Crop Protection AG, Basel, Switzerland"))
.build();
when(dictionaryClient.getDictionaryForType(ADDRESS_CODE)).thenReturn(addressResponse);
try (PDDocument pdDocument = PDDocument.load(pdfFileResource.getInputStream())) {
Document classifiedDoc = pdfSegmentationService.parseDocument(pdDocument);
entityRedactionService.processDocument(classifiedDoc, null);
assertThat(classifiedDoc.getEntities()).hasSize(1); // one page
assertThat(classifiedDoc.getEntities().get(1).stream().filter(entity -> entity.getMatchedRule() == 8).count()).isEqualTo(6);
}
}
@@ -226,15 +331,24 @@ public class EntityRedactionServiceTest {
"import com.iqser.red.service.redaction.v1.server.redaction.model.Section\n" +
"\n" +
"global Section section\n" +
"rule \"8: Not redacted because Vertebrate Study = N\"\n" +
" when\n" +
" Section(rowEquals(\"Vertebrate study Y/N\", \"N\") || rowEquals(\"Vertebrate study Y/N\", \"No\"))\n" +
" then\n" +
" section.redactNot(\"name\", 8, \"Not redacted because row is not a vertebrate study\");\n" +
" section.redactNot(\"address\", 8, \"Not redacted because row is not a vertebrate study\");\n" +
" section.highlightCell(\"Vertebrate study Y/N\", 8, \"hint_only\");\n" +
" end\n" +
"rule \"9: Redact Authors and Addresses in Reference Table, if it is a Vertebrate study\"\n" +
" when\n" +
" Section(isVertebrateStudy())\n" +
" Section(rowEquals(\"Vertebrate study Y/N\", \"Y\") || rowEquals(\"Vertebrate study Y/N\", " +
"\"Yes\"))\n" +
" then\n" +
" section.redact(\"name\", 9, \"Redacted because row is a vertebrate study\");\n" +
" section.redact(\"address\", 9, \"Redacted because rows is a vertebrate study\");\n" +
" section.redact(\"address\", 9, \"Redacted because row is a vertebrate study\");\n" +
" section.highlightCell(\"Vertebrate study Y/N\", 9, \"must_redact\");\n" +
" end";
when(rulesClient.getVersion()).thenReturn(1L);
when(rulesClient.getVersion()).thenReturn(RULES_VERSION.incrementAndGet());
when(rulesClient.getRules()).thenReturn(new RulesResponse(tableRules));
TypeResponse typeResponse = TypeResponse.builder()
.types(Arrays.asList(
@@ -3,6 +3,7 @@ package com.iqser.red.service.redaction.v1.server.segmentation;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -94,6 +95,46 @@ public class PdfSegmentationServiceTest {
List<List<Cell>> firstTableHeaderCells = firstTable.getRows()
.get(0)
.stream()
.map(Collections::singletonList)
.collect(Collectors.toList());
assertThat(secondTable.getRows().stream()
.allMatch(row -> row.stream()
.map(Cell::getHeaderCells)
.collect(Collectors.toList())
.equals(firstTableHeaderCells)))
.isTrue();
}
}
@Test
public void testMultiPageMetadataPropagation() throws IOException {
ClassPathResource pdfFileResource = new ClassPathResource("files/Minimal Examples/Merge Multi Page Table.pdf");
try (PDDocument pdDocument = PDDocument.load(pdfFileResource.getInputStream())) {
Document document = pdfSegmentationService.parseDocument(pdDocument);
assertThat(document.getParagraphs()
.stream()
.flatMap(paragraph -> paragraph.getTables().stream())
.collect(Collectors.toList())).isNotEmpty();
Table firstTable = document.getParagraphs()
.stream()
.flatMap(paragraph -> paragraph.getTables().stream())
.collect(Collectors.toList())
.get(0);
assertThat(firstTable.getColCount()).isEqualTo(9);
assertThat(firstTable.getRowCount()).isEqualTo(5);
Table secondTable = document.getParagraphs()
.stream()
.flatMap(paragraph -> paragraph.getTables().stream())
.collect(Collectors.toList())
.get(1);
assertThat(secondTable.getColCount()).isEqualTo(9);
assertThat(secondTable.getRowCount()).isEqualTo(6);
List<List<Cell>> firstTableHeaderCells = firstTable.getRows()
.get(firstTable.getRowCount() - 1)
.stream()
.map(Cell::getHeaderCells)
.collect(Collectors.toList());
assertThat(secondTable.getRows().stream()
@@ -105,4 +146,44 @@ public class PdfSegmentationServiceTest {
}
}
@Test
public void testHeaderCellsForRotatedTable() throws IOException {
ClassPathResource pdfFileResource = new ClassPathResource("files/Minimal Examples/Rotated Table Headers.pdf");
try (PDDocument pdDocument = PDDocument.load(pdfFileResource.getInputStream())) {
Document document = pdfSegmentationService.parseDocument(pdDocument);
assertThat(document.getParagraphs()
.stream()
.flatMap(paragraph -> paragraph.getTables().stream())
.collect(Collectors.toList())).isNotEmpty();
Table firstTable = document.getParagraphs()
.stream()
.flatMap(paragraph -> paragraph.getTables().stream())
.collect(Collectors.toList())
.get(0);
assertThat(firstTable.getColCount()).isEqualTo(8);
assertThat(firstTable.getRowCount()).isEqualTo(1);
Table secondTable = document.getParagraphs()
.stream()
.flatMap(paragraph -> paragraph.getTables().stream())
.collect(Collectors.toList())
.get(1);
assertThat(secondTable.getColCount()).isEqualTo(8);
assertThat(secondTable.getRowCount()).isEqualTo(6);
List<List<Cell>> firstTableHeaderCells = firstTable.getRows()
.get(0)
.stream()
.map(Collections::singletonList)
.collect(Collectors.toList());
assertThat(secondTable.getRows().stream()
.allMatch(row -> row.stream()
.map(Cell::getHeaderCells)
.collect(Collectors.toList())
.equals(firstTableHeaderCells)))
.isTrue();
}
}
}
@@ -227,6 +227,7 @@ CIBA-GEIGY Limited, Toxicology Services, Short-term Toxicology, 4332 Stein, Swit
CIBA-GEIGY Ltd. CH-4002 Basle, Switzerland
CIBA-GEIGY Ltd., Product Safety, Ecotoxicology, CH-4002 Basel, Switzerland
CIBA-GEIGY Ltd., Switzerland
CibaGeigy Ltd., CH-4002, Basel, Switzerland
CIP Chemisches Institut Pforzheim GmbH, Pforzheim, Germany
CL PHARMA AG
CL PHARMA AG, Linz, Austria
@@ -1563,4 +1564,36 @@ Zyma SA
Zyma SA, Nyon, Switzerland
“Bayer CropScience AG” Monheim
“Biologische Bundesanstalt (BBA)”, Berlin-Dahlem
“W. Neudorff GmbH KG”, An der Mühle 3, D- 31860 Emmertal
“W. Neudorff GmbH KG”, An der Mühle 3, D- 31860 Emmertal
Syngenta Ltd Jealott’s Hill Int. Research Centre Bracknell, Berkshire, RG42 6EY, United Kingdom
MITOX Consultants Science Park 408, 1098XH Amsterdam, NL
Eurofins Agrosciences Services Eco-Chem GmbH, Eutinger Str. 24, 75233 Niefern-Öschelbronn, Germany
Fraunhofer Institute for Molecular Biology and Applied Ecology (IME), 57392 Schmallenberg, Germany
Syngenta Crop Protection AGRCC Cytotest Cell Research GmbH, Germany
BASF SE, Ludwigshafen, Germany
AGRCC Ltd., Switzerland
AGWIL Research Lab. Inc., USA
Charles River Laboratories, UK
Ciba-Geigy Corporation Environmental Health Centre, Farmington
Syngenta Crop Protection, Inc., Greensboro, United States
Experimental Toxicology and Ecology, BASF SE
Wildlife International, Ltd. 8598 Commerce Drive Easton, MD 21601 USA
Küberich, Wiesentheid / Geesdorf, Germany
CiToxLAB Hungary Ltd. H8200 Veszprém, Szabadságpuszta, Hungary
Forellenzucht Trostadt GbR, Trostadt, Germany
Osage Catfisheries Inc., Osage Beach, MO, USA
Fischzucht Rhönforelle, Gersfeld, Germany
Aquatic Biosystems, Ft. Collins, Colorado, USA
West Aquarium GmbH, 37431 Bad Lauterberg, Germany
Aquatic BioSystems, Fort Collins, Colorado, USA
“U.S. Environmental Research Laboratory”, Duluth, Minnesota, USA
Continuous laboratory cultures
Wildlife International, Ltd. 8598 Commerce Drive Easton, MD 21601 USA
Osage Catfisheries, Inc., Osage Beach, MO, USA
Osage catfisheries, Osage Beach (MO), USA
Ciba-Geigy Corporation, Environmental Health Centre, Farmington
Novartis Crop Protection Inc., Greensboro, United States
Syngenta, Jealott’s Hill International Research Station, UK
Hillandale Farms, Kenansville, Florida, USA
Jealott’s Hill International Research Station, UK
L & C Dairy, Avon Park, Florida, UK
@@ -1,3 +1,3 @@
Batches Produced at
CTL
for determination of residues
determination of residues
@@ -5290,6 +5290,7 @@ Naeb O.
Nagai K.
Nagashima Yoshikazu
Nagata H.|Tanaka T.
Nagra, B
Nagra B.S
Nagra B.|Kingdom N.
Nagy K
@@ -5640,6 +5641,7 @@ Pedersen Carol
Pedersen Carol|DuCharme Darlene
Pedersen C|Ducharme D.
Pederson C.A.
Pederson
Peffer
Peffer R
Peffer R.
@@ -6249,6 +6251,7 @@ Roth M.
Roth Markus
Roth RN|Heilman RD
Roth, M.
Roth
Rotroff
Rouaud, J. L.
Roussel C
@@ -6848,7 +6851,7 @@ Sokolowshi Andrea
Sokolowski A
Sokolowski A.
Sokolowski Andrea
Sokolowski,
Sokolowski
Sole
Sole C
Sole, C.
@@ -7248,6 +7251,7 @@ Thanei, P.
Tharp BE
Thede B.
Theophilidis G
Thevenaz
Thevenaz P
Thevenaz P.
Thevenaz Ph.
@@ -8193,4 +8197,113 @@ Zoriki Hosomi R.
Zoriki Hosomi Rosana
Zuberer D
Zubrod J
Zwicker R.E.
Zwicker R.E.
Ott
Argus et al
Mihail
Clapp
Murfitt R
Yang
Swarbrick
Hertl
Breitwieser
Seville, A
Trumper, C.
Gasso-Brown, D.
Váliczkó, É
Váliczkó, É.
Durando, J.
Tarcai, Z.
Pooles, A
Roig, J.
Schäfers, C.
Nixon, W.
Zok, S.
Durjava M.K. et al
Jatzek J
Salinas E
Sousa J. V
Caferella, M.A.
Salina, E.
Walraven J
Kaspers et al
Schneider et al
Lowrie C
Macdougall J
Roberts S
Madrid, S.O.
Maynard, M.S.
Ray, W.
Ray, W.J.
Váliczkó É
Tarcai Z
Orovecz B
Kaspers
Tarcai Z.
Gizler, A
Whitlow
Johnson et al
Bryden et al
Váliczkó
Höger
Dorgerloh
Schäfers C
Caferella M.A
Salinas, E
Zok S
Tummon OJ
Eckstein CL
Francis PD
Ott M
Richeux F
Rajsekhar PV
Webbley
O’Hagan
MacDonald
Jewkes
Harris
Tomlinson
Tomlinson et al
Nagy
Török-Bathó
Hargitai
Shearer
Robertson
Strepka
Hackford
Blunt
Sieber
Elcombe
Haines
Kang
Sayers
Harris J
Baxter A
Macdonald M
PetusArpasy M
TorokBatho M.
van Ravenzwaa y B
Cords S
Dymarkows ka K.
Bahig M
Pekari K
Bercz J
Russell L
Kitchen K
Miyagawa M.
Sasaki Y
Yin D
Huff J
Blackburn K
Carlson G
Pereira M
Stoner G
Zelenak V
Valiczko E
Tomlinso n J
Trumper C
Senciuc M
Vance C.
Przeorska J.
Melville S.
Wicksted G.
@@ -49,9 +49,9 @@ rule "5: Do not redact in guideline sections"
section.redactNot("address", 5, "Section is a guideline section.");
end
rule "6: Redact contact information, if applicant is found"
rule "6: Redact contact information if applicant is found"
when
eval(section.headlineContainsWord("applicant") || section.getText().contains("Applicant"));
eval(section.headlineContainsWord("applicant") || section.getText().contains("Applicant") || section.headlineContainsWord("Primary contact") || section.headlineContainsWord("Alternative contact"));
then
section.redactLineAfter("Name:", "address", 6, "Applicant information was found");
section.redactBetween("Address:", "Contact", "address", 6, "Applicant information was found");
@@ -62,15 +62,21 @@ rule "6: Redact contact information, if applicant is found"
section.redactLineAfter("Tel:", "address", 6, "Applicant information was found");
section.redactLineAfter("E-mail:", "address", 6, "Applicant information was found");
section.redactLineAfter("Email:", "address", 6, "Applicant information was found");
section.redactLineAfter("e-mail:", "address", 6, "Applicant information was found");
section.redactLineAfter("E-mail address:", "address", 6, "Applicant information was found");
section.redactLineAfter("Contact:", "address", 6, "Applicant information was found");
section.redactLineAfter("Alternative contact:", "address", 6, "Applicant information was found");
section.redactLineAfter("Telephone number:", "address", 6, "Applicant information was found");
section.redactLineAfter("Telephone No:", "address", 6, "Applicant information was found");
section.redactLineAfter("Fax number:", "address", 6, "Applicant information was found");
section.redactLineAfter("Telephone:", "address", 6, "Applicant information was found");
section.redactLineAfter("Company:", "address", 6, "Applicant information was found");
section.redactBetween("No:", "Fax", "address", 6, "Applicant information was found");
section.redactBetween("Contact:", "Tel.:", "address", 6, "Applicant information was found");
end
rule "7: Redact contact information, if Producer is found"
rule "7: Redact contact information if Producer is found"
when
eval(section.getText().toLowerCase().contains("producer of the plant protection") || section.getText().toLowerCase().contains("producer of the active substance") || section.getText().contains("Manufacturer of the active substance") || section.getText().contains("Manufacturer:") || section.getText().contains("Producer or producers of the active substance"));
then
@@ -93,12 +99,11 @@ rule "7: Redact contact information, if Producer is found"
rule "8: Not redacted because Vertebrate Study = N"
when
Section(isNotVertebrateStudy())
Section(rowEquals("Vertebrate study Y/N", "N") || rowEquals("Vertebrate study Y/N", "No"))
then
section.redactNot("name", 8, "Not redacted because row is not a vertebrate study");
section.redactNot("address", 8, "Not redacted because row is not a vertebrate study");
section.highlightCell("Vertebrate study Y/N", 8, "hint_only");
section.highlightCell("Verte brate study Y/N", 8, "hint_only");
end
@@ -110,12 +115,12 @@ rule "9: Redact if must redact entry is found"
section.redact("address", 9, "must_redact entry was found.");
end
rule "10: Redact Authors and Addresses in Reference Table, if it is a Vertebrate study"
rule "10: Redact Authors and Addresses in Reference Table if it is a Vertebrate study"
when
Section(isVertebrateStudy())
Section(rowEquals("Vertebrate study Y/N", "Y") || rowEquals("Vertebrate study Y/N", "Yes"))
then
section.redact("name", 10, "Redacted because row is a vertebrate study");
section.redact("address", 10, "Redacted because row is a vertebrate study");
section.highlightCell("Vertebrate study Y/N", 10, "must_redact");
section.highlightCell("Verte brate study Y/N", 10, "must_redact");
end
end
File diff suppressed because one or more lines are too long