001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util;
011:
012: import java.io.*;
013: import java.util.*;
014: import java.net.URL;
015:
016: import javax.xml.transform.*;
017: import javax.xml.parsers.*;
018: import javax.xml.transform.stream.StreamResult;
019: import javax.xml.transform.stream.StreamSource;
020: import javax.xml.transform.dom.DOMSource;
021:
022: import org.mmbase.bridge.*;
023: import org.mmbase.bridge.util.xml.Generator;
024:
025: import org.mmbase.cache.xslt.*;
026:
027: import org.mmbase.util.xml.URIResolver;
028: import org.mmbase.util.logging.Logger;
029: import org.mmbase.util.logging.Logging;
030:
031: /**
032: * Make XSL Transformations
033: *
034: * @core (?) Not specific to MMBase, but used by build files for generation of documentation.
035: * @move org.mmbase.util.xml
036: * @author Case Roole, cjr@dds.nl
037: * @author Michiel Meeuwissen
038: * @version $Id: XSLTransformer.java,v 1.39 2007/11/30 15:49:52 michiel Exp $
039: */
040: public class XSLTransformer {
041: private static final Logger log = Logging
042: .getLoggerInstance(XSLTransformer.class);
043:
044: public static final String NS_AWARE = "org.mmbase.util.XSLTransformer.namespaceAware";
045:
046: /**
047: * Transform an XML document using a certain XSL document.
048: *
049: * @param xmlPath Path to XML file
050: * @param xslPath Path to XSL file
051: * @return String with converted XML document
052: */
053: public static String transform(String xmlPath, String xslPath) {
054: return transform(xmlPath, xslPath, false);
055: }
056:
057: /**
058: * Transform an XML document using a certain XSL document, on
059: * MMBase specic way (error handling, entitity resolving, uri
060: * resolving, logging), and write it to string, which optionally can be
061: * 'cut'.
062: *
063: * @param xmlPath Path to XML file
064: * @param xslPath Path to XSL file
065: * @param cutXML if <code>true</code>, cuts the <?xml> line that normally starts an
066: * xml document
067: * @return String with converted XML document
068: *
069: *
070: */
071: public static String transform(String xmlPath, String xslPath,
072: boolean cutXML) {
073: try {
074: StringWriter res = new StringWriter();
075: transform(new File(xmlPath), new File(xslPath),
076: new StreamResult(res), null, true);
077: String s = res.toString();
078: int n = s.indexOf("\n");
079: if (cutXML && s.length() > n) {
080: s = s.substring(n + 1);
081: }
082: return s;
083: } catch (Exception e) {
084: log.error(e.getMessage());
085: log.error(Logging.stackTrace(e));
086: return "Error during XSLT tranformation: " + e.getMessage();
087: }
088: }
089:
090: /**
091: * This is the base function which calls the actual XSL
092: * transformations. Performs XSL transformation on MMBase specific
093: * way (using MMBase cache, and URIResolver).
094: * @javadoc
095: *
096: * @since MMBase-1.6
097: */
098: public static void transform(Source xml, File xslFile,
099: Result result, Map<String, Object> params)
100: throws TransformerException {
101: transform(xml, xslFile, result, params, true);
102: }
103:
104: /**
105: * This is the base function which calls the actual XSL
106: * transformations. Performs XSL transformation on MMBase specific
107: * way (using MMBase cache, and URIResolver).
108: * @param xml The source XML
109: * @param xslFile The XSL which must be used for the transformation
110: * @param result The XSL out will be written to this
111: * @param params <code>null</code> or a Map to XSL transformations parameters
112: * @param considerDir If <code>true</code>, an URIResolver will be instantiated which can resolve entities relative to the XSL file.
113: *
114: * @since MMBase-1.6
115: */
116: public static void transform(Source xml, File xslFile,
117: Result result, Map<String, Object> params,
118: boolean considerDir) throws TransformerException {
119: try {
120: transform(xml, xslFile.toURL(), result, params, considerDir);
121: } catch (java.net.MalformedURLException mfe) {
122: throw new TransformerException(mfe.getMessage(), mfe);
123: }
124: }
125:
126: /**
127: * @since MMBase-1.8
128: */
129: public static void transform(Source xml, URL xslFile,
130: Result result, Map<String, Object> params)
131: throws TransformerException {
132: transform(xml, xslFile, result, params, true);
133: }
134:
135: /**
136: * @since MMBase-1.8
137: */
138: public static void transform(Source xml, URL xslFile,
139: Result result, Map<String, Object> params,
140: boolean considerDir) throws TransformerException {
141: TemplateCache cache = TemplateCache.getCache();
142: Source xsl;
143: try {
144: xsl = new StreamSource(xslFile.openStream());
145: } catch (IOException ioe) {
146: throw new TransformerException(ioe.getMessage(), ioe);
147: }
148: try {
149: xsl.setSystemId(xslFile.toString());
150: } catch (Exception e) {
151: }
152: URIResolver uri;
153: if (considerDir) {
154: try {
155: uri = new URIResolver(new URL(xslFile, "."));
156: } catch (java.net.MalformedURLException mfe) {
157: // oddd..
158: throw new TransformerException(mfe.getMessage(), mfe);
159: }
160: } else {
161: uri = new URIResolver();
162: }
163: Templates cachedXslt = cache.getTemplates(xsl, uri);
164: if (log.isDebugEnabled()) {
165: // log.debug("Size of cached XSLT " + SizeOf.getByteSize(cachedXslt) + " bytes");
166: log.debug("Size of URIResolver " + SizeOf.getByteSize(uri)
167: + " bytes");
168: log
169: .debug("template cache sze " + cache.size()
170: + " entries");
171: }
172: if (cachedXslt == null) {
173: TransformerFactory tf = FactoryCache.getCache().getFactory(
174: uri);
175: cachedXslt = tf.newTemplates(xsl);
176: cache.put(xsl, cachedXslt, uri);
177: } else {
178: if (log.isDebugEnabled())
179: log.debug("Used xslt from cache with "
180: + xsl.getSystemId());
181: }
182: Transformer transformer = cachedXslt.newTransformer();
183: //Transformer transformer = TransformerFactory.newInstance().newTransformer();
184: if (log.isDebugEnabled()) {
185: log.debug("Size of transformer "
186: + SizeOf.getByteSize(transformer) + " bytes");
187: }
188: if (params != null) {
189: for (Map.Entry<String, Object> entry : params.entrySet()) {
190: if (log.isDebugEnabled())
191: log.debug("setting param " + entry.getKey()
192: + " to " + entry.getValue());
193: transformer.setParameter(entry.getKey(), entry
194: .getValue());
195: }
196: }
197: transformer.transform(xml, result);
198: }
199:
200: /**
201: * Perfoms XSL Transformation on XML-file which is parsed MMBase
202: * specificly (useing MMBasse EntityResolver and Errorhandler).
203: * @javadoc
204: *
205: * @since MMBase-1.6
206: */
207: public static void transform(File xmlFile, File xslFile,
208: Result result, Map<String, Object> params,
209: boolean considerDir) throws TransformerException,
210: ParserConfigurationException, java.io.IOException,
211: org.xml.sax.SAXException {
212: // create the input xml.
213: DocumentBuilderFactory dfactory = DocumentBuilderFactory
214: .newInstance();
215:
216: // turn validating on
217: boolean validation = !"false".equals(System
218: .getProperty("org.mmbase.XSLTransformer.validation"));
219: if (!validation) {
220: log.service("disabled validation");
221: }
222: XMLEntityResolver resolver = new XMLEntityResolver(validation);
223: dfactory.setNamespaceAware(true);
224: if (params != null
225: && Boolean.FALSE.equals(params.get(NS_AWARE))) {
226: dfactory.setNamespaceAware(false);
227: }
228: DocumentBuilder db = dfactory.newDocumentBuilder();
229:
230: XMLErrorHandler handler = new XMLErrorHandler();
231: db.setErrorHandler(handler);
232: db.setEntityResolver(resolver);
233: org.w3c.dom.Document xmlDoc = db.parse(xmlFile);
234: Source s = new DOMSource(xmlDoc);
235: s.setSystemId(xmlFile.toURL().toString());
236: transform(s, xslFile, result, params, considerDir);
237: }
238:
239: /**
240: * Can be used to transform a directory of XML-files. Of course the result must be written to files too.
241: *
242: * The transformations will be called with a paramter "root" which
243: * points back to the root directory relatively. You need this
244: * when all your transformations results (probably html's) need to
245: * refer to the same file which is relative to the root of the transformation.
246: * @javadoc
247: *
248: * @since MMBase-1.6
249: */
250: public static void transform(File xmlDir, File xslFile,
251: File resultDir, boolean recurse,
252: Map<String, Object> params, boolean considerDir)
253: throws TransformerException, ParserConfigurationException,
254: java.io.IOException, org.xml.sax.SAXException {
255: if (!xmlDir.isDirectory()) {
256: throw new TransformerException("" + xmlDir
257: + " is not a directory");
258: }
259: if (!resultDir.exists()) {
260: resultDir.mkdir();
261: }
262: if (!resultDir.isDirectory()) {
263: throw new TransformerException("" + resultDir
264: + " is not a directory");
265: }
266: if (params == null)
267: params = new HashMap<String, Object>();
268:
269: List<String> exclude = (List<String>) params.get("exclude");
270:
271: File[] files = xmlDir.listFiles();
272: for (File element : files) {
273: if (exclude.contains(element.getName()))
274: continue;
275:
276: if (recurse && element.isDirectory()) {
277: if ("CVS".equals(element.getName()))
278: continue;
279: File resultSubDir = new File(resultDir, element
280: .getName());
281: Map<String, Object> myParams;
282: if (params == null) {
283: myParams = new HashMap<String, Object>();
284: } else {
285: myParams = new HashMap<String, Object>(params);
286: }
287:
288: if (myParams.get("root") == null) {
289: myParams.put("root", "../");
290: } else {
291: if ("./".equals(myParams.get("root"))) {
292: myParams.put("root", "../");
293: } else {
294: myParams.put("root", myParams.get("root")
295: + "../");
296: }
297: }
298: log.info("Transforming directory " + element
299: + " (root is " + myParams.get("root") + ")");
300: transform(element, xslFile, resultSubDir, recurse,
301: myParams, considerDir);
302: } else {
303: if (!element.getName().endsWith(".xml"))
304: continue;
305: String fileName = element.getName();
306: fileName = fileName.substring(0, fileName.length() - 4);
307: params.put("filename", fileName);
308: String extension = (String) params.get("extension");
309: if (extension == null)
310: extension = "html";
311: File resultFile = new File(resultDir, fileName + "."
312: + extension);
313: if (resultFile.lastModified() > element.lastModified()) {
314: log.info("Not transforming " + element
315: + " because " + resultFile
316: + " is up to date");
317: } else {
318: log.info("Transforming file " + element + " to "
319: + resultFile);
320: try {
321: Result res;
322: if ("true".equals(params.get("dontopenfile"))) {
323: res = new StreamResult(System.out);
324: } else {
325: res = new StreamResult(resultFile);
326: }
327: transform(element, xslFile, res, params,
328: considerDir);
329: } catch (Exception e) {
330: log.error(e.toString());
331: log.error(Logging.stackTrace(e));
332: }
333: }
334: }
335: }
336: }
337:
338: public static Result getResult(String[] argv) throws Exception {
339: if (argv.length > 2) {
340: FileOutputStream stream = new FileOutputStream(argv[2]);
341: Writer f = new OutputStreamWriter(stream, "utf-8");
342: return new StreamResult(f);
343: } else {
344: return new StreamResult(new OutputStreamWriter(System.out,
345: "utf-8"));
346: }
347: }
348:
349: /**
350: * Invocation of the class from the commandline for testing/building
351: */
352: public static void main(String[] argv) {
353:
354: // log.setLevel(org.mmbase.util.logging.Level.DEBUG);
355: if (argv.length < 2) {
356: log
357: .info("Use with two arguments: <xslt-file>|SER <xml-inputfile|node-number> [xml-outputfile]");
358: log
359: .info("Use with tree arguments: xslt-file xml-inputdir xml-outputdir [key=value options]");
360: log.info("special options can be:");
361: log
362: .info(" usecache=true: Use the Template cache or not (to speed up)");
363: log.info(" namespaceaware=true: ");
364: log
365: .info(" exclude=<filename>: File/directory name to exclude (can be used multiple times");
366: log
367: .info(" extension=<file extensions>: File extensions to use in transformation results (defaults to html)");
368:
369: log
370: .info("Other options are passed to XSL-stylesheet as parameters.");
371:
372: } else {
373: boolean namespaceaware = true;
374: Map<String, Object> params = new HashMap<String, Object>();
375: if (argv.length > 3) {
376: for (int i = 3; i < argv.length; i++) {
377: String key = argv[i];
378: String value = "";
379: int p = key.indexOf("=");
380: if (p > 0) {
381: if (p < key.length() - 1)
382: value = key.substring(p + 1);
383: key = key.substring(0, p);
384: }
385: if (key.equals("namespaceaware")) {
386: namespaceaware = value.equals("true");
387: params.put(NS_AWARE, namespaceaware);
388: } else if (key.equals("usecache")) {
389: TemplateCache.getCache().setActive(
390: value.equals("true"));
391: } else if (key.equals("exclude")) {
392: if (params.get("exclude") == null) {
393: params.put("exclude",
394: new ArrayList<String>());
395: }
396: List<String> excludes = (List<String>) params
397: .get("exclude");
398: excludes.add(value);
399: } else {
400: params.put(key, value);
401: }
402: }
403: }
404: try {
405: File in = new File(argv[1]);
406: if (in.exists()) {
407: if (in.isDirectory()) {
408: log.info("Transforming directory " + in);
409: long start = System.currentTimeMillis();
410: try {
411: transform(in, new File(argv[0]), new File(
412: argv[2]), true, params, true);
413: } catch (Exception e) {
414: log.error("Error: " + e.toString());
415: }
416: log.info("Transforming took "
417: + (System.currentTimeMillis() - start)
418: / 1000.0 + " seconds");
419: } else {
420: log.info("Transforming file " + argv[1]);
421: transform(new File(argv[1]), new File(argv[0]),
422: getResult(argv), params, true);
423: }
424: } else {
425: log
426: .debug(""
427: + in
428: + " does not exist, interpreting it as a node, connecting using RMMCI");
429: Result result = getResult(argv);
430: String nodeNumber = argv[1];
431: CloudContext cc = ContextProvider
432: .getDefaultCloudContext();
433: Cloud cloud = cc.getCloud("mmbase", "anonymous",
434: null);
435: params.put("cloud", cloud);
436: Node node = cloud.getNode(nodeNumber);
437: DocumentBuilder documentBuilder = org.mmbase.util.xml.DocumentReader
438: .getDocumentBuilder();
439: Generator generator = new Generator(
440: documentBuilder, cloud);
441: generator.setNamespaceAware(namespaceaware);
442: generator.add(node, node.getNodeManager().getField(
443: "body"));
444: //generator.add(node);
445: //log.info("" + node.getXMLValue("body").getDocumentElement().getNamespaceURI());
446: generator.add(node.getRelations("idrel"));
447: NodeList relatedNodes = node.getRelatedNodes(
448: "object", "idrel", "both");
449: generator.add(relatedNodes);
450: log.debug("transforming");
451: transform(new DOMSource(generator.getDocument()),
452: new File(argv[0]), result, params, true);
453: }
454: } catch (Exception ex) {
455: log.error(ex.getMessage(), ex);
456: Throwable cause = ex.getCause();
457: while (cause != null) {
458: log.error("CAUSE" + cause.getMessage(), cause);
459: cause = cause.getCause();
460: }
461: }
462: }
463: }
464: }
|