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 }