source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/XMLTransformer.java@ 30782

Last change on this file since 30782 was 30782, checked in by kjdon, 8 years ago

changed the default public and system bits for doctype to transitional html 4. otherwise end up with empty values for some of the pages

  • Property svn:keywords set to Author Date Id Revision
File size: 35.5 KB
Line 
1/*
2 * XMLTransformer.java
3 * Copyright (C) 2002 New Zealand Digital Library, http://www.nzdl.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19package org.greenstone.gsdl3.util;
20
21import java.io.BufferedReader;
22import java.io.File;
23import java.io.FileInputStream;
24import java.io.FileNotFoundException;
25import java.io.FileWriter;
26import java.io.InputStreamReader;
27import java.io.IOException;
28import java.io.StringReader;
29import java.io.StringWriter;
30import java.io.UnsupportedEncodingException;
31import java.net.MalformedURLException;
32import java.net.URISyntaxException;
33import java.net.URI;
34import java.net.URL;
35import java.util.HashMap;
36import java.util.Iterator;
37import java.util.Map;
38import java.util.Properties;
39import java.util.Set;
40
41import javax.xml.parsers.DocumentBuilderFactory;
42import javax.xml.transform.ErrorListener;
43import javax.xml.transform.OutputKeys;
44import javax.xml.transform.Source;
45import javax.xml.transform.Transformer;
46import javax.xml.transform.TransformerConfigurationException;
47import javax.xml.transform.TransformerException;
48import javax.xml.transform.TransformerFactory;
49import javax.xml.transform.URIResolver;
50import javax.xml.transform.dom.DOMResult;
51import javax.xml.transform.dom.DOMSource;
52import javax.xml.transform.stream.StreamResult;
53import javax.xml.transform.stream.StreamSource;
54
55import org.apache.log4j.Logger;
56import org.greenstone.util.GlobalProperties;
57import org.w3c.dom.Document;
58import org.w3c.dom.Element;
59import org.w3c.dom.Node;
60
61/**
62 * XMLTransformer - utility class for greenstone
63 *
64 * transforms xml using xslt
65 *
66 * @author Katherine Don
67 * @version $Revision: 30782 $
68 */
69public class XMLTransformer
70{
71 private static int debugFileCount = 0; // for unique filenames when debugging XML transformations with physical files
72
73 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.util.XMLTransformer.class.getName());
74
75 /** The transformer factory we're using */
76 TransformerFactory t_factory = null;
77
78 /**
79 * The no-arguments constructor.
80 *
81 * Any exceptions thrown are caught internally
82 *
83 * @see javax.xml.transform.TransformerFactory
84 */
85 public XMLTransformer()
86 {
87 // http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/index.html?javax/xml/transform/TransformerFactory.html states that
88 // TransformerFactory.newInstance() looks in jar files for a Factory specified in META-INF/services/javax.xml.transform.TransformerFactory,
89 // else it will use the "platform default"
90 // In this case: xalan.jar's META-INF/services/javax.xml.transform.TransformerFactory contains org.apache.xalan.processor.TransformerFactoryImpl
91 // as required.
92
93 // This means we no longer have to do a System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
94 // followed by a this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
95 // The System.setProperty step to force the TransformerFactory implementation that gets used, conflicts with
96 // Fedora (visiting the Greenstone server pages breaks the Greenstone-tomcat hosted Fedora pages) as Fedora
97 // does not include the xalan.jar and therefore can't then find the xalan TransformerFactory explicitly set.
98
99 // Gone back to forcing use of xalan transformer, since other jars like crimson.jar, which may be on some
100 // classpaths, could be be chosen as the TransformerFactory implementation over xalan. This is what used to
101 // give problems before. Instead, have placed copies of the jars that Fedora needs (xalan.jar and serializer.jar
102 // and the related xsltc.jar which it may need) into packages/tomcat/lib so that it's on the server's classpath
103 // and will be found by Fedora.
104
105 // make sure we are using the xalan transformer
106 System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
107 try
108 {
109 this.t_factory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
110 //this.t_factory = TransformerFactory.newInstance();
111 this.t_factory.setErrorListener(new TransformErrorListener()); // handle errors in the xml Source used to instantiate transformers
112 }
113 catch (Exception e)
114 {
115 logger.error("exception creating t_factory " + e.getMessage());
116 }
117 }
118
119 /**
120 * Transform an XML document using a XSLT stylesheet
121 *
122 * @param stylesheet
123 * a filename for an XSLT stylesheet
124 * @param xml_in
125 * the XML to be transformed
126 * @return the transformed XML
127 */
128 public String transform(String stylesheet, String xml_in)
129 {
130
131 try
132 {
133 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
134 transformerErrorListener.setStylesheet(stylesheet);
135 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
136 Transformer transformer = this.t_factory.newTransformer(new StreamSource(stylesheet));
137
138 // Use the Transformer to transform an XML Source and send the output to a Result object.
139 StringWriter output = new StringWriter();
140 StreamSource streamSource = new StreamSource(new StringReader(xml_in));
141 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
142 transformer.transform(streamSource, new StreamResult(output));
143 return output.toString();
144 }
145 catch (TransformerConfigurationException e)
146 {
147 logger.error("couldn't create transformer object: " + e.getMessageAndLocation());
148 logger.error(e.getLocationAsString());
149 return "";
150 }
151 catch (TransformerException e)
152 {
153 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
154 return "";
155 }
156 }
157
158 public String transformToString(Document stylesheet, Document source)
159 {
160 return transformToString(stylesheet, source, null);
161 }
162
163 public String transformToString(Document stylesheet, Document source, HashMap parameters)
164 {
165
166 try
167 {
168 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
169 transformerErrorListener.setStylesheet(stylesheet);
170 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
171 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
172 if (parameters != null)
173 {
174 Set params = parameters.entrySet();
175 Iterator i = params.iterator();
176 while (i.hasNext())
177 {
178 Map.Entry m = (Map.Entry) i.next();
179 transformer.setParameter((String) m.getKey(), m.getValue());
180 }
181 }
182 //transformer.setParameter("page_lang", source.getDocumentElement().getAttribute(GSXML.LANG_ATT));
183
184 // Use the Transformer to transform an XML Source and send the output to a Result object.
185 StringWriter output = new StringWriter();
186 DOMSource domSource = new DOMSource(source);
187
188 transformer.setErrorListener(new TransformErrorListener(stylesheet, domSource));
189 transformer.transform(domSource, new StreamResult(output));
190 return output.toString();
191 }
192 catch (TransformerConfigurationException e)
193 {
194 logger.error("couldn't create transformer object: " + e.getMessageAndLocation());
195 logger.error(e.getLocationAsString());
196 return "";
197 }
198 catch (TransformerException e)
199 {
200 logger.error("couldn't transform the source: " + e.getMessageAndLocation());
201 return "";
202 }
203 }
204
205 /**
206 * Transform an XML document using a XSLT stylesheet, but using a DOMResult
207 * whose node should be set to the Document donated by resultNode
208 */
209 public Node transform_withResultNode(Document stylesheet, Document source, Document resultNode)
210 {
211 return transform(stylesheet, source, null, null, resultNode);
212 }
213
214 public Node transform(Document stylesheet, Document source)
215 {
216 return transform(stylesheet, source, null, null, null);
217 }
218
219 public Node transform(Document stylesheet, Document source, HashMap<String, Object> parameters)
220 {
221 return transform(stylesheet, source, parameters, null, null);
222 }
223
224 public Node transform(Document stylesheet, Document source, HashMap<String, Object> parameters, Document docDocType)
225 {
226 return transform(stylesheet, source, parameters, docDocType, null);
227 }
228
229 // This method will now set the docType in the new document created and returned, if any are specified in the
230 // (merged) stylesheet being applied. The docDocType parameter is therefore no longer necessary nor used by default.
231 protected Node transform(Document stylesheet, Document source, HashMap<String, Object> parameters, Document docDocType, Document resultNode)
232 {
233 try
234 {
235 // Use the TransformerFactory to process the stylesheet Source and generate a Transformer.
236 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
237 transformerErrorListener.setStylesheet(stylesheet);
238 Transformer transformer = this.t_factory.newTransformer(new DOMSource(stylesheet));
239 //logger.info("XMLTransformer transformer is " + transformer); //done in ErrorListener
240
241 if (parameters != null)
242 {
243 Set params = parameters.entrySet();
244 Iterator i = params.iterator();
245 while (i.hasNext())
246 {
247 Map.Entry m = (Map.Entry) i.next();
248 transformer.setParameter((String) m.getKey(), m.getValue());
249 }
250 }
251
252 // When we transform the DOMResult, we need to make sure the result of
253 // the transformation has a DocType. For that to happen, we need to create
254 // the DOMResult using a Document with a predefined docType.
255
256 // When the DOCType is not explicitly specified (default case), the docDocType variable is null
257 // In such a case, the transformer will work out the docType and output method and the rest
258 // from the stylesheet. Better to let the transformer work this out than GS manually aggregating
259 // all xsls being applied and the GS code deciding on which output method and doctype to use.
260
261 //DOMResult result = docDocType == null ? new DOMResult() : new DOMResult(docDocType);
262 DOMResult result = null;
263
264 Properties props = transformer.getOutputProperties();
265 if(docDocType == null) { // default case
266
267 String outputMethod = props.getProperty(OutputKeys.METHOD);
268 if(outputMethod.equals("html")) {
269 String doctype_public = props.getProperty(OutputKeys.DOCTYPE_PUBLIC);
270 String doctype_system = props.getProperty(OutputKeys.DOCTYPE_SYSTEM);
271
272 if(doctype_public == null) {
273 //doctype_public = ""; // or default to PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"?
274 doctype_public = "-//W3C//DTD HTML 4.01 Transitional//EN";
275 }
276 if(doctype_system == null) {
277 //doctype_system = ""; // or default to "http://www.w3.org/TR/html4/loose.dtd"?
278 doctype_system = "http://www.w3.org/TR/html4/loose.dtd";
279 }
280
281 Document docDocTypeFromTransformer = XMLConverter.newDOM(outputMethod, doctype_public, doctype_system);
282 result = new DOMResult(docDocTypeFromTransformer);
283 }
284 // if output method=xml, the <?xml ?> processing method goes missing hereafter, although it
285 // still exists in OutputKeys' VERSION, ENCODING and OMIT_XML_DECLARATION props at this point
286
287 } else { // if document with doctype was already specified (no longer the default case)
288 result = new DOMResult(docDocType);
289 }
290 // At this point if we haven't initialised result yet, set it to an empty DOMResult
291 if(result == null) {
292 result = new DOMResult();
293 }
294
295
296 if (resultNode != null)
297 {
298 result.setNode(resultNode);
299 }
300 DOMSource domSource = new DOMSource(source);
301 transformer.setErrorListener(new TransformErrorListener(stylesheet, domSource));
302 transformer.transform(domSource, result);
303 return result.getNode(); // pass the entire document
304 }
305 catch (TransformerConfigurationException e)
306 {
307 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)" + "\ncouldn't create transformer object", e);
308 }
309 catch (TransformerException e)
310 {
311 return transformError("XMLTransformer.transform(Doc, Doc, HashMap, Doc)" + "\ncouldn't transform the source", e);
312 }
313 }
314
315 /**
316 When transforming an XML with an XSLT from the command-line, there's two problems if calling
317 XMLTransformer.transform(File xslt, File xml):
318
319 1. Need to run the transformation process from the location of the stylesheet, else it can't find
320 the stylesheet in order to load it. This is resolved by setting the SystemID for the stylesheet.
321
322 2. The XSLT stylesheet has &lt;xsl:import&gt; statements importing further XSLTs which are furthermore
323 specified by relative paths, requiring that the XSLT (and perhaps also XML) input file is located
324 in the folder from which the relative paths of the import statements are specified. This is resolved
325 by working out the absolute path of each imported XSLT file and setting their SystemIDs too.
326
327 Without solving these problems, things will only work if we 1. run the transformation from the
328 web/interfaces/default/transform folder and 2. need to have the input XSLT file (and XML?)
329 placed in the web/interfacces/default/transform folder too
330
331 Both parts of the solution are described in:
332 http://stackoverflow.com/questions/3699860/resolving-relative-paths-when-loading-xslt-filse
333
334 After the systemID on the XSLT input file is set, we can run the command from the toplevel GS3
335 folder.
336 After resolving the URIs of the XSLT files imported by the input XSLT, by setting their systemIDs,
337 the input XSLT (and XML) file need not be located in web/interfaces/default/transform anymore.
338 */
339 public Node transform(File stylesheet, File source, String interfaceName, Document docDocType)
340 {
341 try
342 {
343 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
344 transformerErrorListener.setStylesheet(stylesheet);
345
346 // http://stackoverflow.com/questions/3699860/resolving-relative-paths-when-loading-xslt-files
347 Source xsltSource = new StreamSource(new InputStreamReader(new FileInputStream(stylesheet), "UTF-8"));
348 URI systemID = stylesheet.toURI();
349 try {
350 URL url = systemID.toURL();
351 xsltSource.setSystemId(url.toExternalForm());
352 } catch (MalformedURLException mue) {
353 logger.error("Warning: Unable to set systemID for stylesheet to " + systemID);
354 logger.error("Got exception: " + mue.getMessage(), mue);
355 }
356
357 this.t_factory.setURIResolver(new ClasspathResourceURIResolver(interfaceName));
358 Transformer transformer = this.t_factory.newTransformer(xsltSource);
359 // Alternative way of instantiating a newTransformer():
360 //Templates cachedXSLT = this.t_factory.newTemplates(xsltSource);
361 //Transformer transformer = cachedXSLT.newTransformer();
362
363 DOMResult result = (docDocType == null) ? new DOMResult() : new DOMResult(docDocType);
364 StreamSource streamSource = new StreamSource(new InputStreamReader(new FileInputStream(source), "UTF-8"));
365
366 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
367
368 transformer.transform(streamSource, result);
369 return result.getNode().getFirstChild();
370 }
371 catch (TransformerConfigurationException e)
372 {
373 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't create transformer object for files\n" + stylesheet + "\n" + source, e);
374 }
375 catch (TransformerException e)
376 {
377 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't transform the source for files\n" + stylesheet + "\n" + source, e);
378 }
379 catch (UnsupportedEncodingException e)
380 {
381 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't read file due to an unsupported encoding\n" + stylesheet + "\n" + source, e);
382 }
383 catch (FileNotFoundException e)
384 {
385 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't find the file specified\n" + stylesheet + "\n" + source, e);
386 }
387 }
388
389 /**
390 * Class for resolving the relative paths used for &lt;xsl:import&gt;s
391 * when transforming XML with an XSLT
392 */
393 class ClasspathResourceURIResolver implements URIResolver {
394 String interface_name;
395
396 public ClasspathResourceURIResolver(String interfaceName) {
397 interface_name = interfaceName;
398 }
399
400 /**
401 * Override the URIResolver.resolve() method to turn relative paths of imported xslt files into
402 * absolute paths and set these absolute paths as their SystemIDs when loading them into memory.
403 * @see http://stackoverflow.com/questions/3699860/resolving-relative-paths-when-loading-xslt-files
404 */
405
406 @Override
407 public Source resolve(String href, String base) throws TransformerException {
408
409 //System.err.println("href: " + href); // e.g. href: layouts/main.xsl
410 //System.err.println("base: " + base); // e.g for *toplevel* base: file:/path/to/gs3-svn/CL1.xslt
411
412
413 // 1. for any xsl imported by the original stylesheet, try to work out its absolute path location
414 // by concatenating the parent folder of the base stylesheet file with the href of the file it
415 // imports (which is a path relative to the base file). This need not hold true for the very 1st
416 // base stylesheet: it may be located somewhere entirely different from the greenstone XSLT
417 // files it refers to. This is the case when running transforms on the commandline for testing
418
419 File importXSLfile = new File(href); // assume the xsl imported has an absolute path
420
421 if(!importXSLfile.isAbsolute()) { // imported xsl specifies a path relative to parent of base
422 try {
423 URI baseURI = new URI(base);
424 importXSLfile = new File(new File(baseURI).getParent(), href);
425
426 if(!importXSLfile.exists()) { // if the imported file does not exist, it's not
427 // relative to the base (the stylesheet file importing it). So look in the
428 // interface's transform subfolder for the xsl file to be imported
429 importXSLfile = new File(GSFile.interfaceStylesheetFile(GlobalProperties.getGSDL3Home(), interface_name, href));
430 }
431 } catch(URISyntaxException use) {
432 importXSLfile = new File(href); // try
433 }
434 }
435
436 String importXSLfilepath = ""; // for printing the expected file path on error
437 try {
438 // path of import XSL file after resolving any .. and . dir paths
439 importXSLfilepath = importXSLfile.getCanonicalPath();
440 } catch(IOException ioe) { // resort to using the absolute path
441 importXSLfilepath = importXSLfile.getAbsolutePath();
442 }
443
444 // 2. now we know where the XSL file being imported lives, so set the systemID to its
445 // absolute path when loading it into a Source object
446 URI systemID = importXSLfile.toURI();
447 Source importXSLsrc = null;
448 try {
449 importXSLsrc = new StreamSource(new InputStreamReader(new FileInputStream(importXSLfile), "UTF-8"));
450 URL url = systemID.toURL();
451 importXSLsrc.setSystemId(url.toExternalForm());
452 } catch (MalformedURLException mue) {
453 logger.error("Warning: Unable to set systemID for imported stylesheet to " + systemID);
454 logger.error("Got exception: " + mue.getMessage(), mue);
455 } catch(FileNotFoundException fne) {
456 logger.error("ERROR: importXSLsrc file does not exist " + importXSLfilepath);
457 logger.error("\tError msg: " + fne.getMessage(), fne);
458 } catch(UnsupportedEncodingException uee) {
459 logger.error("ERROR: could not resolve relative path of import XSL file " + importXSLfilepath
460 + " because of encoding issues: " + uee.getMessage(), uee);
461 }
462 return importXSLsrc;
463 //return new StreamSource(this.getClass().getClassLoader().getResourceAsStream(href)); // won't work
464 }
465 }
466
467 public Node transform(File stylesheet, File source)
468 {
469 return transform(stylesheet, source, null);
470 }
471
472 // debugAsFile is only to be set to true when either the stylesheet or source parameters
473 // are not objects of type File. The debugAsFile variable is passed into the
474 // TransformErrorListener. When set to true, the TransformErrorListener will itself create
475 // two files containing the stylesheet and source XML, and try to transform the new source
476 // file with the stylesheet file for debugging purposes.
477 protected Node transform(File stylesheet, File source, Document docDocType)
478 {
479 try
480 {
481 TransformErrorListener transformerErrorListener = (TransformErrorListener) this.t_factory.getErrorListener();
482 transformerErrorListener.setStylesheet(stylesheet);
483 Transformer transformer = this.t_factory.newTransformer(new StreamSource(new InputStreamReader(new FileInputStream(stylesheet), "UTF-8")));
484 DOMResult result = (docDocType == null) ? new DOMResult() : new DOMResult(docDocType);
485 StreamSource streamSource = new StreamSource(new InputStreamReader(new FileInputStream(source), "UTF-8"));
486
487 transformer.setErrorListener(new TransformErrorListener(stylesheet, streamSource));
488
489 transformer.transform(streamSource, result);
490 return result.getNode().getFirstChild();
491 }
492 catch (TransformerConfigurationException e)
493 {
494 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't create transformer object for files\n" + stylesheet + "\n" + source, e);
495 }
496 catch (TransformerException e)
497 {
498 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't transform the source for files\n" + stylesheet + "\n" + source, e);
499 }
500 catch (UnsupportedEncodingException e)
501 {
502 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't read file due to an unsupported encoding\n" + stylesheet + "\n" + source, e);
503 }
504 catch (FileNotFoundException e)
505 {
506 return transformError("XMLTransformer.transform(File, File)" + "\ncouldn't find the file specified\n" + stylesheet + "\n" + source, e);
507 }
508 }
509
510 // Given a heading string on the sort of transformation error that occurred and the exception object itself,
511 // this method prints the exception to the tomcat window (system.err) and the greenstone log and then returns
512 // an xhtml error page that is constructed from it.
513 protected Node transformError(String heading, Exception e)
514 {
515 String message = heading + "\n" + e.getMessage();
516 logger.error(heading + ": " + e.getMessage());
517
518 if (e instanceof TransformerException)
519 {
520 String location = ((TransformerException) e).getLocationAsString();
521 if (location != null)
522 {
523 logger.error(location);
524 message = message + "\n" + location;
525 }
526 }
527 System.err.println("****\n" + message + "\n****");
528 return constructErrorXHTMLPage(message);
529 }
530
531 // Given an error message, splits it into separate lines based on any newlines present and generates an xhtml page
532 // (xml Element) with paragraphs for each line. This is then returned so that it can be displayed in the browser.
533 public static Element constructErrorXHTMLPage(String message)
534 {
535 try
536 {
537 String[] lines = message.split("\n");
538
539 Document xhtmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
540 // <html></html>
541 Node htmlNode = xhtmlDoc.createElement("html");
542 xhtmlDoc.appendChild(htmlNode);
543 // <head></head>
544 Node headNode = xhtmlDoc.createElement("head");
545 htmlNode.appendChild(headNode);
546 // <title></title>
547 Node titleNode = xhtmlDoc.createElement("title");
548 headNode.appendChild(titleNode);
549 Node titleString = xhtmlDoc.createTextNode("Error occurred");
550 titleNode.appendChild(titleString);
551
552 // <body></body>
553 Node bodyNode = xhtmlDoc.createElement("body");
554 htmlNode.appendChild(bodyNode);
555
556 // finally put the message in the body
557 Node h1Node = xhtmlDoc.createElement("h1");
558 bodyNode.appendChild(h1Node);
559 Node headingString = xhtmlDoc.createTextNode("The following error occurred:");
560 h1Node.appendChild(headingString);
561
562 //Node textNode = xhtmlDoc.createTextNode(message);
563 //bodyNode.appendChild(textNode);
564
565 for (int i = 0; i < lines.length; i++)
566 {
567 Node pNode = xhtmlDoc.createElement("p");
568 Node textNode = xhtmlDoc.createTextNode(lines[i]);
569 pNode.appendChild(textNode);
570 bodyNode.appendChild(pNode);
571 }
572
573 return xhtmlDoc.getDocumentElement();
574
575 }
576 catch (Exception e)
577 {
578 String errmsg = "Exception trying to construct error xhtml page from message: " + message + "\n" + e.getMessage();
579 System.err.println(errmsg);
580 logger.error(errmsg);
581 return null;
582 }
583 }
584
585 // ErrorListener class for both Transformer objects and TransformerFactory objects.
586 // This class can be used to register a handler for any fatal errors, errors and warnings that
587 // may occur when either transforming an xml file with an xslt stylesheet using the XMLTransformer,
588 // or when instantiating a Transformer object using the XMLTransformer's TransformerFactory member var.
589 // The latter case occurs when the xml Source used to instantiate a Transformer from a TransformerFactory
590 // is invalid in some manner, which results in a null Transformer object. However, as no
591 // TransformerConfigurationException or TransformerException are thrown in this case, the errors
592 // would have not been noticed until things go wrong later when trying to use the (null) Transformer.
593 //
594 // The errors caught by this ErrorListener class are printed both to the greenstone.log and to the
595 // tomcat console (System.err), and the error message is stored in the errorMessage variable so that
596 // it can be retrieved and be used to generate an xhtml error page.
597 public class TransformErrorListener implements ErrorListener
598 {
599 protected String errorMessage = null;
600 protected String stylesheet = null;
601 protected Source source = null; // can be DOMSource or StreamSource
602 protected boolean debugAsFile = true; // true if xslt or source are not real physical files
603
604 // *********** METHODS TO BE CALLED WHEN SETTING AN ERROR LISTENER ON TRANSFORMERFACTORY OBJECTS
605 // The default constructor is only for when setting an ErrorListener on TransformerFactory objects
606 public TransformErrorListener()
607 {
608 this.stylesheet = null;
609 this.source = null;
610 XMLTransformer.debugFileCount++;
611 }
612
613 public void setStylesheet(Document xslt)
614 {
615 this.debugAsFile = true;
616 this.stylesheet = GSXML.elementToString(xslt.getDocumentElement(), true);
617 this.source = null;
618 }
619
620 public void setStylesheet(String xslt)
621 {
622 this.debugAsFile = true;
623 this.stylesheet = xslt;
624 this.source = null;
625 }
626
627 public void setStylesheet(File xslt)
628 {
629 this.debugAsFile = false; // if this constructor is called, we're dealing with physical files for both xslt and source
630 this.stylesheet = xslt.getAbsolutePath();
631 this.source = null;
632 }
633
634 // *********** METHODS TO BE CALLED WHEN SETTING AN ERROR LISTENER ON TRANSFORMERFACTORY OBJECTS
635 // When setting an ErrorListener on Transformer object, the ErrorListener takes a Stylesheet xslt and a Source
636 public TransformErrorListener(String xslt, Source source)
637 {
638 this.stylesheet = xslt;
639 this.source = source;
640 XMLTransformer.debugFileCount++;
641 }
642
643 public TransformErrorListener(Document xslt, Source source)
644 {
645 this.stylesheet = GSXML.elementToString(xslt.getDocumentElement(), true);
646 this.source = source;
647 XMLTransformer.debugFileCount++;
648 }
649
650 public TransformErrorListener(File xslt, Source source)
651 {
652 this.debugAsFile = false; // if this constructor is called, we're dealing with physical files for both xslt and source
653 this.source = source;
654 this.stylesheet = xslt.getAbsolutePath(); // not necessary to get the string from the file
655 // all we were going to do with it *on error* was write it out to a file anyway
656 }
657
658 // *********** METHODS CALLED AUTOMATICALLY ON ERROR
659
660 // Receive notification of a recoverable error.
661 public void error(TransformerException exception)
662 {
663 handleError("Error:\n", exception);
664 }
665
666 // Receive notification of a non-recoverable error.
667 public void fatalError(TransformerException exception)
668 {
669 handleError("Fatal Error:\n", exception);
670 }
671
672 // Receive notification of a warning.
673 public void warning(TransformerException exception)
674 {
675 handleError("Warning:\n", exception);
676 }
677
678 public String toString(TransformerException e)
679 {
680 String msg = "Exception encountered was:\n\t";
681 String location = e.getLocationAsString();
682 if (location != null)
683 {
684 msg = msg + "Location: " + location + "\n\t";
685 }
686
687 return msg + "Message: " + e.getMessage();
688 }
689
690 // clears the errorPage variable after the first call to this method
691 public String getErrorMessage()
692 {
693 String errMsg = this.errorMessage;
694 if (this.errorMessage != null)
695 {
696 this.errorMessage = null;
697 }
698 return errMsg;
699 }
700
701 // sets the errorMessage member variable to the data stored in the exception
702 // and writes the errorMessage to the logger and tomcat's System.err
703 protected void handleError(String errorType, TransformerException exception)
704 {
705
706 this.errorMessage = errorType + toString(exception);
707
708 // If either the stylesheet or the source to be transformed with it were not files,
709 // so that the transformation was performed in-memory, then the "location" information
710 // during the error handling (if any) wouldn't have been helpful.
711 // To allow proper debugging, we write both stylesheet and source out as physical files
712 // and perform the same transformation again, so that when a transformation error does
713 // occur, the files are not in-memory but can be viewed, and any location information
714 // for the error given by the ErrorListener will be sensible (instead of the unhelpful
715 // "line#0 column#0 in file://somewhere/dummy.xsl").
716 // Note that if the stylesheet and the source it is to transform were both physical
717 // files to start off with, we will not need to perform the same transformation again
718 // since the error reporting would have provided accurate locations for those.
719 if (debugAsFile)
720 {
721 System.err.println("\n****Original error transforming xml:\n" + this.errorMessage + "\n****\n");
722 System.err.println("***** About to perform the transform again with actual files\n******\n");
723 logger.error("Original transformation error: " + this.errorMessage);
724 logger.error("**** About to perform transform again with actual files");
725
726
727 performTransformWithPhysicalFiles(); // will give accurate line numbers
728
729 // No need to print out the current error message (seen in the Else statement below),
730 // as the recursive call to XMLTransformer.transform(File, File, false) in method
731 // performTransformWithPhysicalFiles() will do this for us.
732 }
733 else
734 {
735 // printing out the error message
736 // since !debugAsFile, we are dealing with physical files,
737 // variable stylesheet would have stored the filename instead of contents
738 this.errorMessage = this.errorMessage + "\nstylesheet filename: " + stylesheet;
739
740 this.errorMessage += "\nException CAUSE:\n" + exception.getCause();
741 System.err.println("\n****Error transforming xml:\n" + this.errorMessage + "\n****\n");
742 //System.err.println("Stylesheet was:\n + this.stylesheet + "************END STYLESHEET***********\n\n");
743
744 logger.error(this.errorMessage);
745
746 // now print out the source to a file, and run the stylesheet on it using a transform()
747 // then any error will be referring to one of these two input files.
748 }
749 }
750
751 // This method will redo the transformation that went wrong with *real* files:
752 // it writes out the stylesheet and source XML to files first, then performs the transformation
753 // to get the actual line location of where things went wrong (instead of "line#0 column#0 in dummy.xsl")
754 protected void performTransformWithPhysicalFiles()
755 {
756 File webLogsTmpFolder = new File(GlobalProperties.getGSDL3Home() + File.separator + "logs" + File.separator + "tmp");
757 if (!webLogsTmpFolder.exists())
758 {
759 webLogsTmpFolder.mkdirs(); // create any necessary folders
760 }
761 File styleFile = new File(webLogsTmpFolder + File.separator + "stylesheet" + XMLTransformer.debugFileCount + ".xml");
762 File sourceFile = new File(webLogsTmpFolder + File.separator + "source" + XMLTransformer.debugFileCount + ".xml");
763 try
764 {
765 // write stylesheet to a file called stylesheet_systemID in tmp
766 FileWriter styleSheetWriter = new FileWriter(styleFile);
767 styleSheetWriter.write(stylesheet, 0, stylesheet.length());
768 styleSheetWriter.flush();
769 styleSheetWriter.close();
770 }
771 catch (Exception e)
772 {
773 System.err.println("*** Exception when trying to write out stylesheet to " + styleFile.getAbsolutePath());
774 }
775
776 if (this.source != null)
777 { // ErrorListener was set on a Transformer object
778 try
779 {
780 FileWriter srcWriter = new FileWriter(sourceFile);
781 String contents = "";
782 if (source instanceof DOMSource)
783 {
784 DOMSource domSource = (DOMSource) source;
785 Document doc = (Document) domSource.getNode();
786 contents = GSXML.elementToString(doc.getDocumentElement(), true);
787 //contents = GSXML.xmlNodeToXMLString(domSource.getNode());
788 }
789 else if (source instanceof StreamSource)
790 {
791 StreamSource streamSource = (StreamSource) source;
792 BufferedReader reader = new BufferedReader(streamSource.getReader());
793 String line = "";
794 while ((line = reader.readLine()) != null)
795 {
796 contents = contents + line + "\n";
797 }
798 }
799 srcWriter.write(contents, 0, contents.length());
800 srcWriter.flush();
801 srcWriter.close();
802 }
803 catch (Exception e)
804 {
805 System.err.println("*** Exception when trying to write out stylesheet to " + sourceFile.getAbsolutePath());
806 }
807 }
808
809 System.err.println("*****************************************");
810 System.err.println("Look for stylesheet in: " + styleFile.getAbsolutePath());
811 if (this.source != null)
812 { // ErrorListener was set on a Transformer object
813 System.err.println("Look for source XML in: " + sourceFile.getAbsolutePath());
814 }
815
816 // now perform the transform again, which will assign another TransformErrorListener
817 // but since debuggingAsFile is turned off, we won't recurse into this section of
818 // handling the error again
819 if (this.source != null)
820 { // ErrorListener was set on a Transformer object
821 XMLTransformer.this.transform(styleFile, sourceFile); // calls the File, File version, so debugAsFile will be false
822 }
823 else
824 { // ErrorListener was set on a TransformerFactory object
825
826 // The recursive step in this case is to perform the instantiation
827 // of the Transformer object again.
828 // Only one TransformerFactory object per XMLTransformer,
829 // and only one TransformerHandler object set on any TransformerFactory
830 // But the stylesheet used to create a Transformer from that TransformerFactory
831 // object changes each time, by calls to setStylesheet(),
832 // Therefore, the debugAsFile state for the single TransformerFactory's
833 // TransformerHandler changes each time also.
834
835 try
836 {
837 debugAsFile = false;
838 this.stylesheet = styleFile.getAbsolutePath();
839 //TransformErrorListener transformerErrorListener = (TransformErrorListener)XMLTransformer.this.t_factory.getErrorListener();
840 //transformerErrorListener.setStylesheet(styleFile);
841 Transformer transformer = XMLTransformer.this.t_factory.newTransformer(new StreamSource(styleFile));
842 if (transformer == null)
843 {
844 String msg = "XMLTransformer transformer is " + transformer;
845 logger.info(msg);
846 System.out.println(msg + "\n****\n");
847 }
848 }
849 catch (TransformerConfigurationException e)
850 {
851 String message = "Couldn't create transformer object: " + e.getMessageAndLocation();
852 logger.error(message);
853 logger.error(e.getLocationAsString());
854 System.out.println(message);
855 }
856 }
857 }
858 }
859}
Note: See TracBrowser for help on using the repository browser.