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    }