diff --git a/engine/src/main/java/pro/verron/officestamper/preset/ExperimentalStampers.java b/engine/src/main/java/pro/verron/officestamper/preset/ExperimentalStampers.java index e8f75ba2..0377a22e 100644 --- a/engine/src/main/java/pro/verron/officestamper/preset/ExperimentalStampers.java +++ b/engine/src/main/java/pro/verron/officestamper/preset/ExperimentalStampers.java @@ -1,12 +1,16 @@ package pro.verron.officestamper.preset; +import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.packages.PresentationMLPackage; import org.docx4j.openpackaging.packages.SpreadsheetMLPackage; import pro.verron.officestamper.api.OfficeStamper; import pro.verron.officestamper.api.OfficeStamperException; +import pro.verron.officestamper.api.StreamStamper; import pro.verron.officestamper.experimental.ExcelStamper; import pro.verron.officestamper.experimental.PowerpointStamper; +import java.io.InputStream; + /** * ExperimentalStampers is a class that provides static methods for obtaining instances of OfficeStamper * implementations for stamping PowerPoint presentations and Excel templates with context and writing @@ -29,8 +33,17 @@ private ExperimentalStampers() { * * @since 1.6.8 */ - public static OfficeStamper pptxStamper() { - return new PowerpointStamper(); + public static StreamStamper pptxStamper() { + var stamper = new PowerpointStamper(); + return new StreamStamper<>(ExperimentalStampers::loadPowerPoint, stamper); + } + + private static PresentationMLPackage loadPowerPoint(InputStream inputStream) { + try { + return PresentationMLPackage.load(inputStream); + } catch (Docx4JException e) { + throw new OfficeStamperException(e); + } } /** diff --git a/examples/pom.xml b/examples/pom.xml index 61c36e9d..b5b398b7 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -16,6 +16,12 @@ + + info.picocli + picocli + 4.7.6 + + pro.verron.office-stamper engine diff --git a/examples/src/main/java/pro/verron/officestamper/Examples.java b/examples/src/main/java/pro/verron/officestamper/Examples.java deleted file mode 100644 index 9c537a46..00000000 --- a/examples/src/main/java/pro/verron/officestamper/Examples.java +++ /dev/null @@ -1,52 +0,0 @@ -package pro.verron.officestamper; - -import pro.verron.officestamper.api.OfficeStamperException; -import pro.verron.officestamper.preset.OfficeStampers; - -import java.io.OutputStream; -import java.util.Map; -import java.util.logging.Logger; - -import static pro.verron.officestamper.preset.OfficeStamperConfigurations.standard; - -public class Examples { - - public static final Logger logger = Utils.getLogger(); - - private Examples() { - throw new OfficeStamperException("Examples cannot be instantiated"); - } - - public static void stampDiagnostic(OutputStream outputStream) { - logger.info("Start of the diagnostic stamping procedure"); - - logger.info("Setup a map-reading able docx-stamper instance"); - - var configuration = standard(); - var stamper = OfficeStampers.docxStamper(configuration); - - logger.info("Load the internally packaged 'Diagnostic.docx' template resource"); - var template = Utils.streamResource("Diagnostic.docx"); - - logger.info(""" - Create a context with: \ - system environment variables, \ - jvm properties, \ - and user preferences"""); - - var diagnosticMaker = new Diagnostic(); - var context = Map.of( - "reportDate", diagnosticMaker.date(), - "reportUser", diagnosticMaker.user(), - "environment", diagnosticMaker.environmentVariables(), - "properties", diagnosticMaker.jvmProperties(), - "preferences", diagnosticMaker.userPreferences() - ); - - logger.info("Start stamping process"); - stamper.stamp(template, context, outputStream); - - logger.info("End of the diagnostic stamping procedure"); - } - -} diff --git a/examples/src/main/java/pro/verron/officestamper/ExcelContext.java b/examples/src/main/java/pro/verron/officestamper/ExcelContext.java new file mode 100644 index 00000000..d313e963 --- /dev/null +++ b/examples/src/main/java/pro/verron/officestamper/ExcelContext.java @@ -0,0 +1,114 @@ +package pro.verron.officestamper; + +import org.docx4j.openpackaging.exceptions.Docx4JException; +import org.docx4j.openpackaging.packages.SpreadsheetMLPackage; +import org.docx4j.openpackaging.parts.SpreadsheetML.WorkbookPart; +import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart; +import org.docx4j.openpackaging.parts.relationships.RelationshipsPart; +import org.xlsx4j.org.apache.poi.ss.usermodel.DataFormatter; +import org.xlsx4j.sml.Row; +import org.xlsx4j.sml.Sheet; +import org.xlsx4j.sml.Workbook; +import org.xlsx4j.sml.Worksheet; +import pro.verron.officestamper.api.OfficeStamperException; + +import java.io.InputStream; +import java.util.*; + +import static java.util.Collections.emptyList; + +public class ExcelContext + extends AbstractMap>> { + + public static final DataFormatter formatter = new DataFormatter(); + private final Map>> source; + + public ExcelContext(SpreadsheetMLPackage spreadsheetPackage) { + var workbookPart = spreadsheetPackage.getWorkbookPart(); + var workbook = getWorkbook(workbookPart); + var allSheets = workbook.getSheets(); + var sheets = allSheets.getSheet(); + + var relationshipsPart = workbookPart.getRelationshipsPart(); + + var excel = new TreeMap>>(); + + for (var sheet : sheets) { + var worksheetPart = extractWorksheetPart(sheet, relationshipsPart); + var worksheet = extractWorksheet(worksheetPart); + var sheetDate = worksheet.getSheetData(); + var rows = sheetDate.getRow(); + excel.put(sheet.getName(), extractRecords(rows)); + } + + source = Collections.unmodifiableMap(excel); + } + + private static Workbook getWorkbook(WorkbookPart workbookPart) { + try { + return workbookPart.getContents(); + } catch (Docx4JException e) { + throw new OfficeStamperException(e); + } + } + + private WorksheetPart extractWorksheetPart(Sheet sheet, RelationshipsPart relationshipsPart) { + return (WorksheetPart) relationshipsPart.getPart(sheet.getId()); + } + + private Worksheet extractWorksheet(WorksheetPart worksheetPart) { + try { + return worksheetPart.getContents(); + } catch (Docx4JException e) { + throw new OfficeStamperException(e); + } + } + + private List> extractRecords(List rows) { + if (rows.isEmpty()) return emptyList(); + var headers = extractHeaders(rows.getFirst()); + return extractRecords(headers, rows.subList(1, rows.size())); + } + + private List extractHeaders(Row row) { + return row.getC() + .stream() + .map(formatter::formatCellValue) + .toList(); + } + + private List> extractRecords(List headers, List rows) { + List> list = new ArrayList<>(); + for (var row : rows) { + Map rec = new TreeMap<>(); + for (var i = 0; i < headers.size(); i++) { + rec.put(headers.get(i), formatCellValueAt(row, i)); + } + list.add(rec); + } + return list; + } + + private static String formatCellValueAt(Row row, int i) { + var cells = row.getC(); + if (i >= cells.size()) return ""; + return formatter.formatCellValue(cells.get(i)); + } + + public static Object from(InputStream inputStream) { + try { + return from(SpreadsheetMLPackage.load(inputStream)); + } catch (Docx4JException e) { + throw new OfficeStamperException(e); + } + } + + private static Object from(SpreadsheetMLPackage spreadsheetPackage) { + return new ExcelContext(spreadsheetPackage); + } + + @Override + public Set>>> entrySet() { + return source.entrySet(); + } +} diff --git a/examples/src/main/java/pro/verron/officestamper/Main.java b/examples/src/main/java/pro/verron/officestamper/Main.java index b3be3c9b..454a41ba 100644 --- a/examples/src/main/java/pro/verron/officestamper/Main.java +++ b/examples/src/main/java/pro/verron/officestamper/Main.java @@ -1,28 +1,156 @@ package pro.verron.officestamper; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import pro.verron.officestamper.api.OfficeStamperException; + import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; -import static pro.verron.officestamper.Examples.stampDiagnostic; +import static java.nio.file.Files.newOutputStream; -public class Main { +@Command(name = "officestamper", mixinStandardHelpOptions = true, description = "Office Stamper CLI tool") +public class Main + implements Runnable { private static final Logger logger = Utils.getLogger(); - public static void main(String[] args) - throws Exception { - stampDiagnostic(createOutStream("Diagnostic-")); + @Option(names = {"-i", "--input-file"}, + required = true, + description = "Input file path (csv, properties, html, xml, json, excel) or a keyword for documented data" + + " sources") private String inputFile; + + @Option(names = {"-t", "--template-file"}, + required = true, + description = "Template file path or a keyword for documented template packages") private String templateFile; + + @Option(names = {"-o", "--output-path"}, + defaultValue = "default-output.docx", + description = "Output file path") private String outputPath; + + @Option(names = {"-s", "--stamper"}, + defaultValue = "diagnostic", + description = "Stamper type (diagnostic, powerpoint)") private String stamperType; + + public static void main(String[] args) { + var main = new Main(); + int exitCode = new CommandLine(main).execute(args); + System.exit(exitCode); + } + + @Override + public void run() { + if (inputFile == null || templateFile == null) { + logger.log(Level.SEVERE, "Input file and template file must be provided"); + return; + } + + stamperType = stamperType.toLowerCase(); + + logger.log(Level.INFO, "Input File: {}", inputFile); + logger.log(Level.INFO, "Template File: {}", templateFile); + logger.log(Level.INFO, "Output Path: {}", outputPath); + logger.log(Level.INFO, "Stamper Type: {}", stamperType); + + final var context = switch (inputFile) { + case "diagnostic" -> createDiagnosticContext(); + default -> contextualise(Path.of(inputFile)); + }; + + final var templateStream = switch (templateFile) { + case "diagnostic" -> loadDiagnosticTemplate(); + default -> streamFile(Path.of(templateFile)); + }; + + final var outputStream = createOutputStream(Path.of(outputPath)); + + final var stamper = switch (stamperType) { + case "word" -> new WordStamper(); + case "powerpoint" -> new PowerPointStamper(); + default -> throw new OfficeStamperException("Invalid stamper type: " + stamperType); + }; + + stamper.stamp(context, templateStream, outputStream); + } + + private static Object createDiagnosticContext() { + logger.info(""" + Create a context with: \ + system environment variables, \ + jvm properties, \ + and user preferences"""); + var diagnosticMaker = new Diagnostic(); + var map = new TreeMap(); + map.put("reportDate", diagnosticMaker.date()); + map.put("reportUser", diagnosticMaker.user()); + map.put("environment", diagnosticMaker.environmentVariables()); + map.put("properties", diagnosticMaker.jvmProperties()); + map.put("preferences", diagnosticMaker.userPreferences()); + return map; } - private static OutputStream createOutStream(String prefix) - throws IOException { - var outputPath = Files.createTempFile(prefix, ".docx"); - logger.log(Level.INFO, "Stamping to file: ", outputPath); - return Files.newOutputStream(outputPath); + private Object contextualise(Path path) { + String fileName = path.getFileName() + .toString() + .toLowerCase(); + if (fileName.endsWith(".csv")) return processCsv(path); + if (fileName.endsWith(".properties")) return processProperties(path); + if (fileName.endsWith(".html") || fileName.endsWith(".xml")) return processXmlOrHtml(path); + if (fileName.endsWith(".json")) return processJson(path); + if (fileName.endsWith(".xlsx")) return processExcel(path); + throw new OfficeStamperException("Unsupported file type: " + fileName); } + private static InputStream loadDiagnosticTemplate() { + logger.info("Load the internally packaged 'Diagnostic.docx' template resource"); + return Utils.streamResource("Diagnostic.docx"); + } + + private static InputStream streamFile(Path path) { + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new OfficeStamperException(e); + } + } + + private OutputStream createOutputStream(Path path) { + try { + return newOutputStream(path); + } catch (IOException e) { + throw new OfficeStamperException(e); + } + } + + private Object processCsv(Path path) { + throw new OfficeStamperException("Not yet implemented."); + } + + private Object processProperties(Path path) { + throw new OfficeStamperException("Not yet implemented."); + } + + private Object processXmlOrHtml(Path path) { + throw new OfficeStamperException("Not yet implemented."); + } + + private Object processJson(Path path) { + throw new OfficeStamperException("Not yet implemented."); + } + + private Object processExcel(Path path) { + try { + return ExcelContext.from(Files.newInputStream(path)); + } catch (IOException e) { + throw new OfficeStamperException(e); + } + } } diff --git a/examples/src/main/java/pro/verron/officestamper/PowerPointStamper.java b/examples/src/main/java/pro/verron/officestamper/PowerPointStamper.java new file mode 100644 index 00000000..da99460b --- /dev/null +++ b/examples/src/main/java/pro/verron/officestamper/PowerPointStamper.java @@ -0,0 +1,26 @@ +package pro.verron.officestamper; + + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Logger; + +import static pro.verron.officestamper.preset.ExperimentalStampers.pptxStamper; + +public class PowerPointStamper + implements Stamper { + public static final Logger logger = Utils.getLogger(); + + public void stamp(Object context, InputStream templateStream, OutputStream outputStream) { + logger.info("Start of the stamping procedure"); + + logger.info("Setup a map-reading able docx-stamper instance"); + + var stamper = pptxStamper(); + + logger.info("Start stamping process"); + stamper.stamp(templateStream, context, outputStream); + + logger.info("End of the stamping procedure"); + } +} diff --git a/examples/src/main/java/pro/verron/officestamper/Stamper.java b/examples/src/main/java/pro/verron/officestamper/Stamper.java new file mode 100644 index 00000000..cf15e484 --- /dev/null +++ b/examples/src/main/java/pro/verron/officestamper/Stamper.java @@ -0,0 +1,8 @@ +package pro.verron.officestamper; + +import java.io.InputStream; +import java.io.OutputStream; + +public interface Stamper { + void stamp(Object context, InputStream templateStream, OutputStream outputStream); +} diff --git a/examples/src/main/java/pro/verron/officestamper/WordStamper.java b/examples/src/main/java/pro/verron/officestamper/WordStamper.java new file mode 100644 index 00000000..aa315c56 --- /dev/null +++ b/examples/src/main/java/pro/verron/officestamper/WordStamper.java @@ -0,0 +1,30 @@ +package pro.verron.officestamper; + +import pro.verron.officestamper.preset.OfficeStampers; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Logger; + +import static pro.verron.officestamper.preset.OfficeStamperConfigurations.standard; + +public class WordStamper + implements Stamper { + + public static final Logger logger = Utils.getLogger(); + + @Override + public void stamp(Object context, InputStream templateStream, OutputStream outputStream) { + logger.info("Start of the stamping procedure"); + + logger.info("Setup a map-reading able docx-stamper instance"); + + var configuration = standard(); + var stamper = OfficeStampers.docxStamper(configuration); + + logger.info("Start stamping process"); + stamper.stamp(templateStream, context, outputStream); + + logger.info("End of the stamping procedure"); + } +}