package es.uv.saic.service; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.io.UncheckedIOException; import java.math.BigInteger; import java.nio.file.Files; import java.text.DecimalFormat; import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import es.uv.saic.domain.Indicador; import es.uv.saic.domain.Plantilla; import es.uv.saic.domain.PlantillaComentario; import es.uv.saic.domain.PlantillaConversation; import es.uv.saic.domain.PlantillaRepository; import es.uv.saic.dto.IndicadorEnquestaDTO; import es.uv.saic.dto.IndicadorEnquestaValorDTOImp; import es.uv.saic.dto.InstanciaTascaDTO; import es.uv.saic.dto.NomProcesOrganDTO; import es.uv.saic.dto.OrganTransferDTO; import es.uv.saic.feign.CoreClient; @Service public class PlantillaService { @Autowired private PlantillaRepository r; @Value("${saic.data.filePath}") private String filePath; @Value("${saic.data.tmpPath}") private String tmpPath; @Value("${saic.data.templates.fileNotFound}") private String fileNotFound; @Value("${saic.data.templates.filePath}") private String templatePath; @Value("${saic.data.templates.logoPath}") private String logoPath; @Autowired private CoreClient core; private static final DecimalFormat df = new DecimalFormat("0.00"); public List findAll(){ return this.r.findAll(); } public Plantilla findByID(Integer id) { return this.r.findByIdPlantilla(id); } public Plantilla findByVersioCodiAmbit(Integer versio, String codi, String ambit) { return this.r.findByVersioCodiAmbit(versio, codi, ambit); } public void save(Plantilla p) { this.r.save(p); this.r.flush(); } public void delete(Plantilla p) { this.r.delete(p); } public boolean isUsed(Integer idPlantilla) { List l = this.r.findUsedByIdPlantilla(idPlantilla); if(l.size() > 0) { return true; } return false; } public String addTemplateData(InstanciaTascaDTO it, String template){ HashMap context = new HashMap(); HashMap header = new HashMap(); HashMap img = new HashMap(); Integer idTitulacio = it.getInstancia().getTitulacio(); Integer cursAvaluat = it.getInstancia().getCursAvaluat(); Integer idCentre = it.getInstancia().getCentre(); String ambit = "G"; String opcionsStr = it.getTasca().getOpcions().replaceAll("^\\.$", ""); String [] opcions = new String[0]; if(!opcionsStr.isEmpty()) { opcions = opcionsStr.split(";"); } if(it.getInstancia().getTlugar().equals("C")) { if(it.getInstancia().getTitulacio() == 2) { ambit = "M"; } } /** Add header information **/ addHeaderData(it.getInstancia().getNomval(), it.getInstancia().getLugar(), it.getInstancia().getNomValTitulacio(), it.getInstancia().getCursAvaluat(), header, img); /** Iterate elements inside {[loop]} ... {[endloop]} **/ template = this.iterateLoopTag(template, idCentre, ambit, cursAvaluat, context); /** Iterate elements with data-loop attribute **/ Document doc = Jsoup.parse(template); doc = iterateLoopAttr(idCentre, cursAvaluat, doc, context); /** Add template data (non iterated data)**/ getTemplateData(idTitulacio, idCentre, cursAvaluat, context); this.replaceValues(doc, context, header, img); /** Replace sections if specified in options **/ this.replaceSection(opcions, it, doc); return doc.html(); } public String addTemplateData(Integer idTitulacio, Integer idCentre, Integer curs, String template){ HashMap context = new HashMap(); HashMap header = new HashMap(); HashMap img = new HashMap(); OrganTransferDTO centre = core.findOrganById("C", idCentre); OrganTransferDTO titulacio = core.findOrganById("T", idTitulacio); /** Add header information **/ addHeaderData(centre.getNomVal(), centre.getLugarCentre(), titulacio.getNomVal(), curs, header, img); /** Iterate elements inside {[loop]} ... {[endloop]} **/ template = this.iterateLoopTag(template, idCentre, titulacio.getTambit(), curs, context); /** Iterate elements with data-loop attribute **/ Document doc = Jsoup.parse(template); doc = iterateLoopAttr(idCentre, curs, doc, context); /** Add template data (non iterated data)**/ getTemplateData(idTitulacio, idCentre, curs, context); this.replaceValues(doc, context, header, img); return doc.html(); } private void addHeaderData(String nomVal, Integer lugar, String nomValTitulacion, Integer curs, HashMap header, HashMap img) { header.put("centre", nomVal); header.put("titulacio", nomValTitulacion); header.put("curs", "CURSO "+Integer.toString(curs-1)+" - "+Integer.toString(curs)); header.put("curs_anterior", Integer.toString(curs-2)+" - "+Integer.toString(curs-1)); header.put("conv-ant1", "CONVOCATORIA "+Integer.toString(curs-1)+" - "+Integer.toString(curs)); header.put("conv-ant2", "CONVOCATORIA "+Integer.toString(curs-2)+" - "+Integer.toString(curs-1)); header.put("periode-ant1", Integer.toString(curs-6)+" - "+Integer.toString(curs-1)); header.put("periode-ant2", Integer.toString(curs-7)+" - "+Integer.toString(curs-2)); ClassPathResource fuv = new ClassPathResource("/static/logos/UV.png"); ClassPathResource fc = new ClassPathResource("/static/logos/C"+Integer.toString(lugar)+".png"); String logouv_b64; String logoc_b64; try { logouv_b64 = "data:image/png;base64, "+Base64.getEncoder().encodeToString(fuv.getInputStream().readAllBytes()); } catch (IOException e) { logouv_b64 = "https://saic.uv.es/public/logos/UV.png"; } try { logoc_b64 = "data:image/png;base64, "+Base64.getEncoder().encodeToString(fc.getInputStream().readAllBytes()); } catch (IOException e) { logoc_b64 = "https://saic.uv.es/public/logos/C"+Integer.toString(lugar)+".png"; } img.put("logo_centre", logoc_b64); img.put("logo_uv", logouv_b64); } private Document iterateLoopAttr(Integer idCentre, Integer curs, Document doc, HashMap context) { Elements loop_elements = doc.select("*[data-loop]"); for(Element e : loop_elements) { String Tambit = e.attr("data-loop"); List tits = core.getTitulacionsByCentreTambit(idCentre, Tambit); for(OrganTransferDTO org : tits) { context.clear(); Element e_copy = e.clone(); getTemplateData(org.getLugarTitulacion(), org.getLugarCentre(), curs, context); context.put("titulacio_loop", org.getNomVal()); Elements ielements = e_copy.select("*:matchesWholeOwnText(\\{\\{([^\\}]*)\\}\\})"); for(Element ielem : ielements) { String t = ielem.html().replace("{", "").replace("}", ""); String i = this.formatValue(context.get(t)); if(i != null) { if(ielem.tagName().equals("td")) { ielem.attr("class", "mceEditable"); ielem.attr("id", "ind_"+t); ielem.html(i); } else { ielem.html(i); } } else { if(ielem.tagName().equals("td")) { ielem.attr("class", "mceEditable"); ielem.attr("id", "ind_"+t); ielem.html(""); } else { ielem.html(""); } } } e.after(e_copy); } e.remove(); } context.clear(); return doc; } private String iterateLoopTag(String template, Integer idCentre, String Tambit, Integer curs, HashMap context) { Pattern pattern = Pattern.compile(".*

\\{\\[loop\\]\\}

([\\S\\s]*)

\\{\\[endloop\\]\\}

.*"); Matcher matcher = pattern.matcher(template); String replaced = ""; while(matcher.find()) { String group = matcher.group(1); Document tab = Jsoup.parse(group); List tits = core.getTitulacionsByCentreTambit(idCentre, Tambit); for(OrganTransferDTO t : tits) { Document tabcopy = Jsoup.parse(tab.html()); context.clear(); getTemplateData(t.getLugarTitulacion(), t.getLugarCentre(), curs, context); context.put("titulacio_loop", t.getNomVal()); tabcopy = this.replaceValuesLoop(tabcopy, context); replaced += tabcopy.html()+"

"; } template = template.replace("

{[loop]}

"+group+"

{[endloop]}

", replaced); } context.clear(); return template; } private Document replaceValues(Document doc, HashMap context, HashMap header, HashMap img) { Elements elems = doc.select("*:matchesWholeOwnText(\\{\\{([^\\}]*)\\}\\})"); for(Element e : elems) { String t = e.html().replace("{", "").replace("}", ""); if(header.containsKey(t)) { e.html(header.get(t)); } else if(img.containsKey(t)) { Element x = new Element("img"); x.attr("src", img.get(t)); x.attr("style", "display:block; margin-left:auto; margin-right:auto; width:100px; max-width:100px;"); x.attr("class", "logo"); e.html(""); e.appendChild(x); } else { String i = this.formatValue(context.get(t)); if(i != null) { if(e.parent().tagName().equals("td")) { e.parent().attr("class", "mceEditable"); e.parent().attr("id", "ind_"+t); e.parent().attr("style", e.attr("style")); e.parent().html(i); } else { e.attr("class", "mceEditable"); e.attr("ind", "ind_"+t); e.html(i); } } else { if(e.parent().tagName().equals("td")) { e.parent().attr("class", "mceEditable"); e.parent().attr("id", "ind_"+t); e.parent().attr("style", e.attr("style")); e.parent().html(""); } else { e.attr("class", "mceEditable"); e.attr("ind", "ind_"+t); e.html(""); } } } } return doc; } private void getTemplateData(Integer idTitulacio, Integer idCentre, Integer curs, HashMap context) { /* Indicadores del data warehouse */ try { List indicadores; indicadores = core.getFromTitulacion(idTitulacio, curs); for(Indicador i : indicadores) { context.put(i.getIndicador(), i.getValor()); } } catch(Exception e) { } /* Indicadores de encuestas y otros almacenados en BD */ IndicadorEnquestaDTO indicadorEnquestaDTO = new IndicadorEnquestaDTO(idTitulacio, idCentre, curs); List enquestesT = core.getAllInds2(indicadorEnquestaDTO); for(IndicadorEnquestaValorDTOImp i : enquestesT) { String indicador = i.getAmbit().toLowerCase().equals("t") ? (i.getEnquesta().toLowerCase()+"_"+i.getIndicador().toLowerCase()) : (i.getEnquesta().toLowerCase()+"_"+i.getIndicador().toLowerCase()+"_"+i.getAmbit().toLowerCase()); if(i.getNum() == null) { indicador = i.getTipus().toLowerCase().equals("avg") ? indicador : (indicador += "_"+i.getTipus().toLowerCase()); indicador = i.getCursd() == null ? indicador : (indicador += "_"+i.getCursd().toLowerCase()); context.put(indicador, formatValue(i.getValor())); } else { context.put(indicador, i.getNum()); } } } private Document replaceValuesLoop(Document doc, HashMap context) { Elements elems = doc.select("*:matchesWholeOwnText(\\{\\{([^\\}]*)\\}\\})"); for(Element e : elems) { String t = e.html().replace("{", "").replace("}", ""); String i = this.formatValue(context.get(t)); if(i != null) { if(e.parent().tagName().equals("td")) { e.parent().attr("class", "mceEditable"); e.parent().attr("id", "ind_"+t); e.parent().attr("style", e.attr("style")); e.parent().html(i); } else { e.attr("class", "mceEditable"); e.attr("ind", "ind_"+t); e.html(i); } } else { if(e.parent().tagName().equals("td")) { e.parent().attr("class", "mceEditable"); e.parent().attr("id", "ind_"+t); e.parent().attr("style", e.attr("style")); e.parent().html(""); } else { e.attr("class", "mceEditable"); e.attr("ind", "ind_"+t); e.html(""); } } } return doc; } private String formatValue(String v) { if(v == null) return ""; if(v.isEmpty() | v.isBlank()) return ""; if(v.equals("NP")) return "NP"; try { double d = Double.parseDouble(v); return (Integer.toString((int)d).equals(v) ? v : df.format(d).replace(",", ".")); } catch (NumberFormatException e) { } if(v.endsWith("%") && v.startsWith(".")) { return "0"+v; } return v; } public String savePDF(String content, BigInteger idtascai) throws IOException, InterruptedException { content = content.replace("

", "

"); Document d = Jsoup.parse(content, "UTF8"); d.head().append(""); List trs = d.getElementsByTag("tr"); for(Element t : trs) { if(t.hasAttr("height")) { t.removeAttr("height"); } if(t.hasAttr("style")) { String style = t.attr("style"); style = style.replaceAll("height:[ \\d.pxcmin]{1,};", ""); t.attr("style", style); } } List tds = d.getElementsByTag("td"); for(Element t : tds) { if(t.hasAttr("height")) { t.removeAttr("height"); } if(t.hasAttr("style")) { String style = t.attr("style"); style = style.replaceAll("height:[ \\d.pxcmin]{1,};", ""); t.attr("style", style); } } List ignore = d.getElementsByClass("pdfignore"); for(Element e: ignore) { e.remove(); } InstanciaTascaDTO ita = core.findInstanciaTascaById2(idtascai); if(ita.getTasca().getNomRol().equals("u_uq")) { d.body().append(this.parseComments(d.html())); } String basecommand = "google-chrome --headless --disable-gpu --no-pdf-header-footer --run-all-compositor-stages-before-draw --no-sandbox"; String dst = idtascai.toString()+".pdf"; File src = File.createTempFile("saic-pdfexport-", ".tmp.html", new File(tmpPath)); src.deleteOnExit(); PrintWriter out = new PrintWriter(src.getAbsolutePath()); out.println(d.html()); out.flush(); out.close(); System.out.println(basecommand+" --print-to-pdf='"+filePath+dst+"' "+src.getAbsolutePath()); ProcessBuilder pb = new ProcessBuilder("bash", "-c", basecommand+" --print-to-pdf='"+filePath+dst+"' "+src.getAbsolutePath()); Process pr = pb.start(); pr.waitFor(); src.delete(); return dst; } public byte[] toPDF(String content, Optional idtascai) throws IOException, InterruptedException { content = content.replace("

", "

"); Document d = Jsoup.parse(content, "UTF8"); d.head().append(""); List trs = d.getElementsByTag("tr"); for(Element t : trs) { if(t.hasAttr("height")) { t.removeAttr("height"); } if(t.hasAttr("style")) { String style = t.attr("style"); style = style.replaceAll("height:[ \\d.pxcmin]{1,};", ""); t.attr("style", style); } } List tds = d.getElementsByTag("td"); for(Element t : tds) { if(t.hasAttr("height")) { t.removeAttr("height"); } if(t.hasAttr("style")) { String style = t.attr("style"); style = style.replaceAll("height:[ \\d.pxcmin]{1,};", ""); t.attr("style", style); } } List ignore = d.getElementsByClass("pdfignore"); for(Element e: ignore) { e.remove(); } if(idtascai.isPresent()) { InstanciaTascaDTO ita = core.findInstanciaTascaById2(idtascai.get()); if(ita.getTasca().getNomRol().equals("u_uq")) { d.body().append(this.parseComments(d.html())); } } d.body().append(this.parseComments(d.html())); String basecommand = "google-chrome --headless --disable-gpu --no-pdf-header-footer --run-all-compositor-stages-before-draw --no-sandbox"; File dst = File.createTempFile("saic-pdfpreview-", ".tmp.pdf", new File(tmpPath)); File src = File.createTempFile("saic-pdfpreview-", ".tmp.html", new File(tmpPath)); src.deleteOnExit(); dst.deleteOnExit(); PrintWriter out = new PrintWriter(src.getAbsolutePath()); out.println(d.html()); out.flush(); out.close(); ProcessBuilder pb = new ProcessBuilder("bash", "-c", basecommand+" --print-to-pdf='"+dst+"' "+src.getAbsolutePath()); Process pr = pb.start(); pr.waitFor(); byte[] bytes = Files.readAllBytes(dst.toPath()); src.delete(); dst.delete(); return bytes; } private String parseComments(String content) throws JsonParseException, JsonMappingException, IOException { // Pattern pattern = Pattern.compile("\\<\\!\\-\\-tinycomments\\|2\\.1\\|data\\:application\\/json\\;base64\\,([A-Za-z0-9\\/\\+\\\\]*)\\=*\\-\\-\\>"); Matcher matcher = pattern.matcher(content); String rawComments = ""; if(matcher.find()) { byte[] decoded = Base64.getDecoder().decode(matcher.group(1)); rawComments = new String(decoded, "UTF-8"); if(rawComments.length() < 10) { return ""; } rawComments = rawComments.replaceAll("^.", "["); rawComments = rawComments.replaceAll(".$", "]"); rawComments = rawComments.replaceAll("\"mce-conversation\\_\\d*\":", ""); ObjectMapper mapper = new ObjectMapper(); List comments = mapper.readValue(rawComments, new TypeReference>(){}); String tabComments = "

" + "

COMENTARIOS GENERADOS DURANTE LA REVISIÓN DEL DOCUMENTO

" + "

Nota: Esta página no será visible en la versión final publicada del documento.

" + "" + "" + " " + " " + " " + " " + " " + " " + ""; for(PlantillaConversation conv: comments) { for(PlantillaComentario c: conv.getComments()) { tabComments += (" " + " " + " " + " " + " "); } } tabComments += "
UsuarioNombreComentario
"+c.getAuthor()+""+c.getAuthorName()+""+c.getContent()+"
"; return tabComments; } return ""; } public Document replaceSection(String[] opcions, InstanciaTascaDTO it, Document doc) { if(opcions.length > 0) { NomProcesOrganDTO nomProcesOrganDTO = new NomProcesOrganDTO(opcions[0], it.getInstancia().getTlugar(), it.getInstancia().getLugar(), it.getInstancia().getCentre(), it.getInstancia().getTitulacio()); InstanciaTascaDTO itOld = core.getReportFromNomProcesOrgan(nomProcesOrganDTO); try { Document doc2 = Jsoup.parse(itOld.getText()); Elements target = null; Elements source = null; if(opcions.length == 2) { target = this.extractTemplateAnchor(doc2, true, opcions[1]); source = this.extractTemplateAnchor(doc, false, opcions[1]); } else { target = this.extractTemplateAnchor(doc2, true, opcions[1], opcions[2]); source = this.extractTemplateAnchor(doc, false, opcions[1], opcions[2]); } if(target == null || source == null) { return doc; } Element e = source.get(0); e.before(this.clear(target.outerHtml())); source.remove(); } catch(NullPointerException e){ System.out.println("No previous version found for "+it.getIdInstanciaTasca().toString()); } } return doc; } public Elements extractTemplateAnchor(Document doc, Boolean target, String... args) { // /* Hardcoded to fix anchor positions in existing templates */ try{ String anchor_ini = args[0]; Element eFirst = doc.select("p:has(a#"+anchor_ini+")").first(); Elements elems = eFirst.nextElementSiblings(); Elements table = elems.select("table:not(.pdfignore)"); return table; } catch(Exception e){ return null; } /* Use this code in future */ /* if(args.length == 2){ String anchor_end = args[1]; Element eLast = doc.select("p:has(a#"+anchor_end+")").first(); boolean remove = false; for(Iterator iter = elems.iterator(); iter.hasNext();){ Element x = iter.next(); if(remove){ iter.remove(); } if(x.id().equals(anchor_end) || x.html().contains(anchor_end)){ remove = true; } } } */ } public String clear(String html){ return html.replaceAll("data-mce-style=\"[ ,;:.\\d\\w\\(\\)\\#\\%-]{1,}\"", "") .replaceAll("font-family:[ \\d\\w-,]{1,};", "") .replaceAll("font-size:[ \\d\\w,\\.]{1,};", "") .replaceAll("\\<\\!\\-\\-tinycomments\\|2\\.1\\|data\\:application\\/json\\;base64\\,[A-Za-z0-9\\/\\+\\\\]*\\=*\\-\\-\\>", "") .replaceAll("data\\-mce\\-annotation\\-uid\\=\"mce\\-conversation\\_[a-zA-Z0-9]+\"", "") .replaceAll("data\\-mce\\-annotation\\=\"tinycomments\"", "") .replaceAll("class=\"mce-annotation\"", ""); } public static String asString(Resource resource) { try (Reader reader = new InputStreamReader(resource.getInputStream(), "UTF-8")) { return FileCopyUtils.copyToString(reader); } catch (IOException e) { throw new UncheckedIOException(e); } } public static String readFileToString(String path) { ResourceLoader resourceLoader = new DefaultResourceLoader(); Resource resource = resourceLoader.getResource(path); return asString(resource); } }