Contextualizer.java
package pro.verron.officestamper;
import com.fasterxml.jackson.core.JsonFactory;
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.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import pro.verron.officestamper.api.OfficeStamperException;
import pro.verron.officestamper.excel.ExcelContext;
import pro.verron.officestamper.excel.ExcelMergeStrategy;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.IntStream;
import static java.util.stream.Collectors.toMap;
import static pro.verron.officestamper.PathUtils.baseName;
import static pro.verron.officestamper.PathUtils.extension;
public class Contextualizer {
static Object contextualise(Path path, ExcelStrategy excelStrategy) {
var extension = extension(path);
return switch (extension) {
case "csv" -> processCsv(path);
case "properties" -> processProperties(path);
case "html", "xml" -> processXmlOrHtml(path);
case "json" -> processJson(path);
case "yaml", "yml" -> processYaml(path);
case "xlsx" -> processExcel(path, excelStrategy);
default -> throw new OfficeStamperException("Unsupported file type: " + path);
};
}
static boolean isSupportedDataFile(Path p) {
var extension = extension(p.getFileName());
return extension.equals("csv") || //
extension.equals("properties") || //
extension.equals("html") || //
extension.equals("xml") || //
extension.equals("json") || //
extension.equals("yaml") || //
extension.equals("yml") || //
extension.equals("xlsx");
}
/// Return a list of objects with the csv properties
static Object processCsv(Path path) {
try (var inputStream = Files.newInputStream(path); var streamReader = new InputStreamReader(inputStream); var reader = new CSVReader(streamReader);) {
var headers = reader.readNext();
return reader.readAll().stream().map(row -> mapRowToHeaders(row, headers)).toList();
} catch (IOException | CsvException e) {
throw new OfficeStamperException(e);
}
}
static Object processProperties(Path path) {
var properties = new Properties();
try (var inputStream = Files.newInputStream(path)) {
properties.load(inputStream);
return properties.entrySet()
.stream()
.collect(toMap(e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue()), (a, b) -> b, LinkedHashMap::new));
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
static Object processXmlOrHtml(Path path) {
try {
var factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setExpandEntityReferences(false);
var builder = factory.newDocumentBuilder();
var document = builder.parse(Files.newInputStream(path));
return processNode(document.getDocumentElement());
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new OfficeStamperException(e);
}
}
static Map<String, Object> processNode(Element element) {
var result = new LinkedHashMap<String, Object>();
var children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
var node = children.item(i);
if (node instanceof Element childElement) {
var name = childElement.getTagName();
if (!childElement.hasChildNodes()) {
result.put(name, childElement.getTextContent());
} else {
var firstChild = childElement.getFirstChild();
if (firstChild.getNodeType() == Node.TEXT_NODE) {
result.put(name, childElement.getTextContent());
} else {
result.put(name, processNode(childElement));
}
}
}
}
return result;
}
static Object processJson(Path path) {
try {
var mapper = SerializationUtils.newMapper();
var typeRef = new TypeReference<LinkedHashMap<String, Object>>() {
};
return mapper.readValue(Files.newInputStream(path), typeRef);
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
static Object processYaml(Path path) {
try {
// Lazy YAML support using Jackson if available on classpath;
// else, provide a clear error.
var yaml = "com.fasterxml.jackson.dataformat.yaml.YAMLFactory";
var mapperClass = Class.forName(yaml);
var declaredConstructor = mapperClass.getDeclaredConstructor();
var jsonFactory = (JsonFactory) declaredConstructor.newInstance();
var mapper = new ObjectMapper(jsonFactory);
var typeRef = new TypeReference<LinkedHashMap<String, Object>>() {
};
return mapper.readValue(Files.newInputStream(path), typeRef);
} catch (ClassNotFoundException e) {
var msg = "YAML support requires 'jackson-dataformat-yaml' on the" + " classpath";
throw new OfficeStamperException(msg);
} catch (Exception e) {
throw new OfficeStamperException(e);
}
}
static Object processExcel(Path path, ExcelStrategy excelStrategy) {
try (var is = Files.newInputStream(path)) {
var ctx = ExcelContext.from(is);
if (ExcelMergeStrategy.JOIN == excelStrategy.getMergeStrategy()) {
return ctx.joinAllSheets(excelStrategy.getJoinKey());
}
return ctx;
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
static Map<String, String> mapRowToHeaders(String[] row, String[] headers) {
return IntStream.range(0, headers.length)
.boxed()
.collect(toMap(i -> headers[i], i -> row[i], (_, b) -> b, LinkedHashMap::new));
}
static Object contextualize(ExcelStrategy excelStrategy, Path path) {
return Files.isDirectory(path) ? contextualiseDirectory(path, excelStrategy) : contextualise(path, excelStrategy);
}
static Map<String, Object> contextualiseDirectory(Path dir, ExcelStrategy excelStrategy) {
try (var stream = Files.list(dir)) {
return stream.filter(Files::isRegularFile)
.filter(Contextualizer::isSupportedDataFile)
.collect(toMap(PathUtils::baseName, path -> contextualise(path, excelStrategy), (_, b) -> b, TreeMap::new));
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
static Map<String, Object> contextualiseDirectoryRecursive(Path dir, ExcelStrategy excelStrategy) {
try (var stream = Files.walk(dir)) {
return stream.filter(Files::isRegularFile)
.filter(Contextualizer::isSupportedDataFile)
.collect(toMap(PathUtils::baseName, path -> contextualise(path, excelStrategy), (_, b) -> b, TreeMap::new));
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
static List<Item> contextualizeDir(Path dir, ExcelStrategy excelStrategy) {
try (var stream = Files.list(dir)) {
return stream.sorted().map(entry -> contextualize(entry, excelStrategy)).toList();
} catch (IOException e) {
throw new OfficeStamperException(e);
}
}
private static Item contextualize(Path entry, ExcelStrategy excelStrategy) {
if (Files.isRegularFile(entry) && isSupportedDataFile(entry)) {
return new Item(baseName(entry), contextualise(entry, excelStrategy));
} else if (Files.isDirectory(entry)) {
return new Item(baseName(entry), contextualiseDirectoryRecursive(entry, excelStrategy));
} else throw new RuntimeException("Invalid entry: " + entry);
}
}