/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.ims.qti.render; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.util.Locale; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.RuntimeConstants; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.dom.DOMDocument; import org.dom4j.io.DocumentSource; import org.olat.core.dispatcher.impl.StaticMediaDispatcher; import org.olat.core.gui.translator.Translator; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.Util; import org.olat.core.util.i18n.I18nManager; import org.olat.ims.qti.QTI12ResultDetailsController; import org.olat.ims.resources.IMSEntityResolver; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; /** * @author Mike Stock Comment: * Initial Date: 04.06.2003 */ public class LocalizedXSLTransformer { private static ConcurrentHashMap<String, LocalizedXSLTransformer> instanceHash = new ConcurrentHashMap<String, LocalizedXSLTransformer>(5); private static OLog log = Tracing.createLoggerFor(LocalizedXSLTransformer.class); private static EntityResolver er = new IMSEntityResolver(); private static VelocityEngine velocityEngine; static { // init velocity engine Properties p = null; try { velocityEngine = new VelocityEngine(); p = new Properties(); p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.SimpleLog4JLogSystem"); p.setProperty("runtime.log.logsystem.log4j.category", "syslog"); velocityEngine.init(p); } catch (Exception e) { throw new OLATRuntimeException("config error with velocity properties::" + p.toString(), e); } } private Translator pT; private Templates templates; /** * <code>RESULTS2HTML</code> */ private static final String XSLFILENAME = "results2html_generic.xsl"; /** * Private constructor, use getInstance to get an instance of the * LocalizedXSLTransformer * * @param trans */ private LocalizedXSLTransformer(Translator trans) { pT = trans; initTransformer(); } /** * Get a localized transformer instance. * * @param locale The locale for this transformer instance * @return A localized transformer */ // cluster_ok only in VM public static LocalizedXSLTransformer getInstance(Locale locale) { LocalizedXSLTransformer instance = instanceHash.get(I18nManager.getInstance().getLocaleKey(locale)); if (instance == null) { Translator trans = Util.createPackageTranslator(QTI12ResultDetailsController.class, locale); LocalizedXSLTransformer newInstance = new LocalizedXSLTransformer(trans); instance = instanceHash.putIfAbsent(I18nManager.getInstance().getLocaleKey(locale), newInstance); //see javadoc of ConcurrentHashMap if(instance == null) { //newInstance was put into the map instance = newInstance; } } return instance; } /** * Render with a localized stylesheet. The localized stylesheet is addressed * by its name with appended locale. E.g. mystyle.xsl in DE locale is * addressed by mystyle_de.xsl * * @param node The node to render * @param styleSheetName The stylesheet to use. * @return Results of XSL transformation */ private String render(Element node) { try { Document doc = node.getDocument(); if (doc == null) { doc = new DOMDocument(); doc.add(node); } DocumentSource xmlsource = new DocumentSource(node); StringWriter sw = new StringWriter(); StreamResult result = new StreamResult(sw); templates.newTransformer().transform(xmlsource, result); return sw.toString(); } catch (Exception e) { throw new OLATRuntimeException(LocalizedXSLTransformer.class, "Error transforming XML.", e); } } /** * Render results processing document * * @param doc The <results/>document * @return transformation results */ public String renderResults(Document doc) { return render(doc.getRootElement()); } /** * Helper to create XSLT transformer for this instance */ private void initTransformer() { // translate xsl with velocity Context vcContext = new VelocityContext(); vcContext.put("t", pT); vcContext.put("staticPath", StaticMediaDispatcher.createStaticURIFor("")); String xslAsString = ""; try(InputStream xslin = getClass().getResourceAsStream("/org/olat/ims/resources/xsl/" + XSLFILENAME)) { xslAsString = slurp(xslin); } catch (IOException e) { log.error("Could not convert xsl to string!", e); } String replacedOutput = evaluateValue(xslAsString, vcContext); TransformerFactory tfactory = TransformerFactory.newInstance(); XMLReader reader; try { reader = XMLReaderFactory.createXMLReader(); reader.setEntityResolver(er); Source xsltsource = new SAXSource(reader, new InputSource(new StringReader(replacedOutput))); templates = tfactory.newTemplates(xsltsource); } catch (SAXException e) { throw new OLATRuntimeException("Could not initialize transformer!", e); } catch (TransformerConfigurationException e) { throw new OLATRuntimeException("Could not initialize transformer (wrong config)!", e); } } /** * Takes String with template and fills values from Translator in Context * * @param valToEval String with variables to replace * @param vcContext velocity context containing a translator in this case * @return input String where values from context were replaced */ private String evaluateValue(String valToEval, Context vcContext) { StringWriter evaluatedValue = new StringWriter(); // evaluate inputFieldValue to get a concatenated string try { velocityEngine.evaluate(vcContext, evaluatedValue, "vcUservalue", valToEval); } catch (ParseErrorException e) { log.error("parsing of values in xsl-file of LocalizedXSLTransformer not possible!", e); return "ERROR"; } catch (MethodInvocationException e) { log.error("evaluating of values in xsl-file of LocalizedXSLTransformer not possible!", e); return "ERROR"; } catch (ResourceNotFoundException e) { log.error("xsl-file of LocalizedXSLTransformer not found!", e); return "ERROR"; } catch (Exception e) { log.error("could not read xsl-file of LocalizedXSLTransformer!", e); return "ERROR"; } return evaluatedValue.toString(); } /** * convert xsl InputStream to String * @param in * @return xsl as String * @throws IOException */ private static String slurp(InputStream in) throws IOException { StringBuffer out = new StringBuffer(); byte[] b = new byte[4096]; for (int n; (n = in.read(b)) != -1;) { out.append(new String(b, 0, n)); } return out.toString(); } }