Hace poco tuve que manejar contratos que se firmarían por N número de usuarios, por lo que tuve que buscar una forma de tener un solo documento el cuál sería mi plantilla y de ahí generar el PDF personalizado para cada usuario.
Para este post usaremos Maven para el manejo de dependencias. Las librerías necesarias para poder realizar esto son Apache POI y xdocreport de Opensagres.
Las tareas las separé en dos clases, una para el manejo del archivo Word y la otra para la generación del PDF.
Lo primero que debemos hacer es incluir las librerías en el archivo pom.xml, como se muestra a continuación:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId> org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.poi.xwpf.converter.pdf</artifactId>
<version>2.0.2</version>
</dependency>
Ahora debemos actualizar nuestro proyecto haciendo clic derecho en él, en la opción de maven y update project.
Ya que nuestro proyecto se ha actualizado y tiene las dependencias instaladas, procedemos a modificar nuestras clases Java.
Comenzaremos con la clase DocxHandling la cual se encargará de abrir el documento Word que no llegue y comenzará a sustituir los textos párrafo por párrafo.
public InputStream replaceTexts() throws Exception {
//Validamos que hayamos recibido el path del documento original
if(originalFilePath == null) throw new NullPointerException("Debe proporcionar el path del archivo original");
//Validamos que hayamos recibido los textos a reemplazar
if(textsToReplace == null || textsToReplace.isEmpty()) throw new Exception("Debe proporcionar los textos a reemplazar");
//Generamos un nombre para nuestro archivo temporal
temporalFileName = UUID.randomUUID().toString();
//Abrimos nuestro documento
doc = new XWPFDocument(new FileInputStream(originalFilePath));
//comenzamos con la iteracion de los textos a reemplazar
Iterator it = textsToReplace.keySet().iterator();
while(it.hasNext()) {
String item = (String) it.next();
//Enviamos a reemplazar el texto y guardamos sustituimos el documento en la misma variable
doc = replaceText(doc, item, textsToReplace.get(item));
}
//Una vez se haya terminado de reemplazar, guardamos el documento en un archivo temporal
//Para ello general el archivo temporal que pasaremos a nuestro metodo que se encarga de realizar la escritura
String tmpDestinationPath = Files.createTempFile(temporalFileName, "." + Constantes.EXTENSION_DOCX).toAbsolutePath().toString();
//Guardamos el documento en el archivo
saveWord(tmpDestinationPath,
doc);
//Retornamos un ImputStream por si el usuario va a trabajar con el
return new FileInputStream(tmpDestinationPath);
}
A continuación, muestro el método que se encarga de recorrer el archivo y sustituir los textos por el valor recibido:
private static XWPFDocument replaceText(XWPFDocument doc, String findText, String replaceText){
//Realizamos el recorrido de todos los parrafos
for (int i = 0; i < doc.getParagraphs().size(); ++i ) {
//Asignamos el parrafo a una variable para trabajar con ella
XWPFParagraph s = doc.getParagraphs().get(i);
//De ese parrafo recorremos todos los Runs
for (int x = 0; x < s.getRuns().size(); x++) {
//Asignamos el run en turno a una varibale
XWPFRun run = s.getRuns().get(x);
//Obtenemos el texto
String text = run.text();
//Validamos si el texto contiene el key a sustituir
if(text.contains(findText)) {
//Si lo contiene lo reemplazamos y guardamos en una variable
String replaced = run.getText(run.getTextPosition()).replace(findText, replaceText);
//Pasamos el texto nuevo al run
run.setText(replaced, 0);
}
}
}
//Retornamos el documento con los textos ya reemplazados
return doc;
}
Por último, les voy a mostrar el método que se encarga de escribir el documento en el archivo temporal que hemos creado:
private static void saveWord(String filePath, XWPFDocument doc) throws FileNotFoundException, IOException{
FileOutputStream out = null;
try{
out = new FileOutputStream(filePath);
doc.write(out);
}
finally{
out.close();
}
}
El método anterior no lo comenté ya que es muy sencillo.
Ahora la clase DocxToPdfConverter, que se encarga de convertir nuestro archivo Word (.docx) a PDF . Esta clase es muy sencilla, ya que solamente se debe abrir el documento y se le pasa a la librería para que realice la conversión, a continuación, les muestro el método:
public InputStream convert(InputStream in) {
try {
//Generamos nuestro archivo temporal donde se guardara el PDF
File tmpFile = Files.createTempFile(UUID.randomUUID().toString(), "." + Constantes.EXTENSION_PDF).toFile();
//Abrimos el documento word que recibimos a traves del InputStream
XWPFDocument document = new XWPFDocument(in);
//Generamos la instancia de las opciones del PDF, en este caso la de default
PdfOptions options = PdfOptions.create();
//Generamos nuestro OputStream que se encargará de hacer la escritura en el archivo
OutputStream out = new FileOutputStream(tmpFile);
//Paramos nuestro documento, el output y las opciones a la librería
PdfConverter.getInstance().convert(document, out, options);
//En este punto el archivo temporal ya contiene nuestro PDF y lo retornamos como un InputStream
return new FileInputStream(tmpFile);
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
return null;
}
En los métodos de ambas clases yo trabajo con Streams ya que tengo otro servicio que se encarga de persistir los archivos, pero también podemos trabajar con rutas (paths) para indicar donde queremos que se guarden los archivos generados.
Aquí dejo el enlace al repositorio con los archivos mencionados.