source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/core/TransformingReceptionist.java@ 36169

Last change on this file since 36169 was 36169, checked in by kjdon, 2 years ago

there was a (cut and paste?) error in passing the site name to GSFile.getAllXSLFiles which meant it was always passing null. therefore it couldn't pick up any metadata names from site or collection xsl files. Now that its fixed, adding metadata into these transform files does result it getting added to the page - requires a restart though, as this step of workign out which metadata we need happens at configure time

  • Property svn:keywords set to Author Date Id Revision
File size: 43.0 KB
Line 
1package org.greenstone.gsdl3.core;
2
3import java.io.File;
4import java.io.FileReader;
5import java.io.Serializable;
6import java.io.StringWriter;
7import java.util.ArrayList;
8import java.util.HashMap;
9import java.util.HashSet;
10import java.util.regex.Pattern;
11import java.util.regex.Matcher;
12
13import javax.xml.transform.Transformer;
14import javax.xml.transform.TransformerException;
15import javax.xml.transform.TransformerFactory;
16import javax.xml.transform.dom.DOMSource;
17import javax.xml.transform.stream.StreamResult;
18
19import org.apache.commons.lang3.StringUtils;
20import org.apache.log4j.Logger;
21import org.apache.xerces.parsers.DOMParser;
22import org.greenstone.gsdl3.action.Action;
23import org.greenstone.gsdl3.util.GSConstants;
24import org.greenstone.gsdl3.util.GSFile;
25import org.greenstone.gsdl3.util.GSParams;
26import org.greenstone.gsdl3.util.GSXML;
27import org.greenstone.gsdl3.util.GSXSLT;
28import org.greenstone.gsdl3.util.UserContext;
29import org.greenstone.gsdl3.util.XMLConverter;
30import org.greenstone.gsdl3.util.XMLTransformer;
31import org.greenstone.gsdl3.util.XSLTUtil;
32import org.greenstone.util.GlobalProperties;
33import org.w3c.dom.Comment;
34import org.w3c.dom.Document;
35import org.w3c.dom.Element;
36import org.w3c.dom.Node;
37import org.w3c.dom.NodeList;
38import org.w3c.dom.Text;
39import org.xml.sax.InputSource;
40
41/**
42 * A receptionist that uses xslt to transform the page_data before returning it.
43 * . Receives requests consisting of an xml representation of cgi args, and
44 * returns the page of data - in html by default. The requests are processed by
45 * the appropriate action class
46 *
47 * @see Action
48 */
49public class TransformingReceptionist extends Receptionist
50{
51 protected static final String EXPAND_GSF_FILE = "expand-gsf.xsl";
52 protected static final String EXPAND_GSF_PASS1_FILE = "expand-gsf-pass1.xsl";
53 protected static final String EXPAND_GSLIB_FILE = "expand-gslib.xsl";
54 protected static final String GSLIB_FILE = "gslib.xsl";
55
56 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.core.TransformingReceptionist.class.getName());
57
58 /** The expand-gslib.xsl file is in a fixed location */
59 static final String expand_gslib_filepath = GlobalProperties.getGSDL3Home() + File.separatorChar + "interfaces" + File.separatorChar + "core" + File.separatorChar + "transform" + File.separatorChar + EXPAND_GSLIB_FILE;
60
61 /** the list of xslt to use for actions */
62 protected HashMap<String, String> xslt_map = null;
63
64 /** a transformer class to transform xml using xslt */
65 protected XMLTransformer transformer = null;
66
67 protected TransformerFactory transformerFactory = null;
68 protected DOMParser parser = null;
69
70 protected HashMap<String, ArrayList<String>> _metadataRequiredMap = new HashMap<String, ArrayList<String>>();
71
72 boolean _debug = true;
73
74 public TransformingReceptionist()
75 {
76 super();
77 this.xslt_map = new HashMap<String, String>();
78 this.transformer = new XMLTransformer();
79 try
80 {
81 transformerFactory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
82 this.converter = new XMLConverter();
83 //transformerFactory.setURIResolver(new MyUriResolver()) ;
84
85 parser = new DOMParser();
86 parser.setFeature("http://xml.org/sax/features/validation", false);
87 // don't try and load external DTD - no need if we are not validating, and may cause connection errors if a proxy is not set up.
88 parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
89 // a performance test showed that having this on lead to increased
90 // memory use for small-medium docs, and not much gain for large
91 // docs.
92 // http://www.sosnoski.com/opensrc/xmlbench/conclusions.html
93 parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
94 parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
95 // setting a handler for when fatal errors, errors or warnings happen during xml parsing
96 // call XMLConverter's getParseErrorMessage() to get the errorstring that can be rendered as web page
97 this.parser.setErrorHandler(new XMLConverter.ParseErrorHandler());
98 }
99 catch (Exception e)
100 {
101 e.printStackTrace();
102 }
103 }
104
105 /** configures the receptionist - adding in setting up the xslt map */
106 public boolean configure()
107 {
108 if (!super.configure()) {
109
110 return false;
111 }
112
113 logger.info("configuring the TransformingReceptionist");
114
115 // find the config file containing a list of actions
116 File interface_config_file = new File(GSFile.interfaceConfigFile(GSFile.interfaceHome(GlobalProperties.getGSDL3Home(), (String) this.config_params.get(GSConstants.INTERFACE_NAME))));
117 Document config_doc = this.converter.getDOM(interface_config_file, "UTF-8");
118 Element config_elem = config_doc.getDocumentElement();
119
120 // Find the actions again so that we can set up the xslt map
121 Element action_list = (Element) GSXML.getChildByTagName(config_elem, GSXML.ACTION_ELEM + GSXML.LIST_MODIFIER);
122 NodeList actions = action_list.getElementsByTagName(GSXML.ACTION_ELEM);
123
124 for (int i = 0; i < actions.getLength(); i++)
125 {
126 Element action = (Element) actions.item(i);
127 String class_name = action.getAttribute("class");
128 String action_name = action.getAttribute("name");
129
130 // now do the xslt map
131 String xslt = action.getAttribute("xslt");
132 if (!xslt.equals(""))
133 {
134 this.xslt_map.put(action_name, xslt);
135 }
136 NodeList subactions = action.getElementsByTagName(GSXML.SUBACTION_ELEM);
137 for (int j = 0; j < subactions.getLength(); j++)
138 {
139 Element subaction = (Element) subactions.item(j);
140 String subname = subaction.getAttribute(GSXML.NAME_ATT);
141 String subxslt = subaction.getAttribute("xslt");
142
143 String map_key = action_name + ":" + subname;
144 logger.debug("adding in to xslt map, " + map_key + "->" + subxslt);
145 this.xslt_map.put(map_key, subxslt);
146 }
147 }
148
149 getRequiredMetadataNamesFromXSLFiles();
150
151 return true;
152 }
153
154 protected void getRequiredMetadataNamesFromXSLFiles()
155 {
156 ArrayList<File> xslFiles = GSFile.getAllXSLFiles((String) this.config_params.get(GSConstants.SITE_NAME));
157
158 HashMap<String, ArrayList<String>> includes = new HashMap<String, ArrayList<String>>();
159 HashMap<String, ArrayList<File>> files = new HashMap<String, ArrayList<File>>();
160 HashMap<String, ArrayList<String>> metaNames = new HashMap<String, ArrayList<String>>();
161
162 //First exploratory pass
163 for (File currentFile : xslFiles)
164 {
165
166 String full_filename = currentFile.getPath();
167 int sep_pos = full_filename.lastIndexOf(File.separator)+1;
168 String local_filename = full_filename.substring(sep_pos);
169 if (local_filename.startsWith(".")) {
170 logger.warn("Greenstone does not normally rely on 'dot' files for XSL transformations.\n Is the following file intended to be part of the digital library installation?\n XSL File being read in:\n " + currentFile.getPath());
171 }
172
173 Document currentDoc = this.converter.getDOM(currentFile);
174 if (currentDoc == null)
175 {
176 // Can happen if an editor creates an auto-save temporary file
177 // (such as #header.xsl#) that is not well formed XML
178 continue;
179 }
180
181 HashSet<String> extra_meta_names = new HashSet<String>();
182 GSXSLT.findExtraMetadataNames(currentDoc.getDocumentElement(), extra_meta_names);
183 ArrayList<String> names = new ArrayList<String>(extra_meta_names);
184
185 metaNames.put(currentFile.getAbsolutePath(), names);
186
187 NodeList includeElems = currentDoc.getElementsByTagNameNS(GSXML.XSL_NAMESPACE, "include");
188 NodeList importElems = currentDoc.getElementsByTagNameNS(GSXML.XSL_NAMESPACE, "import");
189
190
191 ArrayList<String> includeAndImportList = new ArrayList<String>();
192 for (int i = 0; i < includeElems.getLength(); i++)
193 {
194 includeAndImportList.add(((Element) includeElems.item(i)).getAttribute(GSXML.HREF_ATT));
195 }
196 for (int i = 0; i < importElems.getLength(); i++)
197 {
198 includeAndImportList.add(((Element) importElems.item(i)).getAttribute(GSXML.HREF_ATT));
199 }
200 includes.put(currentFile.getAbsolutePath(), includeAndImportList);
201
202 String filename = currentFile.getName();
203 if (files.get(filename) == null)
204 {
205 ArrayList<File> fileList = new ArrayList<File>();
206 fileList.add(currentFile);
207 files.put(currentFile.getName(), fileList);
208 }
209 else
210 {
211 ArrayList<File> fileList = files.get(filename);
212 fileList.add(currentFile);
213 }
214 }
215
216 //Second pass
217 for (File currentFile : xslFiles)
218 {
219 ArrayList<File> filesToGet = new ArrayList<File>();
220 filesToGet.add(currentFile);
221
222 ArrayList<String> fullNameList = new ArrayList<String>();
223
224 while (filesToGet.size() > 0)
225 {
226 File currentFileTemp = filesToGet.remove(0);
227
228 //Add the names from this file
229 ArrayList<String> currentNames = metaNames.get(currentFileTemp.getAbsolutePath());
230 if (currentNames == null)
231 {
232 continue;
233 }
234
235 fullNameList.addAll(currentNames);
236
237 ArrayList<String> includedHrefs = includes.get(currentFileTemp.getAbsolutePath());
238
239 for (String href : includedHrefs)
240 {
241 int lastSepIndex = href.lastIndexOf("/");
242 if (lastSepIndex != -1)
243 {
244 href = href.substring(lastSepIndex + 1);
245 }
246
247 ArrayList<File> filesToAdd = files.get(href);
248 if (filesToAdd != null)
249 {
250 filesToGet.addAll(filesToAdd);
251 }
252 }
253 }
254
255 _metadataRequiredMap.put(currentFile.getAbsolutePath(), fullNameList);
256 }
257 }
258
259 protected void preProcessRequest(Element request)
260 {
261 String action = request.getAttribute(GSXML.ACTION_ATT);
262 String subaction = request.getAttribute(GSXML.SUBACTION_ATT);
263
264 String name = null;
265 if (!subaction.equals(""))
266 {
267 String key = action + ":" + subaction;
268 name = this.xslt_map.get(key);
269 }
270 // try the action by itself
271 if (name == null)
272 {
273 name = this.xslt_map.get(action);
274 }
275
276 Element cgi_param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
277 String collection = "";
278
279 if (cgi_param_list != null)
280 {
281 // Don't waste time getting all the parameters
282 HashMap<String, Serializable> params = GSXML.extractParams(cgi_param_list, false);
283 collection = (String) params.get(GSParams.COLLECTION);
284 if (collection == null)
285 {
286 collection = "";
287 }
288 }
289
290 ArrayList<File> stylesheets = GSFile.getStylesheetFiles(GlobalProperties.getGSDL3Home(), (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, name);
291
292 Document doc = XMLConverter.newDOM();
293 Element extraMetadataList = doc.createElement(GSXML.EXTRA_METADATA + GSXML.LIST_MODIFIER);
294 HashSet<String> name_set = new HashSet<String>();
295 for (File stylesheet : stylesheets)
296 {
297 ArrayList<String> requiredMetadata = _metadataRequiredMap.get(stylesheet.getAbsolutePath());
298
299 if (requiredMetadata != null)
300 {
301 for (String metadataString : requiredMetadata)
302 {
303 if (!name_set.contains(metadataString)) {
304 name_set.add(metadataString);
305 Element metadataElem = doc.createElement(GSXML.EXTRA_METADATA);
306 metadataElem.setAttribute(GSXML.NAME_ATT, metadataString);
307 extraMetadataList.appendChild(metadataElem);
308 }
309 }
310 }
311 }
312 request.appendChild(request.getOwnerDocument().importNode(extraMetadataList, true));
313 }
314
315 protected Node postProcessPage(Element page)
316 {
317 // might need to add some data to the page
318 addExtraInfo(page);
319
320
321 // transform the page using xslt
322
323 String currentInterface = (String) config_params.get(GSConstants.INTERFACE_NAME);
324
325 Element request = (Element) GSXML.getChildByTagName(page, GSXML.PAGE_REQUEST_ELEM);
326 String output = request.getAttribute(GSXML.OUTPUT_ATT);
327
328 boolean useClientXSLT = (Boolean) config_params.get(GSConstants.USE_CLIENT_SIDE_XSLT);
329 //logger.info("Client side transforms allowed? " + allowsClientXSLT);
330
331 if (useClientXSLT)
332 {
333 // if not specified, output defaults to 'html', but this isn't what we want when useClientXSLT is on
334 if (output.equals("html")) {
335 output = "xsltclient";
336 }
337 }
338 Node transformed_page = transformPage(page,currentInterface,output);
339
340 if (useClientXSLT) {
341 return transformed_page;
342 }
343 // if the user has specified they want only a part of the full page then subdivide it
344 boolean subdivide = false;
345 String excerptID = null;
346 String excerptTag = null;
347 Element cgi_param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
348 if (cgi_param_list != null)
349 {
350 HashMap<String, Serializable> params = GSXML.extractParams(cgi_param_list, false);
351 if ((excerptID = (String) params.get(GSParams.EXCERPT_ID)) != null)
352 {
353 subdivide = true;
354 }
355 if ((excerptTag = (String) params.get(GSParams.EXCERPT_TAG)) != null)
356 {
357 subdivide = true;
358 }
359 }
360
361 if (subdivide)
362 {
363 Node subdivided_page = subdivide(transformed_page, excerptID, excerptTag);
364 if (subdivided_page != null)
365 {
366 return subdivided_page;
367 }
368 else return null;
369 }
370
371 return transformed_page;
372 }
373
374 protected Node subdivide(Node transformed_page, String excerptID, String excerptTag)
375 {
376 if (excerptID != null)
377 {
378 Node selectedElement = getNodeByIdRecursive(transformed_page, excerptID);
379 modifyNodesByTagRecursive(selectedElement, "a");
380 return selectedElement;
381 }
382 else if (excerptTag != null)
383 {
384 Node selectedElement = getNodeByTagRecursive(transformed_page, excerptTag);
385 return selectedElement;
386 }
387 return transformed_page;
388 }
389
390 protected Node getNodeByIdRecursive(Node parent, String id)
391 {
392 if (parent.hasAttributes() && ((Element) parent).getAttribute("id").equals(id))
393 {
394 return parent;
395 }
396
397 NodeList children = parent.getChildNodes();
398 for (int i = 0; i < children.getLength(); i++)
399 {
400 Node result = null;
401 if ((result = getNodeByIdRecursive(children.item(i), id)) != null)
402 {
403 return result;
404 }
405 }
406 return null;
407 }
408
409 protected Node getNodeByTagRecursive(Node parent, String tag)
410 {
411 if (parent.getNodeType() == Node.ELEMENT_NODE && ((Element) parent).getTagName().equals(tag))
412 {
413 return parent;
414 }
415
416 NodeList children = parent.getChildNodes();
417 for (int i = 0; i < children.getLength(); i++)
418 {
419 Node result = null;
420 if ((result = getNodeByTagRecursive(children.item(i), tag)) != null)
421 {
422 return result;
423 }
424 }
425 return null;
426 }
427
428 protected Node modifyNodesByTagRecursive(Node parent, String tag)
429 {
430 if (parent == null || (parent.getNodeType() == Node.ELEMENT_NODE && ((Element) parent).getTagName().equals(tag)))
431 {
432 return parent;
433 }
434
435 NodeList children = parent.getChildNodes();
436 for (int i = 0; i < children.getLength(); i++)
437 {
438 Node result = null;
439 if ((result = modifyNodesByTagRecursive(children.item(i), tag)) != null)
440 {
441 //TODO: DO SOMETHING HERE?
442 }
443 }
444 return null;
445 }
446
447 protected void replaceNodeWithInterfaceText(Document doc, String interface_name, String lang,
448 Element elem, String attr_name, String attr_val)
449 {
450 String pattern_str_3arg = "util:getInterfaceText\\([^,]+,[^,]+,\\s*'(.+?)'\\s*\\)";
451 String pattern_str_4arg = "util:getInterfaceText\\([^,]+,[^,]+,\\s*'(.+?)'\\s*,\\s*(.+?)\\s*\\)$";
452
453 Pattern pattern3 = Pattern.compile(pattern_str_3arg);
454 Matcher matcher3 = pattern3.matcher(attr_val);
455 if (matcher3.find()) {
456 String dict_key = matcher3.group(1);
457 String dict_val = XSLTUtil.getInterfaceText(interface_name,lang,dict_key);
458
459 Node parent_node = elem.getParentNode();
460
461 Text replacement_text_node = doc.createTextNode(dict_val);
462 parent_node.replaceChild(replacement_text_node,elem);
463 }
464 else {
465 Pattern pattern4 = Pattern.compile(pattern_str_4arg);
466 Matcher matcher4 = pattern4.matcher(attr_val);
467 StringBuffer string_buffer4 = new StringBuffer();
468
469 if (matcher4.find()) {
470 String dict_key = matcher4.group(1);
471 String args = matcher4.group(2);
472 args = args.replaceAll("\\$","\\\\\\$");
473
474 String dict_val = XSLTUtil.getInterfaceText(interface_name,lang,dict_key);
475
476 matcher4.appendReplacement(string_buffer4, "js:getInterfaceTextSubstituteArgs('"+dict_val+"',string("+args+"))");
477 matcher4.appendTail(string_buffer4);
478
479 attr_val = string_buffer4.toString();
480 elem.setAttribute(attr_name,attr_val);
481 }
482 else {
483 logger.error("Failed to find match in attribute: " + attr_name + "=\"" + attr_val + "\"");
484 attr_val = attr_val.replaceAll("util:getInterfaceText\\(.+?,.+?,\\s*(.+?)\\s*\\)","$1");
485 elem.setAttribute(attr_name,attr_val);
486 }
487 }
488
489 }
490
491 protected void resolveExtendedNamespaceAttributesXSLT(Document doc, String interface_name, String lang)
492 {
493 String[] attr_list = new String[] {"select","test"};
494
495 // http://stackoverflow.com/questions/13220520/javascript-replace-child-loop-issue
496 // go through nodeList in reverse to avoid the 'skipping' problem, due to
497 // replaceChild() calls removing items from the "live" nodeList
498
499 NodeList nodeList = doc.getElementsByTagName("*");
500 for (int i=nodeList.getLength()-1; i>=0; i--) {
501 Node node = nodeList.item(i);
502 if (node.getNodeType() == Node.ELEMENT_NODE) {
503 Element elem = (Element)node;
504 for (String attr_name : attr_list) {
505 if (elem.hasAttribute(attr_name)) {
506 String attr_val = elem.getAttribute(attr_name);
507
508 if (attr_val.startsWith("util:getInterfaceText(")) {
509 // replace the node with dictionary lookup
510 replaceNodeWithInterfaceText(doc, interface_name,lang, elem,attr_name,attr_val);
511 }
512 else if (attr_val.contains("util:")) {
513
514 attr_val = attr_val.replaceAll("util:getInterfaceStringsAsJavascript\\(.+?,.+?,\\s*(.+?)\\)","$1");
515
516 //attr_val = attr_val.replaceAll("util:escapeNewLinesAndQuotes\\(\\s*(.+?)\\s*\\)","'escapeNLandQ $1'");
517 //attr_val = attr_val.replaceAll("util:escapeNewLinesAndQuotes\\(\\s*(.+?)\\s*\\)","$1");
518
519 // 'contains()' supported in XSLT 1.0, so OK to change any util:contains() into contains()
520 attr_val = attr_val.replaceAll("util:(contains\\(.+?\\))","$1");
521
522 elem.setAttribute(attr_name,attr_val);
523 }
524
525 if (attr_val.contains("java:")) {
526 if (attr_val.indexOf("getInterfaceTextSubstituteArgs")>=4) {
527
528 attr_val = attr_val.replaceAll("java:.+?\\.(\\w+)\\((.*?)\\)$","js:$1($2)");
529 }
530
531 elem.setAttribute(attr_name,attr_val);
532 }
533 }
534 }
535
536 }
537 }
538 }
539
540
541 protected void resolveExtendedNamespaceAttributesXML(Document doc, String interface_name, String lang)
542 {
543 String[] attr_list = new String[] {"src", "href"};
544
545 // http://stackoverflow.com/questions/13220520/javascript-replace-child-loop-issue
546 // go through nodeList in reverse to avoid the 'skipping' problem, due to
547 // replaceChild() calls removing items from the "live" nodeList
548
549 NodeList nodeList = doc.getElementsByTagName("*");
550 for (int i=nodeList.getLength()-1; i>=0; i--) {
551 Node node = nodeList.item(i);
552 if (node.getNodeType() == Node.ELEMENT_NODE) {
553 Element elem = (Element)node;
554 for (String attr_name : attr_list) {
555 if (elem.hasAttribute(attr_name)) {
556 String attr_val = elem.getAttribute(attr_name);
557
558 if (attr_val.contains("util:getInterfaceText(")) {
559 String pattern_str_3arg = "util:getInterfaceText\\([^,]+,[^,]+,\\s*'(.+?)'\\s*\\)";
560 Pattern pattern3 = Pattern.compile(pattern_str_3arg);
561 Matcher matcher3 = pattern3.matcher(attr_val);
562
563 StringBuffer string_buffer3 = new StringBuffer();
564
565 boolean found_match = false;
566
567 while (matcher3.find()) {
568 found_match = true;
569 String dict_key = matcher3.group(1);
570 String dict_val = XSLTUtil.getInterfaceText(interface_name,lang,dict_key);
571
572 matcher3.appendReplacement(string_buffer3, dict_val);
573 }
574 matcher3.appendTail(string_buffer3);
575
576 if (found_match) {
577 attr_val = string_buffer3.toString();
578 elem.setAttribute(attr_name,attr_val);
579 }
580 else {
581 logger.error("Failed to find match in attribute: " + attr_name + "=\"" + attr_val + "\"");
582 attr_val = attr_val.replaceAll("util:getInterfaceText\\(.+?,.+?,\\s*(.+?)\\s*\\)","$1");
583 elem.setAttribute(attr_name,attr_val);
584 }
585 }
586 else if (attr_val.contains("util:")) {
587
588 logger.error("Encountered unexpected 'util:' prefix exension: " + attr_name + "=\"" + attr_val + "\"");
589 }
590
591 if (attr_val.contains("java:")) {
592 // make anything java: safe from the point of an XSLT without extensions
593 logger.error("Encountered unexpected 'java:' prefix exension: " + attr_name + "=\"" + attr_val + "\"");
594
595 }
596 }
597 }
598
599 }
600 }
601 }
602
603
604
605 /**
606 * overwrite this to add any extra info that might be needed in the page
607 * before transformation
608 */
609 protected void addExtraInfo(Element page)
610 {
611 }
612
613 /**
614 * transform the page using xslt.
615 * we need to get any format element out of the page and add it to the xslt before transforming
616 */
617 protected Node transformPage(Element page_xml, String currentInterface, String output)
618 {
619 _debug = false;
620
621 Element request = (Element) GSXML.getChildByTagName(page_xml, GSXML.PAGE_REQUEST_ELEM);
622
623 //logger.info("Current output mode is: " + output + ", current interface name is: " + currentInterface);
624
625 if (output.equals("xsltclient"))
626 {
627 return generateXSLTClientOutput(request);
628 }
629
630 String action = request.getAttribute(GSXML.ACTION_ATT);
631 String subaction = request.getAttribute(GSXML.SUBACTION_ATT);
632
633 // we should choose how to transform the data based on output, eg diff
634 // choice for html, and wml??
635 // for now, if output=xml, we don't transform the page, we just return
636 // the page xml
637 Document page_with_xslt_params_doc = null;
638
639 if (output.equals("xml") || (output.equals("json")) || output.equals("clientside"))
640 {
641 // Append the xsltparams to the page
642 page_with_xslt_params_doc = converter.newDOM();
643 // Import into new document first!
644 Node page_with_xslt_params = page_with_xslt_params_doc.importNode(page_xml, true);
645 page_with_xslt_params_doc.appendChild(page_with_xslt_params);
646 Element xslt_params = page_with_xslt_params_doc.createElement("xsltparams");
647 page_with_xslt_params.appendChild(xslt_params);
648
649 GSXML.addParameter2ToList(xslt_params, "library_name", (String) config_params.get(GSConstants.LIBRARY_NAME));
650 GSXML.addParameter2ToList(xslt_params, "interface_name", (String) config_params.get(GSConstants.INTERFACE_NAME));
651 GSXML.addParameter2ToList(xslt_params, "site_name", (String) config_params.get(GSConstants.SITE_NAME));
652 GSXML.addParameter2ToList(xslt_params, "cookie_consent", (String) config_params.get(GSConstants.COOKIE_CONSENT));
653 Boolean useClientXSLT = (Boolean) config_params.get(GSConstants.USE_CLIENT_SIDE_XSLT);
654 GSXML.addParameter2ToList(xslt_params, "use_client_side_xslt", useClientXSLT.toString());
655 GSXML.addParameter2ToList(xslt_params, "filepath", GlobalProperties.getGSDL3Home());
656
657 if ((output.equals("xml")) || output.equals("json"))
658 {
659 // Just return the page XML
660 // in the case of "json", calling method responsible for converting to JSON-string
661 return page_with_xslt_params_doc.getDocumentElement();
662 }
663 // in the case of client side, later on we'll use this doc with xslt params added,
664 // along with the xsl.
665 }
666
667 Element cgi_param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
668 String collection = "";
669 String inline_template = "";
670 if (cgi_param_list != null)
671 {
672 // Don't waste time getting all the parameters
673 HashMap<String, Serializable> params = GSXML.extractParams(cgi_param_list, false);
674 collection = (String) params.get(GSParams.COLLECTION);
675 if (collection == null)
676 {
677 collection = "";
678 }
679
680 inline_template = (String) params.get(GSParams.INLINE_TEMPLATE);
681 String debug_p = (String) params.get(GSParams.DEBUG);
682 if (debug_p != null && (debug_p.equals("on") || debug_p.equals("1") || debug_p.equals("true")))
683 {
684 String[] groups = new UserContext(request).getGroups();
685
686 boolean found = false;
687 for (String g : groups)
688 {
689 if (g.equals("administrator"))
690 {
691 found = true;
692 break;
693 }
694 if (!collection.equals("")) {
695 if (g.equals("all-collections-editor")) {
696 found = true;
697 break;
698 }
699
700 if (g.equals(collection+"-collection-editor")) {
701 found = true;
702 break;
703 }
704 }
705 }
706 if (found)
707 {
708 _debug = true;
709 }
710 }
711 }
712
713 config_params.put("collName", collection);
714
715 // find the appropriate stylesheet (based on action/subaction) - eg a=p&sa=home will be home.xsl
716 // This mapping is defined in interfaceConfig.xsl
717 // All versions of the stylesheet (base interface, interface, site, collection) are
718 // merged together into one document
719 Document page_xsl = getXSLTDocument(action, subaction, collection);
720 String page_xsl_filename = getXSLTFilename(action, subaction); // for debug purposes
721 if (page_xsl == null)
722 {
723 logger.error("Couldn't find and/or load the stylesheet ("+page_xsl_filename+") for a="+action+", sa="+subaction+", in collection "+collection);
724 return XMLTransformer.constructErrorXHTMLPage("Couldn't find and/or load the stylesheet \""+page_xsl_filename+"\" for a="+action+", sa="+subaction+", in collection "+collection);
725 }
726
727 if (output.equals("xsl1")) {
728 // if we just output the page_xsl directly then there may be unescaped & in the javascript,
729 // and the page won't display properly
730 return converter.getDOM(getStringFromDocument(page_xsl));
731 }
732
733
734 // put the page into a document - this is necessary for xslt to get
735 // the paths right if you have paths relative to the document root
736 // eg /page.
737 Document page_xml_doc = XMLConverter.newDOM();
738 page_xml_doc.appendChild(page_xml_doc.importNode(page_xml, true));
739 Element page_response = (Element) GSXML.getChildByTagName(page_xml, GSXML.PAGE_RESPONSE_ELEM);
740 Element format_elem = (Element) GSXML.getChildByTagName(page_response, GSXML.FORMAT_ELEM);
741
742 if (output.equals("format1"))
743 {
744 return format_elem;
745 }
746
747 // do we have language attribute for page?
748 String lang_att = page_xml.getAttribute(GSXML.LANG_ATT);
749 if (lang_att != null && lang_att.length() > 0)
750 {
751 config_params.put("lang", lang_att);
752 }
753
754 if (format_elem != null)
755 {
756 //page_response.removeChild(format_elem);
757
758 // need to transform the format info
759 // run expand-gsf.xsl over the format_elem. We need to do this now to turn
760 // eg gsf:template into xsl:template so that the merging works properly.
761 // xsl:templates will get merged
762 // into the main stylesheet, but gsf:templates won't.
763
764 Document format_doc = XMLConverter.newDOM();
765 format_doc.appendChild(format_doc.importNode(format_elem, true));
766
767 if (_debug) {
768
769 String siteHome = GSFile.siteHome(GlobalProperties.getGSDL3Home(), (String) this.config_params.get(GSConstants.SITE_NAME));
770 GSXSLT.insertDebugElements(format_doc, GSFile.collectionConfigFile(siteHome, collection));
771 }
772
773 // should we be doing the double pass here too?
774 Node result = transformGSFElements(collection, format_doc, EXPAND_GSF_FILE);
775 // Since we started creating documents with DocTypes, we can end up with
776 // Document objects here. But we will be working with an Element instead,
777 // so we grab the DocumentElement() of the Document object in such a case.
778 Element new_format;
779 if (result.getNodeType() == Node.DOCUMENT_NODE)
780 {
781 new_format = ((Document) result).getDocumentElement();
782 }
783 else
784 {
785 new_format = (Element) result;
786 }
787
788 if (output.equals("format"))
789 {
790 return new_format;
791 }
792
793 // add the extracted format statements in to the main stylesheet
794 if (_debug)
795 {
796 String siteHome = GSFile.siteHome(GlobalProperties.getGSDL3Home(), (String) this.config_params.get(GSConstants.SITE_NAME));
797 GSXSLT.mergeStylesheetsDebug(page_xsl, new_format, true, true, "OTHER1", GSFile.collectionConfigFile(siteHome, collection));
798 }
799 else
800 {
801 GSXSLT.mergeStylesheets(page_xsl, new_format, true);
802 }
803
804
805 }
806
807 if (output.equals("xsl2")) {
808 return converter.getDOM(getStringFromDocument(page_xsl));
809 }
810
811 Document inline_template_doc = null;
812 if (inline_template != null)
813 {
814 try
815 {
816 inline_template_doc = this.converter.getDOM("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"1.0\" "+GSXML.ALL_NAMESPACES_ATTS + ">" + inline_template + "</xsl:stylesheet>", "UTF-8");
817
818 if (_debug)
819 {
820 GSXSLT.mergeStylesheetsDebug(page_xsl, inline_template_doc.getDocumentElement(), true, true, "OTHER2", "INLINE");
821 }
822 else
823 {
824 //GSXSLT.mergeStylesheets(skinAndLibraryDoc, inlineTemplateDoc.getDocumentElement(), true);
825 GSXSLT.mergeStylesheets(page_xsl, inline_template_doc.getDocumentElement(), true);
826 }
827 }
828 catch (Exception ex)
829 {
830 ex.printStackTrace();
831 }
832 }
833
834
835 if (output.equals("ilt")) {
836 return converter.getDOM(getStringFromDocument(inline_template_doc));
837 }
838 if (output.equals("xsl3")) {
839 return converter.getDOM(getStringFromDocument(page_xsl));
840 }
841
842 // once we are here, have got the main page xsl loaded up. Have added in any format statements from the source xml, and added in any inline template which came through cgi params.
843
844 // next we load in the import and include files. - these, too, go through the inheritance cascade (base interface, interface, site, collection) before being added into the main document
845
846 if (_debug)
847 {
848 GSXSLT.inlineImportAndIncludeFilesDebug(page_xsl, null, _debug, page_xsl_filename, (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces);
849 }
850 else
851 {
852 GSXSLT.inlineImportAndIncludeFiles(page_xsl, null, (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces);
853
854 }
855
856 if (output.equals("xsl4")) {
857 return converter.getDOM(getStringFromDocument(page_xsl));
858 }
859
860 // The next step is to process the page_xsl + gslib_xsl by
861 // expand-gslib.xsl to expand all the gslib elements
862
863 Document expand_gslib_xsl_doc;
864 try
865 {
866 // interfaces/core/transform/expand-gslib.xsl
867 // this takes skinandLibraryXsl, copies skinXSL, merges elements of libraryXsl into it, and replaces gslib elements
868 expand_gslib_xsl_doc = getDoc(expand_gslib_filepath);
869 String errMsg = ((XMLConverter.ParseErrorHandler) parser.getErrorHandler()).getErrorMessage();
870 if (errMsg != null)
871 {
872 return XMLTransformer.constructErrorXHTMLPage("error loading file: " + expand_gslib_filepath + "\n" + errMsg);
873 }
874 }
875 catch (java.io.FileNotFoundException e)
876 {
877 return fileNotFoundErrorPage(e.getMessage());
878 }
879 catch (Exception e)
880 {
881 e.printStackTrace();
882 System.out.println("error loading "+expand_gslib_filepath);
883 return XMLTransformer.constructErrorXHTMLPage("Error loading file: "+ expand_gslib_filepath+"\n" + e.getMessage());
884 }
885
886 // gslib.xsl
887 Document gslib_xsl_doc = null;
888 try
889 {
890 gslib_xsl_doc = GSXSLT.mergedXSLTDocumentCascade(GSLIB_FILE, (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, _debug);
891 }
892 catch (Exception e)
893 {
894 e.printStackTrace();
895 System.out.println("error loading gslib xslt");
896 return XMLTransformer.constructErrorXHTMLPage("error loading gslib xslt\n" + e.getMessage());
897 }
898
899 if (output.equals("gslib-expander")) {
900 return converter.getDOM(getStringFromDocument(expand_gslib_xsl_doc));
901 }
902 if (output.equals("gslib1")) {
903 return converter.getDOM(getStringFromDocument(gslib_xsl_doc));
904 }
905 // Combine the skin file and library variables/templates into one document.
906 // Please note: We dont just use xsl:import because the preprocessing stage
907 // needs to know what's available in the library.
908
909
910 // add in all gslib.xsl's include and import files
911 // debug?? use debug method?? or does it not make sense here??
912 GSXSLT.inlineImportAndIncludeFiles(gslib_xsl_doc, null, (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces);
913 if (output.equals("gslib")) {
914 return converter.getDOM(getStringFromDocument(gslib_xsl_doc));
915 }
916
917 // if the page xsl or gslib xsl uses namespaces that are not listed in
918 // expand_gslib, then they will be ignored. So just check through and add
919 // any in that are missing.
920 GSXML.addMissingNamespaceAttributes(expand_gslib_xsl_doc.getDocumentElement(), page_xsl.getDocumentElement());
921 GSXML.addMissingNamespaceAttributes(expand_gslib_xsl_doc.getDocumentElement(), gslib_xsl_doc.getDocumentElement());
922
923 Document pageAndGslibXsl = null;
924 Document pageAndGslibDoc = converter.newDOM();
925
926 // now, we transform all the gslib elements
927 {
928
929
930 pageAndGslibXsl = converter.newDOM();
931 Element root = pageAndGslibXsl.createElement("pageAndGslibXsl");
932 pageAndGslibXsl.appendChild(root);
933
934 Element s = pageAndGslibXsl.createElement("pageXsl");
935 s.appendChild(pageAndGslibXsl.importNode(page_xsl.getDocumentElement(), true));
936 root.appendChild(s);
937
938 Element l = pageAndGslibXsl.createElement("gslibXsl");
939 if (gslib_xsl_doc != null)
940 {
941 Element gslib_xsl_el = gslib_xsl_doc.getDocumentElement();
942 l.appendChild(pageAndGslibXsl.importNode(gslib_xsl_el, true));
943 }
944 root.appendChild(l);
945
946 if (output.equals("xsl5")) {
947 return converter.getDOM(getStringFromDocument(pageAndGslibXsl));
948 }
949 // actually merge the gslib file with the page
950 XMLTransformer preProcessor = new XMLTransformer();
951 preProcessor.transform_withResultNode(expand_gslib_xsl_doc, pageAndGslibXsl, pageAndGslibDoc);
952 }
953 if (output.equals("xsl6")) {
954 return converter.getDOM(getStringFromDocument(pageAndGslibDoc));
955 }
956
957 pageAndGslibDoc = (Document) transformGSFElements(collection, pageAndGslibDoc, EXPAND_GSF_PASS1_FILE);
958
959 if (output.equals("xsl7")) {
960 return converter.getDOM(getStringFromDocument(pageAndGslibDoc));
961 }
962
963 pageAndGslibDoc = (Document) transformGSFElements(collection, pageAndGslibDoc, EXPAND_GSF_FILE);
964
965 if (output.equals("xsl") || output.equals("skinandlibdocfinal"))
966 {
967 return converter.getDOM(getStringFromDocument(pageAndGslibDoc));
968 }
969
970 if (output.equals("clientside"))
971 {
972
973 // Go through and 'fix up' any 'util:...' or 'java:...' attributes the pageAndGslibDoc has
974 String lang = (String)config_params.get("lang");
975 resolveExtendedNamespaceAttributesXSLT(pageAndGslibDoc,currentInterface,lang); // test= and select= attributes
976 resolveExtendedNamespaceAttributesXML(pageAndGslibDoc,currentInterface,lang); // href= and src= attributes
977 Node skinAndLibFinal = converter.getDOM(getStringFromDocument(pageAndGslibDoc));
978
979 // Send XML and skinandlibdoc down the line together
980 Document finalDoc = converter.newDOM();
981 Node finalDocSkin = finalDoc.importNode(pageAndGslibDoc.getDocumentElement(), true);
982 Node finalDocXML = finalDoc.importNode(page_with_xslt_params_doc.getDocumentElement(), true);
983 Element root = finalDoc.createElement("skinlibfinalPlusXML");
984 root.appendChild(finalDocSkin);
985 root.appendChild(finalDocXML);
986 finalDoc.appendChild(root);
987 return (Node) finalDoc.getDocumentElement();
988 }
989
990 ///logger.debug("final xml is ");
991 ///logger.debug(XMLConverter.getPrettyString(page_xml_doc));
992
993 ///logger.debug("final xsl is");
994 ///logger.debug(XMLConverter.getPrettyString(pageAndGslibDoc));
995
996 // The transformer will now work out the resulting doctype from any set in the (merged) stylesheets and
997 // will set this in the output document it creates. So don't pass in any docWithDocType to the transformer
998
999 // Here, we finally transform the page xml source with the complete xsl file
1000 Node finalResult = this.transformer.transform(pageAndGslibDoc, page_xml_doc, config_params);
1001
1002 if (_debug)
1003 {
1004 GSXSLT.fixTables((Document) finalResult);
1005 }
1006
1007 return finalResult;
1008
1009 }
1010
1011 protected Node generateXSLTClientOutput(Element request) {
1012
1013 // DocType defaults in case the skin doesn't have an "xsl:output" element
1014 String qualifiedName = "html";
1015 String publicID = "-//W3C//DTD HTML 4.01 Transitional//EN";
1016 String systemID = "http://www.w3.org/TR/html4/loose.dtd";
1017
1018 // We need to create an empty document with a predefined DocType,
1019 // that will then be used for the transformation by the DOMResult
1020 Document docWithDoctype = converter.newDOM(qualifiedName, publicID, systemID);
1021 String baseURL = request.getAttribute(GSXML.BASE_URL);
1022
1023 // If you're just getting the client-side transform page, why bother with the rest of this?
1024 Element html = docWithDoctype.createElement("html");
1025 Element img = docWithDoctype.createElement("img");
1026 img.setAttribute("src", "loading.gif"); // Make it dynamic
1027 img.setAttribute("alt", "Please wait...");
1028 Text title_text = docWithDoctype.createTextNode("Please wait..."); // Make this language dependent
1029 Element head = docWithDoctype.createElement("head");
1030
1031 // e.g., <base href="http://localhost:8383/greenstone3/" /><!-- [if lte IE 6]></base><![endif] -->
1032 Element base = docWithDoctype.createElement("base");
1033 base.setAttribute("href",baseURL);
1034 Comment opt_end_base = docWithDoctype.createComment("[if lte IE 6]></base><![endif]");
1035
1036 Element title = docWithDoctype.createElement("title");
1037 title.appendChild(title_text);
1038
1039 Element body = docWithDoctype.createElement("body");
1040
1041 Element jquery_script = docWithDoctype.createElement("script");
1042 jquery_script.setAttribute("src", "jquery-3.6.0.min.js");
1043 jquery_script.setAttribute("type", "text/javascript");
1044 Comment jquery_comment = docWithDoctype.createComment("jQuery");
1045 jquery_script.appendChild(jquery_comment);
1046
1047 Element saxonce_script = docWithDoctype.createElement("script");
1048 saxonce_script.setAttribute("src", "Saxonce/Saxonce.nocache.js");
1049 saxonce_script.setAttribute("type", "text/javascript");
1050 Comment saxonce_comment = docWithDoctype.createComment("SaxonCE");
1051 saxonce_script.appendChild(saxonce_comment);
1052
1053 Element xsltutil_script = docWithDoctype.createElement("script");
1054 xsltutil_script.setAttribute("src", "xslt-util.js");
1055 xsltutil_script.setAttribute("type", "text/javascript");
1056 Comment xsltutil_comment = docWithDoctype.createComment("JavaScript version of XSLTUtil.java");
1057 xsltutil_script.appendChild(xsltutil_comment);
1058
1059 Element script = docWithDoctype.createElement("script");
1060 Comment script_comment = docWithDoctype.createComment("Filler for browser");
1061 script.setAttribute("src", "client-side-xslt.js");
1062 script.setAttribute("type", "text/javascript");
1063 script.appendChild(script_comment);
1064
1065 Element pagevar = docWithDoctype.createElement("script");
1066 Element style = docWithDoctype.createElement("style");
1067 style.setAttribute("type", "text/css");
1068 Text style_text = docWithDoctype.createTextNode("body { text-align: center; padding: 50px; font: 14pt Arial, sans-serif; font-weight: bold; }");
1069 pagevar.setAttribute("type", "text/javascript");
1070 Text page_var_text = docWithDoctype.createTextNode("var placeholder = true;");
1071
1072 html.appendChild(head);
1073 head.appendChild(base); head.appendChild(opt_end_base);
1074 head.appendChild(title);
1075 head.appendChild(style);
1076 style.appendChild(style_text);
1077 html.appendChild(body);
1078 head.appendChild(pagevar);
1079 head.appendChild(jquery_script);
1080 head.appendChild(saxonce_script);
1081 head.appendChild(xsltutil_script);
1082 head.appendChild(script);
1083 pagevar.appendChild(page_var_text);
1084
1085 body.appendChild(img);
1086 docWithDoctype.appendChild(html);
1087
1088 return (Node) docWithDoctype;
1089
1090 }
1091
1092 // transform the xsl with xsl to replace all gsf elements. We do this in 2 stages -
1093 // first do just a text pass, that way we can have gsf elements in the text content
1094 protected Node transformGSFElements(String collection, Document source_xsl, String expand_gsf_filename) {
1095
1096 String expand_gsf_file = GSFile.stylesheetFile(GlobalProperties.getGSDL3Home(), (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, expand_gsf_filename);
1097 Document expand_gsf_doc = this.converter.getDOM(new File(expand_gsf_file));
1098
1099 if (expand_gsf_doc != null)
1100 {
1101 return this.transformer.transform(expand_gsf_doc, source_xsl, config_params);
1102 }
1103 return source_xsl;
1104
1105 }
1106
1107 // method to convert Document to a proper XML string for debug purposes only
1108 protected String getStringFromDocument(Document doc)
1109 {
1110 String content = "";
1111 try
1112 {
1113 DOMSource domSource = new DOMSource(doc);
1114 StringWriter writer = new StringWriter();
1115 StreamResult result = new StreamResult(writer);
1116 TransformerFactory tf = TransformerFactory.newInstance();
1117 Transformer transformer = tf.newTransformer();
1118 transformer.transform(domSource, result);
1119 content = writer.toString();
1120 System.out.println("Change the & to &Amp; for proper debug display");
1121 content = StringUtils.replace(content, "&", "&amp;");
1122 writer.flush();
1123 }
1124 catch (TransformerException ex)
1125 {
1126 ex.printStackTrace();
1127 return null;
1128 }
1129 return content;
1130 }
1131
1132 protected synchronized Document getDoc(String docName) throws Exception
1133 {
1134 File xslt_file = new File(docName);
1135
1136 FileReader reader = new FileReader(xslt_file);
1137 InputSource xml_source = new InputSource(reader);
1138 this.parser.parse(xml_source);
1139 Document doc = this.parser.getDocument();
1140
1141 return doc;
1142 }
1143
1144 protected String getXSLTFilename(String action, String subaction) {
1145 String name = null;
1146 if (!subaction.equals(""))
1147 {
1148 String key = action + ":" + subaction;
1149 name = this.xslt_map.get(key);
1150 }
1151 // try the action by itself
1152 if (name == null)
1153 {
1154 name = this.xslt_map.get(action);
1155 }
1156 if (name == null)
1157 {
1158 // so we can reandomly create any named page
1159 if (action.equals("p") && !subaction.equals(""))
1160 {
1161 // TODO: pages/ won't work for interface other than default!!
1162 name = "pages/" + subaction + ".xsl";
1163 }
1164
1165 }
1166 return name;
1167 }
1168
1169
1170 protected Document getXSLTDocument(String action, String subaction, String collection)
1171 {
1172 String name = getXSLTFilename(action, subaction);
1173 Document finalDoc = null;
1174 if(name != null)
1175 {
1176 // this finds all the stylesheets named "name" and merges them together, in the order of
1177 // base interface, current interface, site, collection - the latter overriding the former.
1178 // templates with the same name will replace earlier versions
1179 finalDoc = GSXSLT.mergedXSLTDocumentCascade(name, (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, _debug);
1180 }
1181 return finalDoc;
1182 }
1183
1184 // returns the path to the gslib.xsl file that is applicable for the current interface
1185 protected String getGSLibXSLFilename()
1186 {
1187 return GSFile.xmlTransformDir(GSFile.interfaceHome(GlobalProperties.getGSDL3Home(), (String) this.config_params.get(GSConstants.INTERFACE_NAME))) + File.separatorChar + "gslib.xsl";
1188 }
1189
1190 // Call this when a FileNotFoundException could be thrown when loading an xsl (xml) file.
1191 // Returns an error xhtml page indicating which xsl (or other xml) file is missing.
1192 protected Document fileNotFoundErrorPage(String filenameMessage)
1193 {
1194 String errorMessage = "ERROR missing file: " + filenameMessage;
1195 Element errPage = XMLTransformer.constructErrorXHTMLPage(errorMessage);
1196 logger.error(errorMessage);
1197 return errPage.getOwnerDocument();
1198 }
1199}
Note: See TracBrowser for help on using the repository browser.