diff --git a/build.gradle b/build.gradle index d727205a..a0cdea0c 100644 --- a/build.gradle +++ b/build.gradle @@ -46,15 +46,15 @@ launch4j { outfile="Stirling-PDF.exe" headerType="console" jarTask = tasks.bootJar - + errTitle="Encountered error, Do you have Java 17?" - downloadUrl="https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe" + downloadUrl="https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe" variables=["BROWSER_OPEN=true"] jreMinVersion="17" - + mutexName="Stirling-PDF" windowTitle="Stirling-PDF" - + messagesStartupError="An error occurred while starting Stirling-PDF" //messagesJreNotFoundError="This application requires a Java Runtime Environment, Please download Java 17." messagesJreVersionError="You are running the wrong version of Java, Please download Java 17." @@ -63,46 +63,48 @@ launch4j { } dependencies { - implementation 'org.yaml:snakeyaml:2.1' - implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2' - - if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') { + implementation 'org.yaml:snakeyaml:2.1' + implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2' + + if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') { implementation 'org.springframework.boot:spring-boot-starter-security:3.1.2' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE' implementation "org.springframework.boot:spring-boot-starter-data-jpa" - implementation "com.h2database:h2" + implementation "com.h2database:h2" } - - testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.4' - - - // https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio - implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4' - implementation 'commons-io:commons-io:2.13.0' - + testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.4' + + + + // https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio + implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4' + implementation 'commons-io:commons-io:2.13.0' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' - - //general PDF + + //general PDF + + // https://mvnrepository.com/artifact/com.opencsv/opencsv + implementation group: 'com.opencsv', name: 'opencsv', version: '5.7.1' implementation 'org.apache.pdfbox:pdfbox:2.0.29' implementation 'org.apache.pdfbox:xmpbox:2.0.29' implementation 'org.bouncycastle:bcprov-jdk15on:1.70' - implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-core' implementation group: 'com.google.zxing', name: 'core', version: '3.5.2' // https://mvnrepository.com/artifact/org.commonmark/commonmark - implementation 'org.commonmark:commonmark:0.21.0' + implementation 'org.commonmark:commonmark:0.21.0' // https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core - implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' - - developmentOnly("org.springframework.boot:spring-boot-devtools") - compileOnly 'org.projectlombok:lombok:1.18.28' - annotationProcessor 'org.projectlombok:lombok:1.18.28' + implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' + developmentOnly("org.springframework.boot:spring-boot-devtools") + compileOnly 'org.projectlombok:lombok:1.18.28' + annotationProcessor 'org.projectlombok:lombok:1.18.28' } + task writeVersion { def propsFile = file('src/main/resources/version.properties') def props = new Properties() @@ -128,7 +130,7 @@ jar { attributes 'Implementation-Title': 'Stirling-PDF', 'Implementation-Version': project.version } - + } tasks.named('test') { diff --git a/src/main/java/stirling/software/SPDF/controller/api/ExtractController.java b/src/main/java/stirling/software/SPDF/controller/api/ExtractController.java new file mode 100644 index 00000000..20cc7328 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/ExtractController.java @@ -0,0 +1,120 @@ +package stirling.software.SPDF.controller.api; + +import com.opencsv.CSVWriter; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import stirling.software.SPDF.controller.api.strippers.PDFTableStripper; +import stirling.software.SPDF.model.api.extract.PDFFilePage; + +import java.awt.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +@RestController +@RequestMapping("/api/v1/extract/pdf-to-csv") +@Tag(name = "General", description = "General APIs") +public class ExtractController { + + private static final Logger logger = LoggerFactory.getLogger(CropController.class); + + @PostMapping(consumes = "multipart/form-data") + @Operation(summary = "Extracts a PDF document to csv", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") + public ResponseEntity PdfToCsv(@ModelAttribute PDFFilePage form) + throws IOException { + + ArrayList tableData = new ArrayList<>(); + int columnsCount = 0; + + try (PDDocument document = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) { + final double res = 72; // PDF units are at 72 DPI + PDFTableStripper stripper = new PDFTableStripper(); + stripper.setSortByPosition(true); + stripper.setRegion(new Rectangle((int) Math.round(1.0 * res), (int) Math.round(1 * res), (int) Math.round(6 * res), (int) Math.round(9.0 * res))); + + PDPage pdPage = document.getPage(form.getPageId() - 1); + stripper.extractTable(pdPage); + columnsCount = stripper.getColumns(); + + for (int c = 0; c < columnsCount; ++c) { + for(int r=0; r notEmptyColumns = new ArrayList<>(); + + for (String item: tableData) { + if(!item.trim().isEmpty()){ + notEmptyColumns.add(item); + }else{ + columnsCount--; + } + } + + List fullTable = notEmptyColumns.stream().map((entity)-> + entity.replace('\n',' ').replace('\r',' ').trim().replaceAll("\\s{2,}", "|")).toList(); + + int rowsCount = fullTable.get(0).split("\\|").length; + + ArrayList headersList = getTableHeaders(columnsCount,fullTable); + ArrayList recordList = getRecordsList(rowsCount,fullTable); + + + StringWriter writer = new StringWriter(); + try (CSVWriter csvWriter = new CSVWriter(writer)) { + csvWriter.writeNext(headersList.toArray(new String[0])); + for (String record : recordList) { + csvWriter.writeNext(record.split("\\|")); + } + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentDisposition(ContentDisposition.builder("attachment").filename(form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted.csv").build()); + headers.setContentType(MediaType.parseMediaType("text/csv")); + + return ResponseEntity.ok() + .headers(headers) + .body(writer.toString()); + } + + private ArrayList getRecordsList( int rowsCounts ,List items){ + ArrayList recordsList = new ArrayList<>(); + + for (int b=1; b getTableHeaders(int columnsCount, List items){ + ArrayList resultList = new ArrayList<>(); + for (int i=0;i boxes; + + // Border to allow when finding intersections + private double dx = 1.0; // This value works for me, feel free to tweak (or add setter) + private double dy = 0.000; // Rows of text tend to overlap, so need to extend + + /** + * Region in which to find table (otherwise whole page) + */ + private Rectangle2D regionArea; + + /** + * Number of rows in inferred table + */ + private int nRows=0; + + /** + * Number of columns in inferred table + */ + private int nCols=0; + + /** + * This is the object that does the text extraction + */ + private PDFTextStripperByArea regionStripper; + + /** + * 1D intervals - used for calculateTableRegions() + * @author Beldaz + * + */ + public static class Interval { + double start; + double end; + public Interval(double start, double end) { + this.start=start; this.end = end; + } + public void add(Interval col) { + if(col.startend) + end = col.end; + } + public static void addTo(Interval x, LinkedList columns) { + int p = 0; + Iterator it = columns.iterator(); + // Find where x should go + while(it.hasNext()) { + Interval col = it.next(); + if(x.end>=col.start) { + if(x.start<=col.end) { // overlaps + x.add(col); + it.remove(); + } + break; + } + ++p; + } + while(it.hasNext()) { + Interval col = it.next(); + if(x.start>col.end) + break; + x.add(col); + it.remove(); + } + columns.add(p, x); + } + + } + + + /** + * Instantiate a new PDFTableStripper object. + * + * @param document + * @throws IOException If there is an error loading the properties. + */ + public PDFTableStripper() throws IOException + { + super.setShouldSeparateByBeads(false); + regionStripper = new PDFTextStripperByArea(); + regionStripper.setSortByPosition( true ); + } + + /** + * Define the region to group text by. + * + * @param rect The rectangle area to retrieve the text from. + */ + public void setRegion(Rectangle2D rect ) + { + regionArea = rect; + } + + public int getRows() + { + return nRows; + } + + public int getColumns() + { + return nCols; + } + + /** + * Get the text for the region, this should be called after extractTable(). + * + * @return The text that was identified in that region. + */ + public String getText(int row, int col) + { + return regionStripper.getTextForRegion("el"+col+"x"+row); + } + + public void extractTable(PDPage pdPage) throws IOException + { + setStartPage(getCurrentPageNo()); + setEndPage(getCurrentPageNo()); + + boxes = new HashSet(); + // flip y-axis + flipAT = new AffineTransform(); + flipAT.translate(0, pdPage.getBBox().getHeight()); + flipAT.scale(1, -1); + + // page may be rotated + rotateAT = new AffineTransform(); + int rotation = pdPage.getRotation(); + if (rotation != 0) + { + PDRectangle mediaBox = pdPage.getMediaBox(); + switch (rotation) + { + case 90: + rotateAT.translate(mediaBox.getHeight(), 0); + break; + case 270: + rotateAT.translate(0, mediaBox.getWidth()); + break; + case 180: + rotateAT.translate(mediaBox.getWidth(), mediaBox.getHeight()); + break; + default: + break; + } + rotateAT.rotate(Math.toRadians(rotation)); + } + // Trigger processing of the document so that writeString is called. + try (Writer dummy = new OutputStreamWriter(new ByteArrayOutputStream())) { + super.output = dummy; + super.processPage(pdPage); + } + + Rectangle2D[][] regions = calculateTableRegions(); + +// System.err.println("Drawing " + nCols + "x" + nRows + "="+ nRows*nCols + " regions"); + for(int i=0; i columns = new LinkedList(); + LinkedList rows = new LinkedList(); + + for(Rectangle2D box: boxes) { + Interval x = new Interval(box.getMinX(), box.getMaxX()); + Interval y = new Interval(box.getMinY(), box.getMaxY()); + + Interval.addTo(x, columns); + Interval.addTo(y, rows); + } + + nRows = rows.size(); + nCols = columns.size(); + Rectangle2D[][] regions = new Rectangle2D[nCols][nRows]; + int i=0; + // Label regions from top left, rather than the transformed orientation + for(Interval column: columns) { + int j=0; + for(Interval row: rows) { + regions[nCols-i-1][nRows-j-1] = new Rectangle2D.Double(column.start, row.start, column.end - column.start, row.end - row.start); + ++j; + } + ++i; + } + + return regions; + } + + /** + * Register each character's bounding box, updating boxes field to maintain + * a list of all distinct groups of characters. + * + * Overrides the default functionality of PDFTextStripper. + * Most of this is taken from DrawPrintTextLocations.java, with extra steps + * at end of main loop + */ + @Override + protected void writeString(String string, List textPositions) throws IOException + { + for (TextPosition text : textPositions) + { + // glyph space -> user space + // note: text.getTextMatrix() is *not* the Text Matrix, it's the Text Rendering Matrix + AffineTransform at = text.getTextMatrix().createAffineTransform(); + PDFont font = text.getFont(); + BoundingBox bbox = font.getBoundingBox(); + + // advance width, bbox height (glyph space) + float xadvance = font.getWidth(text.getCharacterCodes()[0]); // todo: should iterate all chars + Rectangle2D.Float rect = new Rectangle2D.Float(0, bbox.getLowerLeftY(), xadvance, bbox.getHeight()); + + if (font instanceof PDType3Font) + { + // bbox and font matrix are unscaled + at.concatenate(font.getFontMatrix().createAffineTransform()); + } + else + { + // bbox and font matrix are already scaled to 1000 + at.scale(1/1000f, 1/1000f); + } + Shape s = at.createTransformedShape(rect); + s = flipAT.createTransformedShape(s); + s = rotateAT.createTransformedShape(s); + + + // + // Merge character's bounding box with boxes field + // + Rectangle2D bounds = s.getBounds2D(); + // Pad sides to detect almost touching boxes + Rectangle2D hitbox = bounds.getBounds2D(); + hitbox.add(bounds.getMinX() - dx , bounds.getMinY() - dy); + hitbox.add(bounds.getMaxX() + dx , bounds.getMaxY() + dy); + + // Find all overlapping boxes + List intersectList = new ArrayList(); + for(Rectangle2D box: boxes) { + if(box.intersects(hitbox)) { + intersectList.add(box); + } + } + + // Combine all touching boxes and update + // (NOTE: Potentially this could leave some overlapping boxes un-merged, + // but it's sufficient for now and get's fixed up in calculateTableRegions) + for(Rectangle2D box: intersectList) { + bounds.add(box); + boxes.remove(box); + } + boxes.add(bounds); + + } + + } + + /** + * This method does nothing in this derived class, because beads and regions are incompatible. Beads are + * ignored when stripping by area. + * + * @param aShouldSeparateByBeads The new grouping of beads. + */ + @Override + public final void setShouldSeparateByBeads(boolean aShouldSeparateByBeads) + { + } + + /** + * Adapted from PDFTextStripperByArea + * {@inheritDoc} + */ + @Override + protected void processTextPosition( TextPosition text ) + { + if(regionArea!=null && !regionArea.contains( text.getX(), text.getY() ) ) { + // skip character + } else { + super.processTextPosition( text ); + } + } +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java index 76e7be8f..3bad71c9 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java @@ -99,6 +99,14 @@ public class ConverterWebController { return modelAndView; } + @GetMapping("/pdf-to-csv") + @Hidden + public ModelAndView pdfToCSV() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv"); + modelAndView.addObject("currentPage", "pdf-to-csv"); + return modelAndView; + } + @GetMapping("/pdf-to-pdfa") @Hidden diff --git a/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java b/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java new file mode 100644 index 00000000..bfe87a16 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java @@ -0,0 +1,18 @@ +package stirling.software.SPDF.model.api.extract; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper=true) +public class PDFFilePage extends PDFFile { + + + @Schema(description = "Number of chosen page", type = "number") + private int pageId; + + +} + diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index ae4dee24..de8b25ef 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -823,3 +823,8 @@ PDFToXML.title=تحويل PDF إلى XML PDFToXML.header=تحويل PDF إلى XML PDFToXML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات. PDFToXML.submit=تحويل + +#PDFToCSV +PDFToCSV.title= PDF ??? CSV +PDFToCSV.header=PDF ??? CSV +PDFToCSV.submit=?????? \ No newline at end of file diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties index 91bcd4fc..9a06c42a 100644 --- a/src/main/resources/messages_bg_BG.properties +++ b/src/main/resources/messages_bg_BG.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF към XML PDFToXML.header=PDF към XML PDFToXML.credit=Тази услуга използва LibreOffice за преобразуване на файлове. PDFToXML.submit=Преобразуване + +#PDFToCSV +PDFToCSV.title=PDF ??? CSV +PDFToCSV.header=PDF ??? CSV +PDFToCSV.submit=???????? \ No newline at end of file diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index cbe77a15..c5108406 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF a XML PDFToXML.header=PDF a XML PDFToXML.credit=Utilitza LibreOffice per a la conversió d'Arxius. PDFToXML.submit=Converteix + +#PDFToCSV +PDFToCSV.title=PDF a CSV +PDFToCSV.header=PDF a CSV +PDFToCSV.submit=Extracte \ No newline at end of file diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index ea495191..fdff7106 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF in XML PDFToXML.header=PDF in XML PDFToXML.credit=Dieser Dienst verwendet LibreOffice für die Dateikonvertierung. PDFToXML.submit=Konvertieren + +#PDFToCSV +PDFToCSV.title=PDF zu CSV +PDFToCSV.header=PDF zu CSV +PDFToCSV.submit=Extrakt \ No newline at end of file diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties index c087bcfa..7b87af58 100644 --- a/src/main/resources/messages_el_GR.properties +++ b/src/main/resources/messages_el_GR.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF \u03C3\u03B5 XML PDFToXML.header=PDF \u03C3\u03B5 XML PDFToXML.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. PDFToXML.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE + +#PDFToCSV +PDFToCSV.title=PDF ?? CSV +PDFToCSV.header=PDF ?? CSV +PDFToCSV.submit=????????? \ No newline at end of file diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index e1cca351..635700ae 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -822,4 +822,9 @@ PDFToHTML.submit=Convert PDFToXML.title=PDF to XML PDFToXML.header=PDF to XML PDFToXML.credit=This service uses LibreOffice for file conversion. -PDFToXML.submit=Convert \ No newline at end of file +PDFToXML.submit=Convert + +#PDFToCSV +PDFToCSV.title=PDF to CSV +PDFToCSV.header=PDF to CSV +PDFToCSV.submit=Extract \ No newline at end of file diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index a15f9ab6..fc8d774f 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -93,7 +93,6 @@ account.accountSettings=Account Settings account.adminSettings=Admin Settings - View and Add Users account.userControlSettings=User Control Settings account.changeUsername=Change Username -account.changeUsername=Change Username account.password=Confirmation Password account.oldPassword=Old password account.newPassword=New Password @@ -334,7 +333,12 @@ showJS.tags=JS home.autoRedact.title=Auto Redact home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text -showJS.tags=JS +autoRedact.tags=JS + +home.tableExtraxt.title=Table Extraction +home.tableExtraxt.desc=Table Extraction from PDF to CSV +tableExtraxt.tags=CSV + ########################### # # @@ -563,7 +567,7 @@ ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a pho ScannerImageSplit.selectText.9=Border Size: ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1). - + #OCR ocr.title=OCR / Scan Cleanup ocr.header=Cleanup Scans / OCR (Optical Character Recognition) @@ -682,8 +686,8 @@ imageToPDF.selectText.2=Auto rotate PDF imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images) imageToPDF.selectText.4=Merge into single PDF imageToPDF.selectText.5=Convert to separate PDFs - - + + #pdfToImage pdfToImage.title=PDF to Image pdfToImage.header=PDF to Image @@ -773,7 +777,6 @@ changeMetadata.keywords=Keywords: changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss): changeMetadata.producer=Producer: changeMetadata.subject=Subject: -changeMetadata.title=Title: changeMetadata.trapped=Trapped: changeMetadata.selectText.4=Other Metadata: changeMetadata.selectText.5=Add Custom Metadata Entry @@ -823,3 +826,8 @@ PDFToXML.title=PDF to XML PDFToXML.header=PDF to XML PDFToXML.credit=This service uses LibreOffice for file conversion. PDFToXML.submit=Convert + +#PDFToCSV +PDFToCSV.title=PDF to CSV +PDFToCSV.header=PDF to CSV +PDFToCSV.submit=Extract diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 08da2cc2..7b821e3b 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF a XML PDFToXML.header=PDF a XML PDFToXML.credit=Este servicio utiliza LibreOffice para la conversión de archivos PDFToXML.submit=Convertir + +#PDFToCSV +PDFToCSV.title=PDF a CSV +PDFToCSV.header=PDF a CSV +PDFToCSV.submit=Extracto diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 8f6c9a39..76434b95 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDFa XML bihurtu PDFToXML.header=PDFa XML bihurtu PDFToXML.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko PDFToXML.submit=Bihurtu + +#PDFToCSV +PDFToCSV.title=PDF a CSV +PDFToCSV.header=PDF a CSV +PDFToCSV.submit=Extracto \ No newline at end of file diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index dfb074de..a7d777e1 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF en XML PDFToXML.header=PDF en XML PDFToXML.credit=Ce service utilise LibreOffice pour la conversion de fichiers. PDFToXML.submit=Convertir + +#PDFToCSV +PDFToCSV.title=PDF en CSV +PDFToCSV.header=PDF en CSV +PDFToCSV.submit=Extrait \ No newline at end of file diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index 27537f4a..73f9e7a4 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -823,3 +823,8 @@ PDFToXML.title=Da PDF a XML PDFToXML.header=Da PDF a XML PDFToXML.credit=Questo servizio utilizza LibreOffice per la conversione. PDFToXML.submit=Converti + +#PDFToCSV +PDFToCSV.title=Da PDF a CSV +PDFToCSV.header=Da PDF a CSV +PDFToCSV.submit=Estratto diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index 42b0184a..0ec24487 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDFをXMLに変換 PDFToXML.header=PDFをXMLに変換 PDFToXML.credit=本サービスはファイル変換にLibreOfficeを使用しています。 PDFToXML.submit=変換 + +#PDFToCSV +PDFToCSV.title=PDF??CSV? +PDFToCSV.header=PDF??CSV? +PDFToCSV.submit=???? \ No newline at end of file diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index db5428c5..6af0c823 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF to XML PDFToXML.header=PDF를 XML로 변환 PDFToXML.credit=이 서비스는 파일 변환을 위해 LibreOffice를 사용합니다. PDFToXML.submit=변환 + +#PDFToCSV +PDFToCSV.title=PDF? CSV? +PDFToCSV.header=PDF? CSV? +PDFToCSV.submit=?? diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties index ae48b790..ef608a31 100644 --- a/src/main/resources/messages_nl_NL.properties +++ b/src/main/resources/messages_nl_NL.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF naar XML PDFToXML.header=PDF naar XML PDFToXML.credit=Deze service gebruikt LibreOffice voor bestandsconversie. PDFToXML.submit=Converteren + +#PDFToCSV +PDFToCSV.title=PDF naar CSV +PDFToCSV.header=PDF naar CSV +PDFToCSV.submit=Extract \ No newline at end of file diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 1e0cd5c2..27a256f1 100644 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF na XML PDFToXML.header=PDF na XML PDFToXML.credit=Ta usługa używa LibreOffice do konwersji plików. PDFToXML.submit=Konwertuj + +#PDFToCSV +PDFToCSV.title=PDF na CSV +PDFToCSV.header=PDF na CSV +PDFToCSV.submit=Wyci?g diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 1042408f..03c076ee 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF para XML PDFToXML.header=PDF para XML PDFToXML.credit=Este serviço usa o LibreOffice para Conversão de Arquivos. PDFToXML.submit=Converter + +#PDFToCSV +PDFToCSV.title=PDF para CSV +PDFToCSV.header=PDF para CSV +PDFToCSV.submit=Eztenna \ No newline at end of file diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index 033e8d46..97ab70b7 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF către XML PDFToXML.header=PDF către XML PDFToXML.credit=Acest serviciu utilizează LibreOffice pentru conversia fișierului. PDFToXML.submit=Convert + +#PDFToCSV +PDFToCSV.title=PDF n CSV +PDFToCSV.header=PDF n CSV +PDFToCSV.submit=Extrage diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index bdd28442..7b6e1ef5 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF в XML PDFToXML.header=PDF в XML PDFToXML.credit=Этот сервис использует LibreOffice для преобразования файлов. PDFToXML.submit=Конвертировать + +#PDFToCSV +PDFToCSV.title=PDF ? CSV +PDFToCSV.header=PDF ? CSV +PDFToCSV.submit=??????? \ No newline at end of file diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 1e84c528..27060466 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF till XML PDFToXML.header=PDF till XML PDFToXML.credit=Denna tjänst använder LibreOffice för filkonvertering. PDFToXML.submit=Konvertera + +#PDFToCSV +PDFToCSV.title=PDF till CSV +PDFToCSV.header=PDF till CSV +PDFToCSV.submit=Navvit \ No newline at end of file diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 69a12e08..4f46e1e6 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -823,3 +823,8 @@ PDFToXML.title=PDF To XML PDFToXML.header=将PDF转换为XML PDFToXML.credit=此服务使用LibreOffice进行文件转换。 PDFToXML.submit=转换 + +#PDFToCSV +PDFToCSV.title=PDF ? CSV +PDFToCSV.header=PDF ? CSV +PDFToCSV.submit=?? \ No newline at end of file diff --git a/src/main/resources/static/images/pdf-csv.svg b/src/main/resources/static/images/pdf-csv.svg new file mode 100644 index 00000000..95d68c10 --- /dev/null +++ b/src/main/resources/static/images/pdf-csv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/templates/convert/pdf-to-csv.html b/src/main/resources/templates/convert/pdf-to-csv.html new file mode 100644 index 00000000..cbd87ad8 --- /dev/null +++ b/src/main/resources/templates/convert/pdf-to-csv.html @@ -0,0 +1,158 @@ + + + + + + + +
+
+ +
+
+
+

+
+ +
+ +
+ + +
+
+ + + + +
+ +
+ + + +
+
+
+
+
+
+ + diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 4326afcc..5299c9b3 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -1,13 +1,13 @@
- + - - + + - +
- + - + + + + + @@ -147,23 +149,23 @@ - - - - + - - - + @@ -251,61 +253,61 @@ -
+