Main.java
package pro.verron.officestamper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import pro.verron.officestamper.api.OfficeStamperException;
import pro.verron.officestamper.experimental.ExperimentalStampers;
import pro.verron.officestamper.preset.OfficeStampers;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import static java.nio.file.Files.newOutputStream;
/// Main class for the CLI.
@Command(name = "officestamper", mixinStandardHelpOptions = true, description = "Office Stamper CLI tool")
public class Main
implements Runnable {
private static final Logger logger = Utils.getLogger();
@Option(names = {"-i", "--input"},
required = true,
description = "Input file path (csv, properties, html, xml, json, excel) or a keyword (diagnostic) for "
+ "documented data sources") private String inputFile;
@Option(names = {"-t", "--template"},
required = true,
description = "Template file path or a keyword (diagnostic) for documented template packages") private String templateFile;
@Option(names = {"-o", "--output"},
defaultValue = "output.docx",
description = "Output file path") private String outputPath;
@Option(names = {"-s", "--stamper"},
defaultValue = "word",
description = "Stamper type (word, powerpoint)") private String stamperType;
/// Default constructor.
public Main() {
}
static void main(String[] args) {
var main = new Main();
var cli = new CommandLine(main);
int exitCode = cli.execute(args);
System.exit(exitCode);
}
private static InputStream streamFile(Path path) {
try {
return Files.newInputStream(path);
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
@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 = extractContext(inputFile);
final var templateStream = extractTemplate(templateFile);
final var outputStream = createOutputStream(Path.of(outputPath));
final var stamper = switch (stamperType) {
case "word" -> OfficeStampers.docxStamper();
case "powerpoint" -> ExperimentalStampers.pptxStamper();
default -> throw new OfficeStamperException("Invalid stamper type: " + stamperType);
};
stamper.stamp(templateStream, context, outputStream);
}
private Object extractContext(String input) {
if ("diagnostic".equals(input)) return Diagnostic.context();
return contextualise(Path.of(input));
}
private InputStream extractTemplate(String template) {
if ("diagnostic".equals(template)) return Diagnostic.template();
return streamFile(Path.of(template));
}
private OutputStream createOutputStream(Path path) {
try {
return newOutputStream(path);
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
private Object contextualise(Path path) {
if (path.endsWith(".csv")) return processCsv(path);
if (path.endsWith(".properties")) return processProperties(path);
if (path.endsWith(".html") || path.endsWith(".xml")) return processXmlOrHtml(path);
if (path.endsWith(".json")) return processJson(path);
if (path.endsWith(".xlsx")) return processExcel(path);
throw new OfficeStamperException("Unsupported file type: " + path);
}
/// Return a list of objects with the csv properties
private Object processCsv(Path path) {
try (var reader = new CSVReader(new InputStreamReader(Files.newInputStream(path)))) {
String[] headers = reader.readNext();
return reader.readAll()
.stream()
.map(row -> {
Map<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < headers.length; i++) {
map.put(headers[i], row[i]);
}
return map;
})
.toList();
} catch (IOException | CsvException e) {
throw new OfficeStamperException(e);
}
}
private Object processProperties(Path path) {
var properties = new Properties();
try (var inputStream = Files.newInputStream(path)) {
properties.load(inputStream);
return new LinkedHashMap<>(properties.entrySet()
.stream()
.collect(Collectors.toMap(e -> String.valueOf(e.getKey()),
e -> String.valueOf(e.getValue()),
(a, b) -> b,
LinkedHashMap::new)));
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
private Object processXmlOrHtml(Path path) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(Files.newInputStream(path));
return processNode(document.getDocumentElement());
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new OfficeStamperException(e);
}
}
private Map<String, Object> processNode(Element element) {
Map<String, Object> result = new LinkedHashMap<>();
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node instanceof Element childElement) {
String name = childElement.getTagName();
if (childElement.hasChildNodes() && childElement.getFirstChild()
.getNodeType() != Node.TEXT_NODE) {
result.put(name, processNode(childElement));
}
else {
result.put(name, childElement.getTextContent());
}
}
}
return result;
}
private Object processJson(Path path) {
try {
ObjectMapper mapper = new ObjectMapper();
TypeReference<LinkedHashMap<String, Object>> typeRef = new TypeReference<>() {};
return mapper.readValue(Files.newInputStream(path), typeRef);
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
private Object processExcel(Path path) {
try {
return ExcelContext.from(Files.newInputStream(path));
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
}