001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: XSLTProcessorApplet.java 1225408 2011-12-29 01:11:41Z mrglavas $
020 */
021 package org.apache.xalan.client;
022
023 import java.applet.Applet;
024 import java.awt.Graphics;
025 import java.io.IOException;
026 import java.io.PrintWriter;
027 import java.io.StringReader;
028 import java.io.StringWriter;
029 import java.net.MalformedURLException;
030 import java.net.URL;
031 import java.util.Hashtable;
032 import java.util.Iterator;
033 import java.util.Map;
034
035 import javax.xml.transform.Templates;
036 import javax.xml.transform.Transformer;
037 import javax.xml.transform.TransformerConfigurationException;
038 import javax.xml.transform.TransformerException;
039 import javax.xml.transform.TransformerFactory;
040 import javax.xml.transform.stream.StreamResult;
041 import javax.xml.transform.stream.StreamSource;
042
043 import org.apache.xalan.res.XSLMessages;
044 import org.apache.xalan.res.XSLTErrorResources;
045
046 /**
047 * Provides applet host for the XSLT processor. To perform transformations on an HTML client:
048 * <ol>
049 * <li>Use an <applet> tag to embed this applet in the HTML client.</li>
050 * <li>Use the DocumentURL and StyleURL PARAM tags or the {@link #setDocumentURL} and
051 * {@link #setStyleURL} methods to specify the XML source document and XSL stylesheet.</li>
052 * <li>Call the {@link #getHtmlText} method (or one of the transformToHtml() methods)
053 * to perform the transformation and return the result as a String.</li>
054 * </ol>
055 *
056 * This class extends Applet which ultimately causes this class to implement Serializable.
057 * This is a serious restriction on this class. All fields that are not transient and not
058 * static are written-out/read-in during serialization. So even private fields essentially
059 * become part of the API. Developers need to take care when modifying fields.
060 * @xsl.usage general
061 */
062 public class XSLTProcessorApplet extends Applet
063 {
064
065 /**
066 * The stylesheet processor.
067 * This field is now transient because a
068 * javax.xml.transform.TransformerFactory from JAXP
069 * makes no claims to be serializable.
070 */
071 transient TransformerFactory m_tfactory = null;
072
073 /**
074 * @serial
075 */
076 private String m_styleURL;
077
078 /**
079 * @serial
080 */
081 private String m_documentURL;
082
083 // Parameter names. To change a name of a parameter, you need only make
084 // a single change. Simply modify the value of the parameter string below.
085 //--------------------------------------------------------------------------
086
087 /**
088 * @serial
089 */
090 private final String PARAM_styleURL = "styleURL";
091
092 /**
093 * @serial
094 */
095 private final String PARAM_documentURL = "documentURL";
096
097
098 // We'll keep the DOM trees around, so tell which trees
099 // are cached.
100
101 /**
102 * @serial
103 */
104 private String m_styleURLOfCached = null;
105
106 /**
107 * @serial
108 */
109 private String m_documentURLOfCached = null;
110
111 /**
112 * Save this for use on the worker thread; may not be necessary.
113 * @serial
114 */
115 private URL m_codeBase = null;
116
117 /**
118 * @serial
119 */
120 private String m_treeURL = null;
121
122 /**
123 * DocumentBase URL
124 * @serial
125 */
126 private URL m_documentBase = null;
127
128 /**
129 * Thread stuff for the trusted worker thread.
130 */
131 transient private Thread m_callThread = null;
132
133 /**
134 */
135 transient private TrustedAgent m_trustedAgent = null;
136
137 /**
138 * Thread for running TrustedAgent.
139 */
140 transient private Thread m_trustedWorker = null;
141
142 /**
143 * Where the worker thread puts the HTML text.
144 */
145 transient private String m_htmlText = null;
146
147 /**
148 * Where the worker thread puts the document/stylesheet text.
149 */
150 transient private String m_sourceText = null;
151
152 /**
153 * Stylesheet attribute name and value that the caller can set.
154 */
155 transient private String m_nameOfIDAttrOfElemToModify = null;
156
157 /**
158 */
159 transient private String m_elemIdToModify = null;
160
161 /**
162 */
163 transient private String m_attrNameToSet = null;
164
165 /**
166 */
167 transient private String m_attrValueToSet = null;
168
169 /**
170 * The XSLTProcessorApplet constructor takes no arguments.
171 */
172 public XSLTProcessorApplet(){}
173
174 /**
175 * Get basic information about the applet
176 * @return A String with the applet name and author.
177 */
178 public String getAppletInfo()
179 {
180 return "Name: XSLTProcessorApplet\r\n" + "Author: Scott Boag";
181 }
182
183 /**
184 * Get descriptions of the applet parameters.
185 * @return A two-dimensional array of Strings with Name, Type, and Description
186 * for each parameter.
187 */
188 public String[][] getParameterInfo()
189 {
190
191 String[][] info =
192 {
193 { PARAM_styleURL, "String", "URL to an XSL stylesheet" },
194 { PARAM_documentURL, "String", "URL to an XML document" },
195 };
196
197 return info;
198 }
199
200 /**
201 * Standard applet initialization.
202 */
203 public void init()
204 {
205
206 // PARAMETER SUPPORT
207 // The following code retrieves the value of each parameter
208 // specified with the <PARAM> tag and stores it in a member
209 // variable.
210 //----------------------------------------------------------------------
211 String param;
212
213 // styleURL: Parameter description
214 //----------------------------------------------------------------------
215 param = getParameter(PARAM_styleURL);
216
217 // stylesheet parameters
218 m_parameters = new Hashtable();
219
220 if (param != null)
221 setStyleURL(param);
222
223 // documentURL: Parameter description
224 //----------------------------------------------------------------------
225 param = getParameter(PARAM_documentURL);
226
227 if (param != null)
228 setDocumentURL(param);
229
230 m_codeBase = this.getCodeBase();
231 m_documentBase = this.getDocumentBase();
232
233 // If you use a ResourceWizard-generated "control creator" class to
234 // arrange controls in your applet, you may want to call its
235 // CreateControls() method from within this method. Remove the following
236 // call to resize() before adding the call to CreateControls();
237 // CreateControls() does its own resizing.
238 //----------------------------------------------------------------------
239 resize(320, 240);
240 }
241
242 /**
243 * Automatically called when the HTML client containing the applet loads.
244 * This method starts execution of the applet thread.
245 */
246 public void start()
247 {
248
249 m_trustedAgent = new TrustedAgent();
250 Thread currentThread = Thread.currentThread();
251 m_trustedWorker = new Thread(currentThread.getThreadGroup(),
252 m_trustedAgent);
253 m_trustedWorker.start();
254 try
255 {
256 m_tfactory = TransformerFactory.newInstance();
257 this.showStatus("Causing Transformer and Parser to Load and JIT...");
258
259 // Prime the pump so that subsequent transforms are faster.
260 StringReader xmlbuf = new StringReader("<?xml version='1.0'?><foo/>");
261 StringReader xslbuf = new StringReader(
262 "<?xml version='1.0'?><xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'><xsl:template match='foo'><out/></xsl:template></xsl:stylesheet>");
263 PrintWriter pw = new PrintWriter(new StringWriter());
264
265 synchronized (m_tfactory)
266 {
267 Templates templates = m_tfactory.newTemplates(new StreamSource(xslbuf));
268 Transformer transformer = templates.newTransformer();
269 transformer.transform(new StreamSource(xmlbuf), new StreamResult(pw));
270 }
271 System.out.println("Primed the pump!");
272 this.showStatus("Ready to go!");
273 }
274 catch (Exception e)
275 {
276 this.showStatus("Could not prime the pump!");
277 System.out.println("Could not prime the pump!");
278 e.printStackTrace();
279 }
280 }
281
282 /**
283 * Do not call; this applet contains no UI or visual components.
284 *
285 */
286 public void paint(Graphics g){}
287
288 /**
289 * Automatically called when the HTML page containing the applet is no longer
290 * on the screen. Stops execution of the applet thread.
291 */
292 public void stop()
293 {
294 if (null != m_trustedWorker)
295 {
296 m_trustedWorker.stop();
297
298 // m_trustedWorker.destroy();
299 m_trustedWorker = null;
300 }
301
302 m_styleURLOfCached = null;
303 m_documentURLOfCached = null;
304 }
305
306 /**
307 * Cleanup; called when applet is terminated and unloaded.
308 */
309 public void destroy()
310 {
311 if (null != m_trustedWorker)
312 {
313 m_trustedWorker.stop();
314
315 // m_trustedWorker.destroy();
316 m_trustedWorker = null;
317 }
318 m_styleURLOfCached = null;
319 m_documentURLOfCached = null;
320 }
321
322 /**
323 * Set the URL to the XSL stylesheet that will be used
324 * to transform the input XML. No processing is done yet.
325 * @param urlString valid URL string for XSL stylesheet.
326 */
327 public void setStyleURL(String urlString)
328 {
329 m_styleURL = urlString;
330 }
331
332 /**
333 * Set the URL to the XML document that will be transformed
334 * with the XSL stylesheet. No processing is done yet.
335 * @param urlString valid URL string for XML document.
336 */
337 public void setDocumentURL(String urlString)
338 {
339 m_documentURL = urlString;
340 }
341
342 /**
343 * The processor keeps a cache of the source and
344 * style trees, so call this method if they have changed
345 * or you want to do garbage collection.
346 */
347 public void freeCache()
348 {
349 m_styleURLOfCached = null;
350 m_documentURLOfCached = null;
351 }
352
353 /**
354 * Set an attribute in the stylesheet, which gives the ability
355 * to have some dynamic selection control.
356 * @param nameOfIDAttrOfElemToModify The name of an attribute to search for a unique id.
357 * @param elemId The unique ID to look for.
358 * @param attrName Once the element is found, the name of the attribute to set.
359 * @param value The value to set the attribute to.
360 */
361 public void setStyleSheetAttribute(String nameOfIDAttrOfElemToModify,
362 String elemId, String attrName,
363 String value)
364 {
365 m_nameOfIDAttrOfElemToModify = nameOfIDAttrOfElemToModify;
366 m_elemIdToModify = elemId;
367 m_attrNameToSet = attrName;
368 m_attrValueToSet = value;
369 }
370
371
372 /**
373 * Stylesheet parameter key/value pair stored in a hashtable
374 */
375 transient Hashtable m_parameters;
376
377 /**
378 * Submit a stylesheet parameter.
379 *
380 * @param key stylesheet parameter key
381 * @param expr the parameter expression to be submitted.
382 * @see javax.xml.transform.Transformer#setParameter(String,Object)
383 */
384 public void setStylesheetParam(String key, String expr)
385 {
386 m_parameters.put(key, expr);
387 }
388
389 /**
390 * Given a String containing markup, escape the markup so it
391 * can be displayed in the browser.
392 *
393 * @param s String to escape
394 *
395 * The escaped string.
396 */
397 public String escapeString(String s)
398 {
399 StringBuffer sb = new StringBuffer();
400 int length = s.length();
401
402 for (int i = 0; i < length; i++)
403 {
404 char ch = s.charAt(i);
405
406 if ('<' == ch)
407 {
408 sb.append("<");
409 }
410 else if ('>' == ch)
411 {
412 sb.append(">");
413 }
414 else if ('&' == ch)
415 {
416 sb.append("&");
417 }
418 else if (0xd800 <= ch && ch < 0xdc00)
419 {
420 // UTF-16 surrogate
421 int next;
422
423 if (i + 1 >= length)
424 {
425 throw new RuntimeException(
426 XSLMessages.createMessage(
427 XSLTErrorResources.ER_INVALID_UTF16_SURROGATE,
428 new Object[]{ Integer.toHexString(ch) })); //"Invalid UTF-16 surrogate detected: "
429
430 //+Integer.toHexString(ch)+ " ?");
431 }
432 else
433 {
434 next = s.charAt(++i);
435
436 if (!(0xdc00 <= next && next < 0xe000))
437 throw new RuntimeException(
438 XSLMessages.createMessage(
439 XSLTErrorResources.ER_INVALID_UTF16_SURROGATE,
440 new Object[]{
441 Integer.toHexString(ch) + " "
442 + Integer.toHexString(next) })); //"Invalid UTF-16 surrogate detected: "
443
444 //+Integer.toHexString(ch)+" "+Integer.toHexString(next));
445 next = ((ch - 0xd800) << 10) + next - 0xdc00 + 0x00010000;
446 }
447 sb.append("&#x");
448 sb.append(Integer.toHexString(next));
449 sb.append(";");
450 }
451 else
452 {
453 sb.append(ch);
454 }
455 }
456 return sb.toString();
457 }
458
459 /**
460 * Assuming the stylesheet URL and the input XML URL have been set,
461 * perform the transformation and return the result as a String.
462 *
463 * @return A string that contains the contents pointed to by the URL.
464 *
465 */
466 public String getHtmlText()
467 {
468 m_trustedAgent.m_getData = true;
469 m_callThread = Thread.currentThread();
470 try
471 {
472 synchronized (m_callThread)
473 {
474 m_callThread.wait();
475 }
476 }
477 catch (InterruptedException ie)
478 {
479 System.out.println(ie.getMessage());
480 }
481 return m_htmlText;
482 }
483
484 /**
485 * Get an XML document (or stylesheet)
486 *
487 * @param treeURL valid URL string for the document.
488 *
489 * @return document
490 *
491 * @throws IOException
492 */
493 public String getTreeAsText(String treeURL) throws IOException
494 {
495 m_treeURL = treeURL;
496 m_trustedAgent.m_getData = true;
497 m_trustedAgent.m_getSource = true;
498 m_callThread = Thread.currentThread();
499 try
500 {
501 synchronized (m_callThread)
502 {
503 m_callThread.wait();
504 }
505 }
506 catch (InterruptedException ie)
507 {
508 System.out.println(ie.getMessage());
509 }
510 return m_sourceText;
511 }
512
513 /**
514 * Use a Transformer to copy the source document
515 * to a StreamResult.
516 *
517 * @return the document as a string
518 */
519 private String getSource() throws TransformerException
520 {
521 StringWriter osw = new StringWriter();
522 PrintWriter pw = new PrintWriter(osw, false);
523 String text = "";
524 try
525 {
526 URL docURL = new URL(m_documentBase, m_treeURL);
527 synchronized (m_tfactory)
528 {
529 Transformer transformer = m_tfactory.newTransformer();
530 StreamSource source = new StreamSource(docURL.toString());
531 StreamResult result = new StreamResult(pw);
532 transformer.transform(source, result);
533 text = osw.toString();
534 }
535 }
536 catch (MalformedURLException e)
537 {
538 e.printStackTrace();
539 throw new RuntimeException(e.getMessage());
540 }
541 catch (Exception any_error)
542 {
543 any_error.printStackTrace();
544 }
545 return text;
546 }
547
548 /**
549 * Get the XML source Tree as a text string suitable
550 * for display in a browser. Note that this is for display of the
551 * XML itself, not for rendering of HTML by the browser.
552 *
553 * @return XML source document as a string.
554 * @throws Exception thrown if tree can not be converted.
555 */
556 public String getSourceTreeAsText() throws Exception
557 {
558 return getTreeAsText(m_documentURL);
559 }
560
561 /**
562 * Get the XSL style Tree as a text string suitable
563 * for display in a browser. Note that this is for display of the
564 * XML itself, not for rendering of HTML by the browser.
565 *
566 * @return The XSL stylesheet as a string.
567 * @throws Exception thrown if tree can not be converted.
568 */
569 public String getStyleTreeAsText() throws Exception
570 {
571 return getTreeAsText(m_styleURL);
572 }
573
574 /**
575 * Get the HTML result Tree as a text string suitable
576 * for display in a browser. Note that this is for display of the
577 * XML itself, not for rendering of HTML by the browser.
578 *
579 * @return Transformation result as unmarked text.
580 * @throws Exception thrown if tree can not be converted.
581 */
582 public String getResultTreeAsText() throws Exception
583 {
584 return escapeString(getHtmlText());
585 }
586
587 /**
588 * Process a document and a stylesheet and return
589 * the transformation result. If one of these is null, the
590 * existing value (of a previous transformation) is not affected.
591 *
592 * @param doc URL string to XML document
593 * @param style URL string to XSL stylesheet
594 *
595 * @return HTML transformation result
596 */
597 public String transformToHtml(String doc, String style)
598 {
599
600 if (null != doc)
601 {
602 m_documentURL = doc;
603 }
604
605 if (null != style)
606 {
607 m_styleURL = style;
608 }
609
610 return getHtmlText();
611 }
612
613 /**
614 * Process a document and a stylesheet and return
615 * the transformation result. Use the xsl:stylesheet PI to find the
616 * document, if one exists.
617 *
618 * @param doc URL string to XML document containing an xsl:stylesheet PI.
619 *
620 * @return HTML transformation result
621 */
622 public String transformToHtml(String doc)
623 {
624
625 if (null != doc)
626 {
627 m_documentURL = doc;
628 }
629
630 m_styleURL = null;
631
632 return getHtmlText();
633 }
634
635
636 /**
637 * Process the transformation.
638 *
639 * @return The transformation result as a string.
640 *
641 * @throws TransformerException
642 */
643 private String processTransformation() throws TransformerException
644 {
645 String htmlData = null;
646 this.showStatus("Waiting for Transformer and Parser to finish loading and JITing...");
647
648 synchronized (m_tfactory)
649 {
650 URL documentURL = null;
651 URL styleURL = null;
652 StringWriter osw = new StringWriter();
653 PrintWriter pw = new PrintWriter(osw, false);
654 StreamResult result = new StreamResult(pw);
655
656 this.showStatus("Begin Transformation...");
657 try
658 {
659 documentURL = new URL(m_codeBase, m_documentURL);
660 StreamSource xmlSource = new StreamSource(documentURL.toString());
661
662 styleURL = new URL(m_codeBase, m_styleURL);
663 StreamSource xslSource = new StreamSource(styleURL.toString());
664
665 Transformer transformer = m_tfactory.newTransformer(xslSource);
666
667 Iterator m_entries = m_parameters.entrySet().iterator();
668 while (m_entries.hasNext()) {
669 Map.Entry entry = (Map.Entry) m_entries.next();
670 Object key = entry.getKey();
671 Object expression = entry.getValue();
672 transformer.setParameter((String) key, expression);
673 }
674 transformer.transform(xmlSource, result);
675 }
676 catch (TransformerConfigurationException tfe)
677 {
678 tfe.printStackTrace();
679 throw new RuntimeException(tfe.getMessage());
680 }
681 catch (MalformedURLException e)
682 {
683 e.printStackTrace();
684 throw new RuntimeException(e.getMessage());
685 }
686
687 this.showStatus("Transformation Done!");
688 htmlData = osw.toString();
689 }
690 return htmlData;
691 }
692
693 /**
694 * This class maintains a worker thread that that is
695 * trusted and can do things like access data. You need
696 * this because the thread that is called by the browser
697 * is not trusted and can't access data from the URLs.
698 */
699 class TrustedAgent implements Runnable
700 {
701
702 /**
703 * Specifies whether the worker thread should perform a transformation.
704 */
705 public boolean m_getData = false;
706
707 /**
708 * Specifies whether the worker thread should get an XML document or XSL stylesheet.
709 */
710 public boolean m_getSource = false;
711
712 /**
713 * The real work is done from here.
714 *
715 */
716 public void run()
717 {
718 while (true)
719 {
720 Thread.yield();
721
722 if (m_getData) // Perform a transformation or get a document.
723 {
724 try
725 {
726 m_getData = false;
727 m_htmlText = null;
728 m_sourceText = null;
729 if (m_getSource) // Get a document.
730 {
731 m_getSource = false;
732 m_sourceText = getSource();
733 }
734 else // Perform a transformation.
735 m_htmlText = processTransformation();
736 }
737 catch (Exception e)
738 {
739 e.printStackTrace();
740 }
741 finally
742 {
743 synchronized (m_callThread)
744 {
745 m_callThread.notify();
746 }
747 }
748 }
749 else
750 {
751 try
752 {
753 Thread.sleep(50);
754 }
755 catch (InterruptedException ie)
756 {
757 ie.printStackTrace();
758 }
759 }
760 }
761 }
762 }
763
764 // For compatiblity with old serialized objects
765 // We will change non-serialized fields and change methods
766 // and not have this break us.
767 private static final long serialVersionUID=4618876841979251422L;
768
769 // For compatibility when de-serializing old objects
770 private void readObject(java.io.ObjectInputStream inStream) throws IOException, ClassNotFoundException
771 {
772 inStream.defaultReadObject();
773
774 // Needed assignment of non-serialized fields
775
776 // A TransformerFactory is not guaranteed to be serializable,
777 // so we create one here
778 m_tfactory = TransformerFactory.newInstance();
779 }
780 }