| 1 | package pro.verron.officestamper; | |
| 2 | ||
| 3 | ||
| 4 | import com.fasterxml.jackson.core.type.TypeReference; | |
| 5 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 6 | import com.opencsv.CSVReader; | |
| 7 | import com.opencsv.exceptions.CsvException; | |
| 8 | import org.w3c.dom.Document; | |
| 9 | import org.w3c.dom.Element; | |
| 10 | import org.w3c.dom.Node; | |
| 11 | import org.w3c.dom.NodeList; | |
| 12 | import org.xml.sax.SAXException; | |
| 13 | import picocli.CommandLine; | |
| 14 | import picocli.CommandLine.Command; | |
| 15 | import picocli.CommandLine.Option; | |
| 16 | import pro.verron.officestamper.api.OfficeStamperException; | |
| 17 | import pro.verron.officestamper.experimental.ExperimentalStampers; | |
| 18 | import pro.verron.officestamper.preset.OfficeStampers; | |
| 19 | ||
| 20 | import javax.xml.XMLConstants; | |
| 21 | import javax.xml.parsers.DocumentBuilder; | |
| 22 | import javax.xml.parsers.DocumentBuilderFactory; | |
| 23 | import javax.xml.parsers.ParserConfigurationException; | |
| 24 | import java.io.IOException; | |
| 25 | import java.io.InputStream; | |
| 26 | import java.io.InputStreamReader; | |
| 27 | import java.io.OutputStream; | |
| 28 | import java.nio.file.Files; | |
| 29 | import java.nio.file.Path; | |
| 30 | import java.util.LinkedHashMap; | |
| 31 | import java.util.Map; | |
| 32 | import java.util.Properties; | |
| 33 | import java.util.logging.Level; | |
| 34 | import java.util.logging.Logger; | |
| 35 | import java.util.stream.Collectors; | |
| 36 | ||
| 37 | import static java.nio.file.Files.newOutputStream; | |
| 38 | ||
| 39 | /// Main class for the CLI. | |
| 40 | @Command(name = "officestamper", mixinStandardHelpOptions = true, description = "Office Stamper CLI tool") | |
| 41 | public class Main | |
| 42 | implements Runnable { | |
| 43 | ||
| 44 | private static final Logger logger = Utils.getLogger(); | |
| 45 | @Option(names = {"-i", "--input"}, | |
| 46 | required = true, | |
| 47 | description = "Input file path (csv, properties, html, xml, json, excel) or a keyword (diagnostic) for " | |
| 48 | + "documented data sources") private String inputFile; | |
| 49 | @Option(names = {"-t", "--template"}, | |
| 50 | required = true, | |
| 51 | description = "Template file path or a keyword (diagnostic) for documented template packages") private String templateFile; | |
| 52 | @Option(names = {"-o", "--output"}, | |
| 53 | defaultValue = "output.docx", | |
| 54 | description = "Output file path") private String outputPath; | |
| 55 | @Option(names = {"-s", "--stamper"}, | |
| 56 | defaultValue = "word", | |
| 57 | description = "Stamper type (word, powerpoint)") private String stamperType; | |
| 58 | ||
| 59 | /// Default constructor. | |
| 60 | public Main() { | |
| 61 | } | |
| 62 | ||
| 63 | static void main(String[] args) { | |
| 64 | var main = new Main(); | |
| 65 | var cli = new CommandLine(main); | |
| 66 | int exitCode = cli.execute(args); | |
| 67 |
1
1. main : removed call to java/lang/System::exit → NO_COVERAGE |
System.exit(exitCode); |
| 68 | } | |
| 69 | ||
| 70 | private static InputStream streamFile(Path path) { | |
| 71 | try { | |
| 72 |
1
1. streamFile : replaced return value with null for pro/verron/officestamper/Main::streamFile → NO_COVERAGE |
return Files.newInputStream(path); |
| 73 | } catch (IOException e) { | |
| 74 | throw new OfficeStamperException(e); | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | @Override | |
| 79 | public void run() { | |
| 80 |
2
1. run : negated conditional → NO_COVERAGE 2. run : negated conditional → NO_COVERAGE |
if (inputFile == null || templateFile == null) { |
| 81 | logger.log(Level.SEVERE, "Input file and template file must be provided"); | |
| 82 | return; | |
| 83 | } | |
| 84 | ||
| 85 | stamperType = stamperType.toLowerCase(); | |
| 86 | ||
| 87 | logger.log(Level.INFO, "Input File: {}", inputFile); | |
| 88 | logger.log(Level.INFO, "Template File: {}", templateFile); | |
| 89 | logger.log(Level.INFO, "Output Path: {}", outputPath); | |
| 90 | logger.log(Level.INFO, "Stamper Type: {}", stamperType); | |
| 91 | ||
| 92 | final var context = extractContext(inputFile); | |
| 93 | final var templateStream = extractTemplate(templateFile); | |
| 94 | final var outputStream = createOutputStream(Path.of(outputPath)); | |
| 95 | ||
| 96 | final var stamper = switch (stamperType) { | |
| 97 | case "word" -> OfficeStampers.docxStamper(); | |
| 98 | case "powerpoint" -> ExperimentalStampers.pptxStamper(); | |
| 99 | default -> throw new OfficeStamperException("Invalid stamper type: " + stamperType); | |
| 100 | }; | |
| 101 | ||
| 102 |
1
1. run : removed call to pro/verron/officestamper/api/StreamStamper::stamp → NO_COVERAGE |
stamper.stamp(templateStream, context, outputStream); |
| 103 | } | |
| 104 | ||
| 105 | private Object extractContext(String input) { | |
| 106 |
2
1. extractContext : negated conditional → NO_COVERAGE 2. extractContext : replaced return value with null for pro/verron/officestamper/Main::extractContext → NO_COVERAGE |
if ("diagnostic".equals(input)) return Diagnostic.context(); |
| 107 |
1
1. extractContext : replaced return value with null for pro/verron/officestamper/Main::extractContext → NO_COVERAGE |
return contextualise(Path.of(input)); |
| 108 | } | |
| 109 | ||
| 110 | private InputStream extractTemplate(String template) { | |
| 111 |
2
1. extractTemplate : replaced return value with null for pro/verron/officestamper/Main::extractTemplate → NO_COVERAGE 2. extractTemplate : negated conditional → NO_COVERAGE |
if ("diagnostic".equals(template)) return Diagnostic.template(); |
| 112 |
1
1. extractTemplate : replaced return value with null for pro/verron/officestamper/Main::extractTemplate → NO_COVERAGE |
return streamFile(Path.of(template)); |
| 113 | } | |
| 114 | ||
| 115 | private OutputStream createOutputStream(Path path) { | |
| 116 | try { | |
| 117 |
1
1. createOutputStream : replaced return value with null for pro/verron/officestamper/Main::createOutputStream → NO_COVERAGE |
return newOutputStream(path); |
| 118 | } catch (IOException e) { | |
| 119 | throw new OfficeStamperException(e); | |
| 120 | } | |
| 121 | } | |
| 122 | ||
| 123 | private Object contextualise(Path path) { | |
| 124 |
2
1. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE 2. contextualise : negated conditional → NO_COVERAGE |
if (path.endsWith(".csv")) return processCsv(path); |
| 125 |
2
1. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE 2. contextualise : negated conditional → NO_COVERAGE |
if (path.endsWith(".properties")) return processProperties(path); |
| 126 |
3
1. contextualise : negated conditional → NO_COVERAGE 2. contextualise : negated conditional → NO_COVERAGE 3. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE |
if (path.endsWith(".html") || path.endsWith(".xml")) return processXmlOrHtml(path); |
| 127 |
2
1. contextualise : negated conditional → NO_COVERAGE 2. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE |
if (path.endsWith(".json")) return processJson(path); |
| 128 |
2
1. contextualise : replaced return value with null for pro/verron/officestamper/Main::contextualise → NO_COVERAGE 2. contextualise : negated conditional → NO_COVERAGE |
if (path.endsWith(".xlsx")) return processExcel(path); |
| 129 | throw new OfficeStamperException("Unsupported file type: " + path); | |
| 130 | } | |
| 131 | ||
| 132 | /// Return a list of objects with the csv properties | |
| 133 | private Object processCsv(Path path) { | |
| 134 | try (var reader = new CSVReader(new InputStreamReader(Files.newInputStream(path)))) { | |
| 135 | String[] headers = reader.readNext(); | |
| 136 |
1
1. processCsv : replaced return value with null for pro/verron/officestamper/Main::processCsv → NO_COVERAGE |
return reader.readAll() |
| 137 | .stream() | |
| 138 | .map(row -> { | |
| 139 | Map<String, String> map = new LinkedHashMap<>(); | |
| 140 |
2
1. lambda$processCsv$0 : changed conditional boundary → NO_COVERAGE 2. lambda$processCsv$0 : negated conditional → NO_COVERAGE |
for (int i = 0; i < headers.length; i++) { |
| 141 | map.put(headers[i], row[i]); | |
| 142 | } | |
| 143 |
1
1. lambda$processCsv$0 : replaced return value with Collections.emptyMap for pro/verron/officestamper/Main::lambda$processCsv$0 → NO_COVERAGE |
return map; |
| 144 | }) | |
| 145 | .toList(); | |
| 146 | } catch (IOException | CsvException e) { | |
| 147 | throw new OfficeStamperException(e); | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 151 | private Object processProperties(Path path) { | |
| 152 | var properties = new Properties(); | |
| 153 | try (var inputStream = Files.newInputStream(path)) { | |
| 154 |
1
1. processProperties : removed call to java/util/Properties::load → NO_COVERAGE |
properties.load(inputStream); |
| 155 |
1
1. processProperties : replaced return value with null for pro/verron/officestamper/Main::processProperties → NO_COVERAGE |
return new LinkedHashMap<>(properties.entrySet() |
| 156 | .stream() | |
| 157 |
1
1. lambda$processProperties$0 : replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$0 → NO_COVERAGE |
.collect(Collectors.toMap(e -> String.valueOf(e.getKey()), |
| 158 |
1
1. lambda$processProperties$1 : replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$1 → NO_COVERAGE |
e -> String.valueOf(e.getValue()), |
| 159 |
1
1. lambda$processProperties$2 : replaced return value with "" for pro/verron/officestamper/Main::lambda$processProperties$2 → NO_COVERAGE |
(a, b) -> b, |
| 160 | LinkedHashMap::new))); | |
| 161 | } catch (IOException e) { | |
| 162 | throw new OfficeStamperException(e); | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | private Object processXmlOrHtml(Path path) { | |
| 167 | try { | |
| 168 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
| 169 |
1
1. processXmlOrHtml : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → NO_COVERAGE |
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); |
| 170 |
1
1. processXmlOrHtml : removed call to javax/xml/parsers/DocumentBuilderFactory::setExpandEntityReferences → NO_COVERAGE |
factory.setExpandEntityReferences(false); |
| 171 | ||
| 172 | DocumentBuilder builder = factory.newDocumentBuilder(); | |
| 173 | Document document = builder.parse(Files.newInputStream(path)); | |
| 174 |
1
1. processXmlOrHtml : replaced return value with null for pro/verron/officestamper/Main::processXmlOrHtml → NO_COVERAGE |
return processNode(document.getDocumentElement()); |
| 175 | } catch (ParserConfigurationException | SAXException | IOException e) { | |
| 176 | throw new OfficeStamperException(e); | |
| 177 | } | |
| 178 | } | |
| 179 | ||
| 180 | private Map<String, Object> processNode(Element element) { | |
| 181 | Map<String, Object> result = new LinkedHashMap<>(); | |
| 182 | NodeList children = element.getChildNodes(); | |
| 183 | ||
| 184 |
2
1. processNode : changed conditional boundary → NO_COVERAGE 2. processNode : negated conditional → NO_COVERAGE |
for (int i = 0; i < children.getLength(); i++) { |
| 185 | Node node = children.item(i); | |
| 186 |
1
1. processNode : negated conditional → NO_COVERAGE |
if (node instanceof Element childElement) { |
| 187 | String name = childElement.getTagName(); | |
| 188 |
1
1. processNode : negated conditional → NO_COVERAGE |
if (childElement.hasChildNodes() && childElement.getFirstChild() |
| 189 |
1
1. processNode : negated conditional → NO_COVERAGE |
.getNodeType() != Node.TEXT_NODE) { |
| 190 | result.put(name, processNode(childElement)); | |
| 191 | } | |
| 192 | else { | |
| 193 | result.put(name, childElement.getTextContent()); | |
| 194 | } | |
| 195 | } | |
| 196 | } | |
| 197 |
1
1. processNode : replaced return value with Collections.emptyMap for pro/verron/officestamper/Main::processNode → NO_COVERAGE |
return result; |
| 198 | } | |
| 199 | ||
| 200 | private Object processJson(Path path) { | |
| 201 | try { | |
| 202 | ObjectMapper mapper = new ObjectMapper(); | |
| 203 | TypeReference<LinkedHashMap<String, Object>> typeRef = new TypeReference<>() {}; | |
| 204 |
1
1. processJson : replaced return value with null for pro/verron/officestamper/Main::processJson → NO_COVERAGE |
return mapper.readValue(Files.newInputStream(path), typeRef); |
| 205 | } catch (IOException e) { | |
| 206 | throw new OfficeStamperException(e); | |
| 207 | } | |
| 208 | } | |
| 209 | ||
| 210 | private Object processExcel(Path path) { | |
| 211 | try { | |
| 212 |
1
1. processExcel : replaced return value with null for pro/verron/officestamper/Main::processExcel → NO_COVERAGE |
return ExcelContext.from(Files.newInputStream(path)); |
| 213 | } catch (IOException e) { | |
| 214 | throw new OfficeStamperException(e); | |
| 215 | } | |
| 216 | } | |
| 217 | } | |
Mutations | ||
| 67 |
1.1 |
|
| 72 |
1.1 |
|
| 80 |
1.1 2.2 |
|
| 102 |
1.1 |
|
| 106 |
1.1 2.2 |
|
| 107 |
1.1 |
|
| 111 |
1.1 2.2 |
|
| 112 |
1.1 |
|
| 117 |
1.1 |
|
| 124 |
1.1 2.2 |
|
| 125 |
1.1 2.2 |
|
| 126 |
1.1 2.2 3.3 |
|
| 127 |
1.1 2.2 |
|
| 128 |
1.1 2.2 |
|
| 136 |
1.1 |
|
| 140 |
1.1 2.2 |
|
| 143 |
1.1 |
|
| 154 |
1.1 |
|
| 155 |
1.1 |
|
| 157 |
1.1 |
|
| 158 |
1.1 |
|
| 159 |
1.1 |
|
| 169 |
1.1 |
|
| 170 |
1.1 |
|
| 174 |
1.1 |
|
| 184 |
1.1 2.2 |
|
| 186 |
1.1 |
|
| 188 |
1.1 |
|
| 189 |
1.1 |
|
| 197 |
1.1 |
|
| 204 |
1.1 |
|
| 212 |
1.1 |