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

Last change on this file since 22085 was 22085, checked in by sjm84, 14 years ago

Created a util package from classes that could be useful outside of their original packages

  • Property svn:keywords set to Author Date Id Revision
File size: 23.4 KB
Line 
1package org.greenstone.gsdl3.core;
2
3import org.greenstone.util.GlobalProperties;
4import org.greenstone.gsdl3.util.*;
5import org.greenstone.gsdl3.action.*;
6// XML classes
7import org.w3c.dom.Node;
8import org.w3c.dom.NodeList;
9import org.w3c.dom.Document;
10import org.w3c.dom.Element;
11import org.xml.sax.InputSource;
12
13// other java classes
14import java.io.File;
15import java.io.StringWriter;
16import java.io.FileReader;
17import java.io.FileNotFoundException;
18import java.util.HashMap;
19import java.util.Enumeration;
20
21import javax.xml.parsers.*;
22import javax.xml.transform.*;
23import javax.xml.transform.dom.*;
24import javax.xml.transform.stream.*;
25import org.apache.log4j.*;
26import org.apache.xerces.dom.*;
27import org.apache.xerces.parsers.DOMParser;
28
29/** A receptionist that uses xslt to transform the page_data before returning it. . Receives requests consisting
30 * of an xml representation of cgi args, and returns the page of data - in
31 * html by default. The requests are processed by the appropriate action class
32 *
33 * @see Action
34 */
35public class TransformingReceptionist extends Receptionist{
36
37 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.core.TransformingReceptionist.class.getName());
38
39 /** The preprocess.xsl file is in a fixed location */
40 static final String preprocess_xsl_filename = GlobalProperties.getGSDL3Home() + File.separatorChar
41 + "ui" + File.separatorChar + "xslt" + File.separatorChar + "preProcess.xsl";
42
43 /** the list of xslt to use for actions */
44 protected HashMap xslt_map = null;
45
46 /** a transformer class to transform xml using xslt */
47 protected XMLTransformer transformer=null;
48
49 protected TransformerFactory transformerFactory=null;
50 protected DOMParser parser = null;
51 public TransformingReceptionist() {
52 super();
53 this.xslt_map = new HashMap();
54 this.transformer = new XMLTransformer();
55 try {
56 transformerFactory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
57 this.converter = new XMLConverter();
58 //transformerFactory.setURIResolver(new MyUriResolver()) ;
59
60 parser = new DOMParser();
61 parser.setFeature("http://xml.org/sax/features/validation", false);
62 // 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.
63 parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
64 // a performance test showed that having this on lead to increased
65 // memory use for small-medium docs, and not much gain for large
66 // docs.
67 // http://www.sosnoski.com/opensrc/xmlbench/conclusions.html
68 parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
69 parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
70 // setting a handler for when fatal errors, errors or warnings happen during xml parsing
71 // call XMLConverter's getParseErrorMessage() to get the errorstring that can be rendered as web page
72 this.parser.setErrorHandler(new XMLConverter.ParseErrorHandler());
73 }
74 catch (Exception e) {
75 e.printStackTrace();
76 }
77
78 }
79
80 /** configures the receptionist - overwrite this to set up the xslt map*/
81 public boolean configure() {
82
83 if (this.config_params==null) {
84 logger.error(" config variables must be set before calling configure");
85 return false;
86 }
87 if (this.mr==null) {
88 logger.error(" message router must be set before calling configure");
89 return false;
90 }
91
92 // find the config file containing a list of actions
93 File interface_config_file = new File(GSFile.interfaceConfigFile(GSFile.interfaceHome(GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.INTERFACE_NAME))));
94 if (!interface_config_file.exists()) {
95 logger.error(" interface config file: "+interface_config_file.getPath()+" not found!");
96 return false;
97 }
98 Document config_doc = this.converter.getDOM(interface_config_file, "utf-8");
99 if (config_doc == null) {
100 logger.error(" could not parse interface config file: "+interface_config_file.getPath());
101 return false;
102 }
103 Element config_elem = config_doc.getDocumentElement();
104 String base_interface = config_elem.getAttribute("baseInterface");
105 setUpBaseInterface(base_interface);
106 setUpInterfaceOptions(config_elem);
107
108 Element action_list = (Element)GSXML.getChildByTagName(config_elem, GSXML.ACTION_ELEM+GSXML.LIST_MODIFIER);
109 NodeList actions = action_list.getElementsByTagName(GSXML.ACTION_ELEM);
110
111 for (int i=0; i<actions.getLength(); i++) {
112 Element action = (Element) actions.item(i);
113 String class_name = action.getAttribute("class");
114 String action_name = action.getAttribute("name");
115 Action ac = null;
116 try {
117 ac = (Action)Class.forName("org.greenstone.gsdl3.action."+class_name).newInstance();
118 } catch (Exception e) {
119 logger.error(" couldn't load in action "+class_name);
120 e.printStackTrace();
121 continue;
122 }
123 ac.setConfigParams(this.config_params);
124 ac.setMessageRouter(this.mr);
125 ac.configure();
126 ac.getActionParameters(this.params);
127 this.action_map.put(action_name, ac);
128
129 // now do the xslt map
130 String xslt = action.getAttribute("xslt");
131 if (!xslt.equals("")) {
132 this.xslt_map.put(action_name, xslt);
133 }
134 NodeList subactions = action.getElementsByTagName(GSXML.SUBACTION_ELEM);
135 for (int j=0; j<subactions.getLength(); j++) {
136 Element subaction = (Element)subactions.item(j);
137 String subname = subaction.getAttribute(GSXML.NAME_ATT);
138 String subxslt = subaction.getAttribute("xslt");
139
140 String map_key = action_name+":"+subname;
141 logger.debug("adding in to xslt map, "+map_key+"->"+subxslt);
142 this.xslt_map.put(map_key, subxslt);
143 }
144 }
145 Element lang_list = (Element)GSXML.getChildByTagName(config_elem, "languageList");
146 if (lang_list == null) {
147 logger.error(" didn't find a language list in the config file!!");
148 } else {
149 this.language_list = (Element) this.doc.importNode(lang_list, true);
150 }
151
152 return true;
153 }
154
155
156 protected Node postProcessPage(Element page) {
157 // might need to add some data to the page
158 addExtraInfo(page);
159 // transform the page using xslt
160 Node transformed_page = transformPage(page);
161
162 return transformed_page;
163 }
164
165 /** overwrite this to add any extra info that might be needed in the page before transformation */
166 protected void addExtraInfo(Element page) {}
167
168 /** transform the page using xslt
169 * we need to get any format element out of the page and add it to the xslt
170 * before transforming */
171 protected Node transformPage(Element page) {
172
173 logger.debug("page before transfomring:");
174 logger.debug(this.converter.getPrettyString(page));
175
176 Element request = (Element)GSXML.getChildByTagName(page, GSXML.PAGE_REQUEST_ELEM);
177 String action = request.getAttribute(GSXML.ACTION_ATT);
178 String subaction = request.getAttribute(GSXML.SUBACTION_ATT);
179
180 String output = request.getAttribute(GSXML.OUTPUT_ATT);
181 // we should choose how to transform the data based on output, eg diff
182 // choice for html, and wml??
183 // for now, if output=xml, we don't transform the page, we just return
184 // the page xml
185 if (output.equals("xml")) {
186 return page;
187 }
188
189
190 Element cgi_param_list = (Element)GSXML.getChildByTagName(request, GSXML.PARAM_ELEM+GSXML.LIST_MODIFIER);
191 String collection = "";
192 if (cgi_param_list != null) {
193 HashMap params = GSXML.extractParams(cgi_param_list, false);
194 collection = (String)params.get(GSParams.COLLECTION);
195 if (collection == null) collection = "";
196 }
197
198 String xslt_file = getXSLTFileName(action, subaction, collection);
199 if (xslt_file==null) {
200 // returning file not found error page to indicate which file is missing
201 return fileNotFoundErrorPage(xslt_file);
202 }
203
204 Document style_doc = this.converter.getDOM(new File(xslt_file), "UTF-8");
205 String errorPage = this.converter.getParseErrorMessage();
206 if(errorPage != null) {
207 return XMLTransformer.constructErrorXHTMLPage(
208 "Cannot parse the xslt file: " + xslt_file + "\n" + errorPage);
209 }
210 if (style_doc == null) {
211 logger.error(" cant parse the xslt file needed, so returning the original page!");
212 return page;
213 }
214
215
216 // put the page into a document - this is necessary for xslt to get
217 // the paths right if you have paths relative to the document root
218 // eg /page.
219 Document doc = this.converter.newDOM();
220 doc.appendChild(doc.importNode(page, true));
221 Element page_response = (Element)GSXML.getChildByTagName(page, GSXML.PAGE_RESPONSE_ELEM);
222 Element format_elem = (Element)GSXML.getChildByTagName(page_response, GSXML.FORMAT_ELEM);
223 if (output.equals("formatelem")) {
224 return format_elem;
225 }
226 if (format_elem != null) {
227 //page_response.removeChild(format_elem);
228 logger.debug("format elem="+this.converter.getPrettyString(format_elem));
229 // need to transform the format info
230 String configStylesheet_file = GSFile.stylesheetFile(GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.SITE_NAME), collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, "config_format.xsl");
231 Document configStylesheet_doc = this.converter.getDOM(new File(configStylesheet_file));
232 if (configStylesheet_doc != null) {
233 Document format_doc = this.converter.newDOM();
234 format_doc.appendChild(format_doc.importNode(format_elem, true));
235 Node result = this.transformer.transform(configStylesheet_doc, format_doc);
236
237 // Since we started creating documents with DocTypes, we can end up with
238 // Document objects here. But we will be working with an Element instead,
239 // so we grab the DocumentElement() of the Document object in such a case.
240 Element new_format;
241 if(result.getNodeType() == Node.DOCUMENT_NODE) {
242 new_format = ((Document)result).getDocumentElement();
243 } else {
244 new_format = (Element)result;
245 }
246 logger.debug("new format elem="+this.converter.getPrettyString(new_format));
247 if (output.equals("newformat")) {
248 return new_format;
249 }
250
251 // add extracted GSF statements in to the main stylesheet
252 GSXSLT.mergeStylesheets(style_doc, new_format);
253 //System.out.println("added extracted GSF statements into the main stylesheet") ;
254
255 // add extracted GSF statements in to the debug test stylesheet
256 //GSXSLT.mergeStylesheets(oldStyle_doc, new_format);
257 } else {
258 logger.error(" couldn't parse the config_format stylesheet, adding the format info as is");
259 GSXSLT.mergeStylesheets(style_doc, format_elem);
260 //GSXSLT.mergeStylesheets(oldStyle_doc, format_elem);
261 }
262 logger.debug("the converted stylesheet is:");
263 logger.debug(this.converter.getPrettyString(style_doc.getDocumentElement()));
264 }
265
266 //for debug purposes only
267 Document oldStyle_doc = style_doc;
268
269
270 Document preprocessingXsl ;
271 try {
272 preprocessingXsl = getPreprocessDoc();
273 String errMsg = ((XMLConverter.ParseErrorHandler)parser.getErrorHandler()).getErrorMessage();
274 if(errMsg != null) {
275 return XMLTransformer.constructErrorXHTMLPage("error loading preprocess xslt file: "
276 + preprocess_xsl_filename + "\n" + errMsg);
277 }
278 } catch (java.io.FileNotFoundException e) {
279 return fileNotFoundErrorPage(e.getMessage());
280 } catch (Exception e) {
281 e.printStackTrace() ;
282 System.out.println("error loading preprocess xslt") ;
283 return XMLTransformer.constructErrorXHTMLPage("error loading preprocess xslt\n" + e.getMessage());
284 }
285
286 Document libraryXsl = null;
287 try {
288 libraryXsl = getLibraryDoc() ;
289 String errMsg = ((XMLConverter.ParseErrorHandler)parser.getErrorHandler()).getErrorMessage();
290 if(errMsg != null) {
291 return XMLTransformer.constructErrorXHTMLPage("Error loading xslt file: "
292 + this.getLibraryXSLFilename() + "\n" + errMsg);
293 }
294 } catch (java.io.FileNotFoundException e) {
295 return fileNotFoundErrorPage(e.getMessage());
296 } catch (Exception e) {
297 e.printStackTrace() ;
298 System.out.println("error loading library xslt") ;
299 return XMLTransformer.constructErrorXHTMLPage("error loading library xslt\n" + e.getMessage()) ;
300 }
301
302 // Combine the skin file and library variables/templates into one document.
303 // Please note: We dont just use xsl:import because the preprocessing stage
304 // needs to know what's available in the library.
305
306 Document skinAndLibraryXsl = null ;
307 Document skinAndLibraryDoc = converter.newDOM();
308 try {
309
310 skinAndLibraryXsl = converter.newDOM();
311 Element root = skinAndLibraryXsl.createElement("skinAndLibraryXsl") ;
312 skinAndLibraryXsl.appendChild(root) ;
313
314 Element s = skinAndLibraryXsl.createElement("skinXsl") ;
315 s.appendChild(skinAndLibraryXsl.importNode(style_doc.getDocumentElement(), true)) ;
316 root.appendChild(s) ;
317
318 Element l = skinAndLibraryXsl.createElement("libraryXsl") ;
319 Element libraryXsl_el = libraryXsl.getDocumentElement();
320 l.appendChild(skinAndLibraryXsl.importNode(libraryXsl_el, true)) ;
321 root.appendChild(l) ;
322 //System.out.println("Skin and Library XSL are now together") ;
323
324
325 //System.out.println("Pre-processing the skin file...") ;
326
327 //pre-process the skin style sheet
328 //In other words, apply the preProcess.xsl to 'skinAndLibraryXsl' in order to
329 //expand all GS-Lib statements into complete XSL statements and also to create
330 //a valid xsl style sheet document.
331
332 Transformer preProcessor = transformerFactory.newTransformer(new DOMSource(preprocessingXsl));
333 preProcessor.setErrorListener(new XMLTransformer.TransformErrorListener());
334 DOMResult result = new DOMResult();
335 result.setNode(skinAndLibraryDoc);
336 preProcessor.transform(new DOMSource(skinAndLibraryXsl), result);
337 //System.out.println("GS-Lib statements are now expanded") ;
338
339 }
340 catch (TransformerException e) {
341 e.printStackTrace() ;
342 System.out.println("TransformerException while preprocessing the skin xslt") ;
343 return XMLTransformer.constructErrorXHTMLPage(e.getMessage()) ;
344 }
345 catch (Exception e) {
346 e.printStackTrace() ;
347 System.out.println("Error while preprocessing the skin xslt") ;
348 return XMLTransformer.constructErrorXHTMLPage(e.getMessage()) ;
349 }
350
351 //The following code is to be uncommented if we need to append the extracted GSF statements
352 //after having extracted the GSLib elements. In case of a problem during postprocessing.
353 /*
354 // put the page into a document - this is necessary for xslt to get
355 // the paths right if you have paths relative to the document root
356 // eg /page.
357 Document doc = this.converter.newDOM();
358 doc.appendChild(doc.importNode(page, true));
359 Element page_response = (Element)GSXML.getChildByTagName(page, GSXML.PAGE_RESPONSE_ELEM);
360 Element format_elem = (Element)GSXML.getChildByTagName(page_response, GSXML.FORMAT_ELEM);
361 if (output.equals("formatelem")) {
362 return format_elem;
363 }
364 if (format_elem != null) {
365 //page_response.removeChild(format_elem);
366 logger.debug("format elem="+this.converter.getPrettyString(format_elem));
367 // need to transform the format info
368 String configStylesheet_file = GSFile.stylesheetFile(GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.SITE_NAME), collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, "config_format.xsl");
369 Document configStylesheet_doc = this.converter.getDOM(new File(configStylesheet_file));
370 if (configStylesheet_doc != null) {
371 Document format_doc = this.converter.newDOM();
372 format_doc.appendChild(format_doc.importNode(format_elem, true));
373 Node result = this.transformer.transform(configStylesheet_doc, format_doc);
374
375 // Since we started creating documents with DocTypes, we can end up with
376 // Document objects here. But we will be working with an Element instead,
377 // so we grab the DocumentElement() of the Document object in such a case.
378 Element new_format;
379 if(result.getNodeType() == Node.DOCUMENT_NODE) {
380 new_format = ((Document)result).getDocumentElement();
381 } else {
382 new_format = (Element)result;
383 }
384 logger.debug("new format elem="+this.converter.getPrettyString(new_format));
385 if (output.equals("newformat")) {
386 return new_format;
387 }
388
389 // add extracted GSF statements in to the main stylesheet
390 GSXSLT.mergeStylesheets(skinAndLibraryDoc, new_format);
391 //System.out.println("added extracted GSF statements into the main stylesheet") ;
392
393 // add extracted GSF statements in to the debug test stylesheet
394 //GSXSLT.mergeStylesheets(oldStyle_doc, new_format);
395 } else {
396 logger.error(" couldn't parse the config_format stylesheet, adding the format info as is");
397 GSXSLT.mergeStylesheets(skinAndLibraryDoc, format_elem);
398 // GSXSLT.mergeStylesheets(oldStyle_doc, format_elem);
399 }
400 logger.debug("the converted stylesheet is:");
401 logger.debug(this.converter.getPrettyString(skinAndLibraryDoc.getDocumentElement()));
402 }
403 */
404
405 // there is a thing called a URIResolver which you can set for a
406 // transformer or transformer factory. may be able to use this
407 // instead of this absoluteIncludepaths hack
408
409 GSXSLT.absoluteIncludePaths(skinAndLibraryDoc, GlobalProperties.getGSDL3Home(),
410 (String)this.config_params.get(GSConstants.SITE_NAME),
411 collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME),
412 base_interfaces);
413
414
415 //Same but for the debug version when we want the do the transformation like we use to do
416 //without any gslib elements.
417 GSXSLT.absoluteIncludePaths(oldStyle_doc, GlobalProperties.getGSDL3Home(),
418 (String)this.config_params.get(GSConstants.SITE_NAME),
419 collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME),
420 base_interfaces);
421
422 //Send different stages of the skin xslt to the browser for debug purposes only
423 //using &o=skindoc or &o=skinandlib etc...
424 if (output.equals("skindoc")) {
425 return converter.getDOM(getStringFromDocument(style_doc));
426 }
427 if (output.equals("skinandlib")) {
428 return converter.getDOM(getStringFromDocument(skinAndLibraryXsl));
429 }
430 if (output.equals("skinandlibdoc")) {
431 return converter.getDOM(getStringFromDocument(skinAndLibraryDoc));
432 }
433 if (output.equals("oldskindoc")) {
434 return converter.getDOM(getStringFromDocument(oldStyle_doc));
435 }
436
437 // DocType defaults in case the skin doesn't have an "xsl:output" element
438 String qualifiedName = "html";
439 String publicID = "-//W3C//DTD HTML 4.01 Transitional//EN";
440 String systemID = "http://www.w3.org/TR/html4/loose.dtd";
441
442 // Try to get the system and public ID from the current skin xsl document
443 // otherwise keep the default values.
444 Element root = skinAndLibraryDoc.getDocumentElement();
445 NodeList nodes = root.getElementsByTagName("xsl:output");
446 // If there is at least one "xsl:output" command in the final xsl then...
447 if(nodes.getLength() != 0) {
448 // There should be only one element called xsl:output,
449 // but if this is not the case get the last one
450 Element xsl_output = (Element)nodes.item(nodes.getLength()-1);
451 if (xsl_output != null) {
452 // Qualified name will always be html even for xhtml pages
453 //String attrValue = xsl_output.getAttribute("method");
454 //qualifiedName = attrValue.equals("") ? qualifiedName : attrValue;
455
456 String attrValue = xsl_output.getAttribute("doctype-system");
457 systemID = attrValue.equals("") ? systemID : attrValue;
458
459 attrValue = xsl_output.getAttribute("doctype-public");
460 publicID = attrValue.equals("") ? publicID : attrValue;
461 }
462 }
463
464 // We need to create an empty document with a predefined DocType,
465 // that will then be used for the transformation by the DOMResult
466 Document docWithDoctype = converter.newDOM(qualifiedName, publicID, systemID);
467
468 //System.out.println(converter.getPrettyString(docWithDoctype));
469 //System.out.println("Doctype vals: " + qualifiedName + " " + publicID + " " + systemID) ;
470
471
472 //System.out.println("Generate final HTML from current skin") ;
473 //Transformation of the XML message from the receptionist to HTML with doctype
474 return this.transformer.transform(skinAndLibraryDoc, doc, config_params, docWithDoctype);
475
476
477 // The line below will do the transformation like we use to do before having Skin++ implemented,
478 // it will not contain any GS-Lib statements expanded, and the result will not contain any doctype.
479
480 //return (Element)this.transformer.transform(style_doc, doc, config_params);
481
482 }
483
484
485 // method to convert Document to a proper XML string for debug purposes only
486 protected String getStringFromDocument(Document doc)
487 {
488 String content = "";
489 try
490 {
491 DOMSource domSource = new DOMSource(doc);
492 StringWriter writer = new StringWriter();
493 StreamResult result = new StreamResult(writer);
494 TransformerFactory tf = TransformerFactory.newInstance();
495 Transformer transformer = tf.newTransformer();
496 transformer.transform(domSource, result);
497 content = writer.toString();
498 System.out.println("Change the & to &Amp; for proper debug dispay") ;
499 content = content.replaceAll("&", "&amp;");
500 writer.flush();
501 }
502 catch(TransformerException ex)
503 {
504 ex.printStackTrace();
505 return null;
506 }
507 return content;
508 }
509
510
511 protected Document getPreprocessDoc() throws Exception {
512
513 File xslt_file = new File(preprocess_xsl_filename) ;
514
515 FileReader reader = new FileReader(xslt_file);
516 InputSource xml_source = new InputSource(reader);
517 this.parser.parse(xml_source);
518 Document doc = this.parser.getDocument();
519
520 return doc ;
521 }
522
523 protected Document getLibraryDoc() throws Exception {
524 Document doc = null;
525 File xslt_file = new File(this.getLibraryXSLFilename()) ;
526
527 FileReader reader = new FileReader(xslt_file);
528 InputSource xml_source = new InputSource(reader);
529 this.parser.parse(xml_source);
530
531 doc = this.parser.getDocument();
532 return doc ;
533 }
534
535 protected String getXSLTFileName(String action, String subaction,
536 String collection) {
537
538 String name = null;
539 if (!subaction.equals("")) {
540 String key = action+":"+subaction;
541 name = (String) this.xslt_map.get(key);
542 }
543 // try the action by itself
544 if (name==null) {
545 name = (String) this.xslt_map.get(action);
546 }
547 // now find the absolute path
548 String stylesheet = GSFile.stylesheetFile(GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.SITE_NAME), collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, name);
549 if (stylesheet==null) {
550 logger.info(" cant find stylesheet for "+name);
551 }
552 return stylesheet;
553 }
554
555 // returns the library.xsl path of the library file that is applicable for the current interface
556 protected String getLibraryXSLFilename() {
557 return GSFile.xmlTransformDir(GSFile.interfaceHome(
558 GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.INTERFACE_NAME)))
559 + File.separatorChar + "library.xsl";
560 }
561
562 // Call this when a FileNotFoundException could be thrown when loading an xsl (xml) file.
563 // Returns an error xhtml page indicating which xsl (or other xml) file is missing.
564 protected Document fileNotFoundErrorPage(String filenameMessage) {
565 String errorMessage = "ERROR missing file: " + filenameMessage;
566 Element errPage = XMLTransformer.constructErrorXHTMLPage(errorMessage);
567 logger.error(errorMessage);
568 return errPage.getOwnerDocument();
569 }
570}
Note: See TracBrowser for help on using the repository browser.