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: AbstractTranslet.java 468652 2006-10-28 07:05:17Z minchau $ 020 */ 021 022 package org.apache.xalan.xsltc.runtime; 023 024 import java.io.File; 025 import java.io.FileWriter; 026 import java.text.DecimalFormat; 027 import java.text.DecimalFormatSymbols; 028 import java.util.ArrayList; 029 import java.util.Enumeration; 030 import java.util.Vector; 031 import javax.xml.transform.Templates; 032 import javax.xml.parsers.DocumentBuilderFactory; 033 import org.w3c.dom.Document; 034 import org.w3c.dom.DOMImplementation; 035 import javax.xml.parsers.ParserConfigurationException; 036 037 import org.apache.xml.dtm.DTM; 038 039 import org.apache.xalan.xsltc.DOM; 040 import org.apache.xalan.xsltc.DOMCache; 041 import org.apache.xalan.xsltc.DOMEnhancedForDTM; 042 import org.apache.xalan.xsltc.Translet; 043 import org.apache.xalan.xsltc.TransletException; 044 import org.apache.xalan.xsltc.dom.DOMAdapter; 045 import org.apache.xalan.xsltc.dom.KeyIndex; 046 import org.apache.xalan.xsltc.runtime.output.TransletOutputHandlerFactory; 047 import org.apache.xml.dtm.DTMAxisIterator; 048 import org.apache.xml.serializer.SerializationHandler; 049 050 /** 051 * @author Jacek Ambroziak 052 * @author Santiago Pericas-Geertsen 053 * @author Morten Jorgensen 054 * @author G. Todd Miller 055 * @author John Howard, JohnH@schemasoft.com 056 */ 057 public abstract class AbstractTranslet implements Translet { 058 059 // These attributes are extracted from the xsl:output element. They also 060 // appear as fields (with the same type, only public) in Output.java 061 public String _version = "1.0"; 062 public String _method = null; 063 public String _encoding = "UTF-8"; 064 public boolean _omitHeader = false; 065 public String _standalone = null; 066 public String _doctypePublic = null; 067 public String _doctypeSystem = null; 068 public boolean _indent = false; 069 public String _mediaType = null; 070 public Vector _cdata = null; 071 public int _indentamount = -1; 072 073 public static final int FIRST_TRANSLET_VERSION = 100; 074 public static final int VER_SPLIT_NAMES_ARRAY = 101; 075 public static final int CURRENT_TRANSLET_VERSION = VER_SPLIT_NAMES_ARRAY; 076 077 // Initialize Translet version field to base value. A class that extends 078 // AbstractTranslet may override this value to a more recent translet 079 // version; if it doesn't override the value (because it was compiled 080 // before the notion of a translet version was introduced, it will get 081 // this default value). 082 protected int transletVersion = FIRST_TRANSLET_VERSION; 083 084 // DOM/translet handshaking - the arrays are set by the compiled translet 085 protected String[] namesArray; 086 protected String[] urisArray; 087 protected int[] typesArray; 088 protected String[] namespaceArray; 089 090 // The Templates object that is used to create this Translet instance 091 protected Templates _templates = null; 092 093 // Boolean flag to indicate whether this translet has id functions. 094 protected boolean _hasIdCall = false; 095 096 // TODO - these should only be instanciated when needed 097 protected StringValueHandler stringValueHandler = new StringValueHandler(); 098 099 // Use one empty string instead of constantly instanciating String(""); 100 private final static String EMPTYSTRING = ""; 101 102 // This is the name of the index used for ID attributes 103 private final static String ID_INDEX_NAME = "##id"; 104 105 106 /************************************************************************ 107 * Debugging 108 ************************************************************************/ 109 public void printInternalState() { 110 System.out.println("-------------------------------------"); 111 System.out.println("AbstractTranslet this = " + this); 112 System.out.println("pbase = " + pbase); 113 System.out.println("vframe = " + pframe); 114 System.out.println("paramsStack.size() = " + paramsStack.size()); 115 System.out.println("namesArray.size = " + namesArray.length); 116 System.out.println("namespaceArray.size = " + namespaceArray.length); 117 System.out.println(""); 118 System.out.println("Total memory = " + Runtime.getRuntime().totalMemory()); 119 } 120 121 /** 122 * Wrap the initial input DOM in a dom adapter. This adapter is wrapped in 123 * a DOM multiplexer if the document() function is used (handled by compiled 124 * code in the translet - see compiler/Stylesheet.compileTransform()). 125 */ 126 public final DOMAdapter makeDOMAdapter(DOM dom) 127 throws TransletException { 128 setRootForKeys(dom.getDocument()); 129 return new DOMAdapter(dom, namesArray, urisArray, typesArray, namespaceArray); 130 } 131 132 /************************************************************************ 133 * Parameter handling 134 ************************************************************************/ 135 136 // Parameter's stack: <tt>pbase</tt> and <tt>pframe</tt> are used 137 // to denote the current parameter frame. 138 protected int pbase = 0, pframe = 0; 139 protected ArrayList paramsStack = new ArrayList(); 140 141 /** 142 * Push a new parameter frame. 143 */ 144 public final void pushParamFrame() { 145 paramsStack.add(pframe, new Integer(pbase)); 146 pbase = ++pframe; 147 } 148 149 /** 150 * Pop the topmost parameter frame. 151 */ 152 public final void popParamFrame() { 153 if (pbase > 0) { 154 final int oldpbase = ((Integer)paramsStack.get(--pbase)).intValue(); 155 for (int i = pframe - 1; i >= pbase; i--) { 156 paramsStack.remove(i); 157 } 158 pframe = pbase; pbase = oldpbase; 159 } 160 } 161 162 /** 163 * Add a new global parameter if not already in the current frame. 164 * To setParameters of the form {http://foo.bar}xyz 165 * This needs to get mapped to an instance variable in the class 166 * The mapping created so that 167 * the global variables in the generated class become 168 * http$colon$$flash$$flash$foo$dot$bar$colon$xyz 169 */ 170 public final Object addParameter(String name, Object value) { 171 name = BasisLibrary.mapQNameToJavaName (name); 172 return addParameter(name, value, false); 173 } 174 175 /** 176 * Add a new global or local parameter if not already in the current frame. 177 * The 'isDefault' parameter is set to true if the value passed is the 178 * default value from the <xsl:parameter> element's select attribute or 179 * element body. 180 */ 181 public final Object addParameter(String name, Object value, 182 boolean isDefault) 183 { 184 // Local parameters need to be re-evaluated for each iteration 185 for (int i = pframe - 1; i >= pbase; i--) { 186 final Parameter param = (Parameter) paramsStack.get(i); 187 188 if (param._name.equals(name)) { 189 // Only overwrite if current value is the default value and 190 // the new value is _NOT_ the default value. 191 if (param._isDefault || !isDefault) { 192 param._value = value; 193 param._isDefault = isDefault; 194 return value; 195 } 196 return param._value; 197 } 198 } 199 200 // Add new parameter to parameter stack 201 paramsStack.add(pframe++, new Parameter(name, value, isDefault)); 202 return value; 203 } 204 205 /** 206 * Clears the parameter stack. 207 */ 208 public void clearParameters() { 209 pbase = pframe = 0; 210 paramsStack.clear(); 211 } 212 213 /** 214 * Get the value of a parameter from the current frame or 215 * <tt>null</tt> if undefined. 216 */ 217 public final Object getParameter(String name) { 218 219 name = BasisLibrary.mapQNameToJavaName (name); 220 221 for (int i = pframe - 1; i >= pbase; i--) { 222 final Parameter param = (Parameter)paramsStack.get(i); 223 if (param._name.equals(name)) return param._value; 224 } 225 return null; 226 } 227 228 /************************************************************************ 229 * Message handling - implementation of <xsl:message> 230 ************************************************************************/ 231 232 // Holds the translet's message handler - used for <xsl:message>. 233 // The deault message handler dumps a string stdout, but anything can be 234 // used, such as a dialog box for applets, etc. 235 private MessageHandler _msgHandler = null; 236 237 /** 238 * Set the translet's message handler - must implement MessageHandler 239 */ 240 public final void setMessageHandler(MessageHandler handler) { 241 _msgHandler = handler; 242 } 243 244 /** 245 * Pass a message to the message handler - used by Message class. 246 */ 247 public final void displayMessage(String msg) { 248 if (_msgHandler == null) { 249 System.err.println(msg); 250 } 251 else { 252 _msgHandler.displayMessage(msg); 253 } 254 } 255 256 /************************************************************************ 257 * Decimal number format symbol handling 258 ************************************************************************/ 259 260 // Contains decimal number formatting symbols used by FormatNumberCall 261 public Hashtable _formatSymbols = null; 262 263 /** 264 * Adds a DecimalFormat object to the _formatSymbols hashtable. 265 * The entry is created with the input DecimalFormatSymbols. 266 */ 267 public void addDecimalFormat(String name, DecimalFormatSymbols symbols) { 268 // Instanciate hashtable for formatting symbols if needed 269 if (_formatSymbols == null) _formatSymbols = new Hashtable(); 270 271 // The name cannot be null - use empty string instead 272 if (name == null) name = EMPTYSTRING; 273 274 // Construct a DecimalFormat object containing the symbols we got 275 final DecimalFormat df = new DecimalFormat(); 276 if (symbols != null) { 277 df.setDecimalFormatSymbols(symbols); 278 } 279 _formatSymbols.put(name, df); 280 } 281 282 /** 283 * Retrieves a named DecimalFormat object from _formatSymbols hashtable. 284 */ 285 public final DecimalFormat getDecimalFormat(String name) { 286 287 if (_formatSymbols != null) { 288 // The name cannot be null - use empty string instead 289 if (name == null) name = EMPTYSTRING; 290 291 DecimalFormat df = (DecimalFormat)_formatSymbols.get(name); 292 if (df == null) df = (DecimalFormat)_formatSymbols.get(EMPTYSTRING); 293 return df; 294 } 295 return(null); 296 } 297 298 /** 299 * Give the translet an opportunity to perform a prepass on the document 300 * to extract any information that it can store in an optimized form. 301 * 302 * Currently, it only extracts information about attributes of type ID. 303 */ 304 public final void prepassDocument(DOM document) { 305 setIndexSize(document.getSize()); 306 buildIDIndex(document); 307 } 308 309 /** 310 * Leverages the Key Class to implement the XSLT id() function. 311 * buildIdIndex creates the index (##id) that Key Class uses. 312 * The index contains the element node index (int) and Id value (String). 313 */ 314 private final void buildIDIndex(DOM document) { 315 setRootForKeys(document.getDocument()); 316 317 if (document instanceof DOMEnhancedForDTM) { 318 DOMEnhancedForDTM enhancedDOM = (DOMEnhancedForDTM)document; 319 320 // If the input source is DOMSource, the KeyIndex table is not 321 // built at this time. It will be built later by the lookupId() 322 // and containsId() methods of the KeyIndex class. 323 if (enhancedDOM.hasDOMSource()) { 324 buildKeyIndex(ID_INDEX_NAME, document); 325 return; 326 } 327 else { 328 final Hashtable elementsByID = enhancedDOM.getElementsWithIDs(); 329 330 if (elementsByID == null) { 331 return; 332 } 333 334 // Given a Hashtable of DTM nodes indexed by ID attribute values, 335 // loop through the table copying information to a KeyIndex 336 // for the mapping from ID attribute value to DTM node 337 final Enumeration idValues = elementsByID.keys(); 338 boolean hasIDValues = false; 339 340 while (idValues.hasMoreElements()) { 341 final Object idValue = idValues.nextElement(); 342 final int element = 343 document.getNodeHandle( 344 ((Integer)elementsByID.get(idValue)) 345 .intValue()); 346 347 buildKeyIndex(ID_INDEX_NAME, element, idValue); 348 hasIDValues = true; 349 } 350 351 if (hasIDValues) { 352 setKeyIndexDom(ID_INDEX_NAME, document); 353 } 354 } 355 } 356 } 357 358 /** 359 * After constructing the translet object, this method must be called to 360 * perform any version-specific post-initialization that's required. 361 */ 362 public final void postInitialization() { 363 // If the version of the translet had just one namesArray, split 364 // it into multiple fields. 365 if (transletVersion < VER_SPLIT_NAMES_ARRAY) { 366 int arraySize = namesArray.length; 367 String[] newURIsArray = new String[arraySize]; 368 String[] newNamesArray = new String[arraySize]; 369 int[] newTypesArray = new int[arraySize]; 370 371 for (int i = 0; i < arraySize; i++) { 372 String name = namesArray[i]; 373 int colonIndex = name.lastIndexOf(':'); 374 int lNameStartIdx = colonIndex+1; 375 376 if (colonIndex > -1) { 377 newURIsArray[i] = name.substring(0, colonIndex); 378 } 379 380 // Distinguish attribute and element names. Attribute has 381 // @ before local part of name. 382 if (name.charAt(lNameStartIdx) == '@') { 383 lNameStartIdx++; 384 newTypesArray[i] = DTM.ATTRIBUTE_NODE; 385 } else if (name.charAt(lNameStartIdx) == '?') { 386 lNameStartIdx++; 387 newTypesArray[i] = DTM.NAMESPACE_NODE; 388 } else { 389 newTypesArray[i] = DTM.ELEMENT_NODE; 390 } 391 newNamesArray[i] = 392 (lNameStartIdx == 0) ? name 393 : name.substring(lNameStartIdx); 394 } 395 396 namesArray = newNamesArray; 397 urisArray = newURIsArray; 398 typesArray = newTypesArray; 399 } 400 401 // Was translet compiled using a more recent version of the XSLTC 402 // compiler than is known by the AbstractTranslet class? If, so 403 // and we've made it this far (which is doubtful), we should give up. 404 if (transletVersion > CURRENT_TRANSLET_VERSION) { 405 BasisLibrary.runTimeError(BasisLibrary.UNKNOWN_TRANSLET_VERSION_ERR, 406 this.getClass().getName()); 407 } 408 } 409 410 /************************************************************************ 411 * Index(es) for <xsl:key> / key() / id() 412 ************************************************************************/ 413 414 // Container for all indexes for xsl:key elements 415 private Hashtable _keyIndexes = null; 416 private KeyIndex _emptyKeyIndex = null; 417 private int _indexSize = 0; 418 private int _currentRootForKeys = 0; 419 420 /** 421 * This method is used to pass the largest DOM size to the translet. 422 * Needed to make sure that the translet can index the whole DOM. 423 */ 424 public void setIndexSize(int size) { 425 if (size > _indexSize) _indexSize = size; 426 } 427 428 /** 429 * Creates a KeyIndex object of the desired size - don't want to resize!!! 430 */ 431 public KeyIndex createKeyIndex() { 432 return(new KeyIndex(_indexSize)); 433 } 434 435 /** 436 * Adds a value to a key/id index 437 * @param name is the name of the index (the key or ##id) 438 * @param node is the node handle of the node to insert 439 * @param value is the value that will look up the node in the given index 440 */ 441 public void buildKeyIndex(String name, int node, Object value) { 442 if (_keyIndexes == null) _keyIndexes = new Hashtable(); 443 444 KeyIndex index = (KeyIndex)_keyIndexes.get(name); 445 if (index == null) { 446 _keyIndexes.put(name, index = new KeyIndex(_indexSize)); 447 } 448 index.add(value, node, _currentRootForKeys); 449 } 450 451 /** 452 * Create an empty KeyIndex in the DOM case 453 * @param name is the name of the index (the key or ##id) 454 * @param dom is the DOM 455 */ 456 public void buildKeyIndex(String name, DOM dom) { 457 if (_keyIndexes == null) _keyIndexes = new Hashtable(); 458 459 KeyIndex index = (KeyIndex)_keyIndexes.get(name); 460 if (index == null) { 461 _keyIndexes.put(name, index = new KeyIndex(_indexSize)); 462 } 463 index.setDom(dom); 464 } 465 466 /** 467 * Returns the index for a given key (or id). 468 * The index implements our internal iterator interface 469 */ 470 public KeyIndex getKeyIndex(String name) { 471 // Return an empty key index iterator if none are defined 472 if (_keyIndexes == null) { 473 return (_emptyKeyIndex != null) 474 ? _emptyKeyIndex 475 : (_emptyKeyIndex = new KeyIndex(1)); 476 } 477 478 // Look up the requested key index 479 final KeyIndex index = (KeyIndex)_keyIndexes.get(name); 480 481 // Return an empty key index iterator if the requested index not found 482 if (index == null) { 483 return (_emptyKeyIndex != null) 484 ? _emptyKeyIndex 485 : (_emptyKeyIndex = new KeyIndex(1)); 486 } 487 488 return(index); 489 } 490 491 private void setRootForKeys(int root) { 492 _currentRootForKeys = root; 493 } 494 495 /** 496 * This method builds key indexes - it is overridden in the compiled 497 * translet in cases where the <xsl:key> element is used 498 */ 499 public void buildKeys(DOM document, DTMAxisIterator iterator, 500 SerializationHandler handler, 501 int root) throws TransletException { 502 503 } 504 505 /** 506 * This method builds key indexes - it is overridden in the compiled 507 * translet in cases where the <xsl:key> element is used 508 */ 509 public void setKeyIndexDom(String name, DOM document) { 510 getKeyIndex(name).setDom(document); 511 512 } 513 514 /************************************************************************ 515 * DOM cache handling 516 ************************************************************************/ 517 518 // Hold the DOM cache (if any) used with this translet 519 private DOMCache _domCache = null; 520 521 /** 522 * Sets the DOM cache used for additional documents loaded using the 523 * document() function. 524 */ 525 public void setDOMCache(DOMCache cache) { 526 _domCache = cache; 527 } 528 529 /** 530 * Returns the DOM cache used for this translet. Used by the LoadDocument 531 * class (if present) when the document() function is used. 532 */ 533 public DOMCache getDOMCache() { 534 return(_domCache); 535 } 536 537 /************************************************************************ 538 * Multiple output document extension. 539 * See compiler/TransletOutput for actual implementation. 540 ************************************************************************/ 541 542 public SerializationHandler openOutputHandler(String filename, boolean append) 543 throws TransletException 544 { 545 try { 546 final TransletOutputHandlerFactory factory 547 = TransletOutputHandlerFactory.newInstance(); 548 549 String dirStr = new File(filename).getParent(); 550 if ((null != dirStr) && (dirStr.length() > 0)) { 551 File dir = new File(dirStr); 552 dir.mkdirs(); 553 } 554 555 factory.setEncoding(_encoding); 556 factory.setOutputMethod(_method); 557 factory.setWriter(new FileWriter(filename, append)); 558 factory.setOutputType(TransletOutputHandlerFactory.STREAM); 559 560 final SerializationHandler handler 561 = factory.getSerializationHandler(); 562 563 transferOutputSettings(handler); 564 handler.startDocument(); 565 return handler; 566 } 567 catch (Exception e) { 568 throw new TransletException(e); 569 } 570 } 571 572 public SerializationHandler openOutputHandler(String filename) 573 throws TransletException 574 { 575 return openOutputHandler(filename, false); 576 } 577 578 public void closeOutputHandler(SerializationHandler handler) { 579 try { 580 handler.endDocument(); 581 handler.close(); 582 } 583 catch (Exception e) { 584 // what can you do? 585 } 586 } 587 588 /************************************************************************ 589 * Native API transformation methods - _NOT_ JAXP/TrAX 590 ************************************************************************/ 591 592 /** 593 * Main transform() method - this is overridden by the compiled translet 594 */ 595 public abstract void transform(DOM document, DTMAxisIterator iterator, 596 SerializationHandler handler) 597 throws TransletException; 598 599 /** 600 * Calls transform() with a given output handler 601 */ 602 public final void transform(DOM document, SerializationHandler handler) 603 throws TransletException { 604 try { 605 transform(document, document.getIterator(), handler); 606 } finally { 607 _keyIndexes = null; 608 } 609 } 610 611 /** 612 * Used by some compiled code as a shortcut for passing strings to the 613 * output handler 614 */ 615 public final void characters(final String string, 616 SerializationHandler handler) 617 throws TransletException { 618 if (string != null) { 619 //final int length = string.length(); 620 try { 621 handler.characters(string); 622 } catch (Exception e) { 623 throw new TransletException(e); 624 } 625 } 626 } 627 628 /** 629 * Add's a name of an element whose text contents should be output as CDATA 630 */ 631 public void addCdataElement(String name) { 632 if (_cdata == null) { 633 _cdata = new Vector(); 634 } 635 636 int lastColon = name.lastIndexOf(':'); 637 638 if (lastColon > 0) { 639 String uri = name.substring(0, lastColon); 640 String localName = name.substring(lastColon+1); 641 _cdata.addElement(uri); 642 _cdata.addElement(localName); 643 } else { 644 _cdata.addElement(null); 645 _cdata.addElement(name); 646 } 647 } 648 649 /** 650 * Transfer the output settings to the output post-processor 651 */ 652 protected void transferOutputSettings(SerializationHandler handler) { 653 if (_method != null) { 654 if (_method.equals("xml")) { 655 if (_standalone != null) { 656 handler.setStandalone(_standalone); 657 } 658 if (_omitHeader) { 659 handler.setOmitXMLDeclaration(true); 660 } 661 handler.setCdataSectionElements(_cdata); 662 if (_version != null) { 663 handler.setVersion(_version); 664 } 665 handler.setIndent(_indent); 666 handler.setIndentAmount(_indentamount); 667 if (_doctypeSystem != null) { 668 handler.setDoctype(_doctypeSystem, _doctypePublic); 669 } 670 } 671 else if (_method.equals("html")) { 672 handler.setIndent(_indent); 673 handler.setDoctype(_doctypeSystem, _doctypePublic); 674 if (_mediaType != null) { 675 handler.setMediaType(_mediaType); 676 } 677 } 678 } 679 else { 680 handler.setCdataSectionElements(_cdata); 681 if (_version != null) { 682 handler.setVersion(_version); 683 } 684 if (_standalone != null) { 685 handler.setStandalone(_standalone); 686 } 687 if (_omitHeader) { 688 handler.setOmitXMLDeclaration(true); 689 } 690 handler.setIndent(_indent); 691 handler.setDoctype(_doctypeSystem, _doctypePublic); 692 } 693 } 694 695 private Hashtable _auxClasses = null; 696 697 public void addAuxiliaryClass(Class auxClass) { 698 if (_auxClasses == null) _auxClasses = new Hashtable(); 699 _auxClasses.put(auxClass.getName(), auxClass); 700 } 701 702 public void setAuxiliaryClasses(Hashtable auxClasses) { 703 _auxClasses = auxClasses; 704 } 705 706 public Class getAuxiliaryClass(String className) { 707 if (_auxClasses == null) return null; 708 return((Class)_auxClasses.get(className)); 709 } 710 711 // GTM added (see pg 110) 712 public String[] getNamesArray() { 713 return namesArray; 714 } 715 716 public String[] getUrisArray() { 717 return urisArray; 718 } 719 720 public int[] getTypesArray() { 721 return typesArray; 722 } 723 724 public String[] getNamespaceArray() { 725 return namespaceArray; 726 } 727 728 public boolean hasIdCall() { 729 return _hasIdCall; 730 } 731 732 public Templates getTemplates() { 733 return _templates; 734 } 735 736 public void setTemplates(Templates templates) { 737 _templates = templates; 738 } 739 740 /************************************************************************ 741 * DOMImplementation caching for basis library 742 ************************************************************************/ 743 protected DOMImplementation _domImplementation = null; 744 745 public Document newDocument(String uri, String qname) 746 throws ParserConfigurationException 747 { 748 if (_domImplementation == null) { 749 _domImplementation = DocumentBuilderFactory.newInstance() 750 .newDocumentBuilder().getDOMImplementation(); 751 } 752 return _domImplementation.createDocument(uri, qname, null); 753 } 754 }