1/*
2 * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20/*
21 * $Id: BasisLibrary.java,v 1.6 2006/06/20 21:51:58 spericas Exp $
22 */
23
24package com.sun.org.apache.xalan.internal.xsltc.runtime;
25
26import com.sun.org.apache.xalan.internal.utils.SecuritySupport;
27import java.text.DecimalFormat;
28import java.text.DecimalFormatSymbols;
29import java.text.FieldPosition;
30import java.text.MessageFormat;
31import java.text.NumberFormat;
32import java.util.Locale;
33import java.util.ResourceBundle;
34import java.util.concurrent.atomic.AtomicInteger;
35import javax.xml.transform.dom.DOMSource;
36
37import com.sun.org.apache.xalan.internal.xsltc.DOM;
38import com.sun.org.apache.xalan.internal.xsltc.Translet;
39import com.sun.org.apache.xalan.internal.xsltc.dom.AbsoluteIterator;
40import com.sun.org.apache.xml.internal.dtm.Axis;
41import com.sun.org.apache.xalan.internal.xsltc.dom.DOMAdapter;
42import com.sun.org.apache.xalan.internal.xsltc.dom.MultiDOM;
43import com.sun.org.apache.xalan.internal.xsltc.dom.SingletonIterator;
44import com.sun.org.apache.xalan.internal.xsltc.dom.StepIterator;
45import com.sun.org.apache.xalan.internal.xsltc.dom.ArrayNodeListIterator;
46import com.sun.org.apache.xml.internal.dtm.DTM;
47import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
48import com.sun.org.apache.xml.internal.dtm.DTMManager;
49import com.sun.org.apache.xml.internal.dtm.ref.DTMDefaultBase;
50import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeProxy;
51
52import org.w3c.dom.DOMException;
53import org.w3c.dom.Attr;
54import org.w3c.dom.Document;
55import org.w3c.dom.Element;
56import org.w3c.dom.NodeList;
57import org.xml.sax.SAXException;
58import com.sun.org.apache.xml.internal.serializer.NamespaceMappings;
59import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
60import com.sun.org.apache.xml.internal.utils.XML11Char;
61
62/**
63 * Standard XSLT functions. All standard functions expect the current node
64 * and the DOM as their last two arguments.
65 */
66public final class BasisLibrary {
67
68    private final static String EMPTYSTRING = "";
69
70    /**
71     * Re-use a single instance of StringBuffer (per thread) in the basis library.
72     * StringBuilder is better, however, DecimalFormat only accept StringBuffer
73     */
74    private static final ThreadLocal<StringBuilder> threadLocalStringBuilder =
75        new ThreadLocal<StringBuilder> () {
76            @Override protected StringBuilder initialValue() {
77                return new StringBuilder();
78            }
79    };
80
81    /**
82     * ThreadLocal for StringBuffer used
83     */
84    private static final ThreadLocal<StringBuffer> threadLocalStringBuffer =
85        new ThreadLocal<StringBuffer> () {
86            @Override protected StringBuffer initialValue() {
87                return new StringBuffer();
88            }
89    };
90
91    /**
92     * Standard function count(node-set)
93     */
94    public static int countF(DTMAxisIterator iterator) {
95        return(iterator.getLast());
96    }
97
98    /**
99     * Standard function position()
100     * @deprecated This method exists only for backwards compatibility with old
101     *             translets.  New code should not reference it.
102     */
103    public static int positionF(DTMAxisIterator iterator) {
104        return iterator.isReverse()
105                     ? iterator.getLast() - iterator.getPosition() + 1
106                     : iterator.getPosition();
107    }
108
109    /**
110     * XSLT Standard function sum(node-set).
111     * stringToDouble is inlined
112     */
113    public static double sumF(DTMAxisIterator iterator, DOM dom) {
114        try {
115            double result = 0.0;
116            int node;
117            while ((node = iterator.next()) != DTMAxisIterator.END) {
118                result += Double.parseDouble(dom.getStringValueX(node));
119            }
120            return result;
121        }
122        catch (NumberFormatException e) {
123            return Double.NaN;
124        }
125    }
126
127    /**
128     * XSLT Standard function string()
129     */
130    public static String stringF(int node, DOM dom) {
131        return dom.getStringValueX(node);
132    }
133
134    /**
135     * XSLT Standard function string(value)
136     */
137    public static String stringF(Object obj, DOM dom) {
138        if (obj instanceof DTMAxisIterator) {
139            return dom.getStringValueX(((DTMAxisIterator)obj).reset().next());
140        }
141        else if (obj instanceof Node) {
142            return dom.getStringValueX(((Node)obj).node);
143        }
144        else if (obj instanceof DOM) {
145            return ((DOM)obj).getStringValue();
146        }
147        else {
148            return obj.toString();
149        }
150    }
151
152    /**
153     * XSLT Standard function string(value)
154     */
155    public static String stringF(Object obj, int node, DOM dom) {
156        if (obj instanceof DTMAxisIterator) {
157            return dom.getStringValueX(((DTMAxisIterator)obj).reset().next());
158        }
159        else if (obj instanceof Node) {
160            return dom.getStringValueX(((Node)obj).node);
161        }
162        else if (obj instanceof DOM) {
163            // When the first argument is a DOM we want the whole
164            // DOM and not just a single node - that would not make sense.
165            //return ((DOM)obj).getStringValueX(node);
166            return ((DOM)obj).getStringValue();
167        }
168        else if (obj instanceof Double) {
169            Double d = (Double)obj;
170            final String result = d.toString();
171            final int length = result.length();
172            if ((result.charAt(length-2)=='.') &&
173                (result.charAt(length-1) == '0'))
174                return result.substring(0, length-2);
175            else
176                return result;
177        }
178        else {
179            return obj != null ? obj.toString() : "";
180        }
181    }
182
183    /**
184     * XSLT Standard function number()
185     */
186    public static double numberF(int node, DOM dom) {
187        return stringToReal(dom.getStringValueX(node));
188    }
189
190    /**
191     * XSLT Standard function number(value)
192     */
193    public static double numberF(Object obj, DOM dom) {
194        if (obj instanceof Double) {
195            return ((Double) obj).doubleValue();
196        }
197        else if (obj instanceof Integer) {
198            return ((Integer) obj).doubleValue();
199        }
200        else if (obj instanceof Boolean) {
201            return  ((Boolean) obj).booleanValue() ? 1.0 : 0.0;
202        }
203        else if (obj instanceof String) {
204            return stringToReal((String) obj);
205        }
206        else if (obj instanceof DTMAxisIterator) {
207            DTMAxisIterator iter = (DTMAxisIterator) obj;
208            return stringToReal(dom.getStringValueX(iter.reset().next()));
209        }
210        else if (obj instanceof Node) {
211            return stringToReal(dom.getStringValueX(((Node) obj).node));
212        }
213        else if (obj instanceof DOM) {
214            return stringToReal(((DOM) obj).getStringValue());
215        }
216        else {
217            final String className = obj.getClass().getName();
218            runTimeError(INVALID_ARGUMENT_ERR, className, "number()");
219            return 0.0;
220        }
221    }
222
223    /**
224     * XSLT Standard function round()
225     */
226    public static double roundF(double d) {
227            return (d<-0.5 || d>0.0)?Math.floor(d+0.5):((d==0.0)?
228                        d:(Double.isNaN(d)?Double.NaN:-0.0));
229    }
230
231    /**
232     * XSLT Standard function boolean()
233     */
234    public static boolean booleanF(Object obj) {
235        if (obj instanceof Double) {
236            final double temp = ((Double) obj).doubleValue();
237            return temp != 0.0 && !Double.isNaN(temp);
238        }
239        else if (obj instanceof Integer) {
240            return ((Integer) obj).doubleValue() != 0;
241        }
242        else if (obj instanceof Boolean) {
243            return  ((Boolean) obj).booleanValue();
244        }
245        else if (obj instanceof String) {
246            return !((String) obj).equals(EMPTYSTRING);
247        }
248        else if (obj instanceof DTMAxisIterator) {
249            DTMAxisIterator iter = (DTMAxisIterator) obj;
250            return iter.reset().next() != DTMAxisIterator.END;
251        }
252        else if (obj instanceof Node) {
253            return true;
254        }
255        else if (obj instanceof DOM) {
256            String temp = ((DOM) obj).getStringValue();
257            return !temp.equals(EMPTYSTRING);
258        }
259        else {
260            final String className = obj.getClass().getName();
261            runTimeError(INVALID_ARGUMENT_ERR, className, "boolean()");
262        }
263        return false;
264    }
265
266    /**
267     * XSLT Standard function substring(). Must take a double because of
268     * conversions resulting into NaNs and rounding.
269     */
270    public static String substringF(String value, double start) {
271        if (Double.isNaN(start))
272            return(EMPTYSTRING);
273
274        final int strlen = getStringLength(value);
275        int istart = (int)Math.round(start) - 1;
276
277        if (istart > strlen)
278            return(EMPTYSTRING);
279        if (istart < 1)
280            istart = 0;
281        try {
282            istart = value.offsetByCodePoints(0, istart);
283            return value.substring(istart);
284        } catch (IndexOutOfBoundsException e) {
285            runTimeError(RUN_TIME_INTERNAL_ERR, "substring()");
286            return null;
287        }
288    }
289
290    /**
291     * XSLT Standard function substring(). Must take a double because of
292     * conversions resulting into NaNs and rounding.
293     */
294    public static String substringF(String value, double start, double length) {
295        if (Double.isInfinite(start) ||
296            Double.isNaN(start) ||
297            Double.isNaN(length) ||
298            length < 0)
299            return(EMPTYSTRING);
300
301        int istart = (int)Math.round(start) - 1;
302        int ilength = (int)Math.round(length);
303        final int isum;
304        if (Double.isInfinite(length))
305            isum = Integer.MAX_VALUE;
306        else
307            isum = istart + ilength;
308
309        final int strlen = getStringLength(value);
310        if (isum < 0 || istart > strlen)
311                return(EMPTYSTRING);
312
313        if (istart < 0) {
314            ilength += istart;
315            istart = 0;
316        }
317
318        try {
319            istart = value.offsetByCodePoints(0, istart);
320            if (isum > strlen) {
321                return value.substring(istart);
322            } else {
323                int offset = value.offsetByCodePoints(istart, ilength);
324                return value.substring(istart, offset);
325            }
326        } catch (IndexOutOfBoundsException e) {
327            runTimeError(RUN_TIME_INTERNAL_ERR, "substring()");
328            return null;
329        }
330    }
331
332    /**
333     * XSLT Standard function substring-after().
334     */
335    public static String substring_afterF(String value, String substring) {
336        final int index = value.indexOf(substring);
337        if (index >= 0)
338            return value.substring(index + substring.length());
339        else
340            return EMPTYSTRING;
341    }
342
343    /**
344     * XSLT Standard function substring-before().
345     */
346    public static String substring_beforeF(String value, String substring) {
347        final int index = value.indexOf(substring);
348        if (index >= 0)
349            return value.substring(0, index);
350        else
351            return EMPTYSTRING;
352    }
353
354    /**
355     * XSLT Standard function translate().
356     */
357    public static String translateF(String value, String from, String to) {
358        final int tol = to.length();
359        final int froml = from.length();
360        final int valuel = value.length();
361
362        final StringBuilder result = threadLocalStringBuilder.get();
363    result.setLength(0);
364        for (int j, i = 0; i < valuel; i++) {
365            final char ch = value.charAt(i);
366            for (j = 0; j < froml; j++) {
367                if (ch == from.charAt(j)) {
368                    if (j < tol)
369                        result.append(to.charAt(j));
370                    break;
371                }
372            }
373            if (j == froml)
374                result.append(ch);
375        }
376        return result.toString();
377    }
378
379    /**
380     * XSLT Standard function normalize-space().
381     */
382    public static String normalize_spaceF(int node, DOM dom) {
383        return normalize_spaceF(dom.getStringValueX(node));
384    }
385
386    /**
387     * XSLT Standard function normalize-space(string).
388     */
389    public static String normalize_spaceF(String value) {
390        int i = 0, n = value.length();
391        StringBuilder result = threadLocalStringBuilder.get();
392    result.setLength(0);
393
394        while (i < n && isWhiteSpace(value.charAt(i)))
395            i++;
396
397        while (true) {
398            while (i < n && !isWhiteSpace(value.charAt(i))) {
399                result.append(value.charAt(i++));
400            }
401            if (i == n)
402                break;
403            while (i < n && isWhiteSpace(value.charAt(i))) {
404                i++;
405            }
406            if (i < n)
407                result.append(' ');
408        }
409        return result.toString();
410    }
411
412    /**
413     * XSLT Standard function generate-id().
414     */
415    public static String generate_idF(int node) {
416        if (node > 0)
417            // Only generate ID if node exists
418            return "N" + node;
419        else
420            // Otherwise return an empty string
421            return EMPTYSTRING;
422    }
423
424    /**
425     * utility function for calls to local-name().
426     */
427    public static String getLocalName(String value) {
428        int idx = value.lastIndexOf(':');
429        if (idx >= 0) value = value.substring(idx + 1);
430        idx = value.lastIndexOf('@');
431        if (idx >= 0) value = value.substring(idx + 1);
432        return(value);
433    }
434
435    /**
436     * External functions that cannot be resolved are replaced with a call
437     * to this method. This method will generate a runtime errors. A good
438     * stylesheet checks whether the function exists using conditional
439     * constructs, and never really tries to call it if it doesn't exist.
440     * But simple stylesheets may result in a call to this method.
441     * The compiler should generate a warning if it encounters a call to
442     * an unresolved external function.
443     */
444    public static void unresolved_externalF(String name) {
445        runTimeError(EXTERNAL_FUNC_ERR, name);
446    }
447
448    /**
449     * Utility function to throw a runtime error on the use of an extension
450     * function when the secure processing feature is set to true.
451     */
452    public static void unallowed_extension_functionF(String name) {
453        runTimeError(UNALLOWED_EXTENSION_FUNCTION_ERR, name);
454    }
455
456    /**
457     * Utility function to throw a runtime error on the use of an extension
458     * element when the secure processing feature is set to true.
459     */
460    public static void unallowed_extension_elementF(String name) {
461        runTimeError(UNALLOWED_EXTENSION_ELEMENT_ERR, name);
462    }
463
464    /**
465     * Utility function to throw a runtime error for an unsupported element.
466     *
467     * This is only used in forward-compatibility mode, when the control flow
468     * cannot be determined. In 1.0 mode, the error message is emitted at
469     * compile time.
470     */
471    public static void unsupported_ElementF(String qname, boolean isExtension) {
472        if (isExtension)
473            runTimeError(UNSUPPORTED_EXT_ERR, qname);
474        else
475            runTimeError(UNSUPPORTED_XSL_ERR, qname);
476    }
477
478    /**
479     * XSLT Standard function namespace-uri(node-set).
480     */
481    public static String namespace_uriF(DTMAxisIterator iter, DOM dom) {
482        return namespace_uriF(iter.next(), dom);
483    }
484
485    /**
486     * XSLT Standard function system-property(name)
487     */
488    public static String system_propertyF(String name) {
489        if (name.equals("xsl:version"))
490            return("1.0");
491        if (name.equals("xsl:vendor"))
492            return("Apache Software Foundation (Xalan XSLTC)");
493        if (name.equals("xsl:vendor-url"))
494            return("http://xml.apache.org/xalan-j");
495
496        runTimeError(INVALID_ARGUMENT_ERR, name, "system-property()");
497        return(EMPTYSTRING);
498    }
499
500    /**
501     * XSLT Standard function namespace-uri().
502     */
503    public static String namespace_uriF(int node, DOM dom) {
504        final String value = dom.getNodeName(node);
505        final int colon = value.lastIndexOf(':');
506        if (colon >= 0)
507            return value.substring(0, colon);
508        else
509            return EMPTYSTRING;
510    }
511
512    /**
513     * Implements the object-type() extension function.
514     *
515     * @see <a href="http://www.exslt.org/">EXSLT</a>
516     */
517    public static String objectTypeF(Object obj)
518    {
519      if (obj instanceof String)
520        return "string";
521      else if (obj instanceof Boolean)
522        return "boolean";
523      else if (obj instanceof Number)
524        return "number";
525      else if (obj instanceof DOM)
526        return "RTF";
527      else if (obj instanceof DTMAxisIterator)
528        return "node-set";
529      else
530        return "unknown";
531    }
532
533    /**
534     * Implements the nodeset() extension function.
535     */
536    public static DTMAxisIterator nodesetF(Object obj) {
537        if (obj instanceof DOM) {
538           //final DOMAdapter adapter = (DOMAdapter) obj;
539           final DOM dom = (DOM)obj;
540           return new SingletonIterator(dom.getDocument(), true);
541        }
542        else if (obj instanceof DTMAxisIterator) {
543           return (DTMAxisIterator) obj;
544        }
545        else {
546            final String className = obj.getClass().getName();
547            runTimeError(DATA_CONVERSION_ERR, "node-set", className);
548            return null;
549        }
550    }
551
552    //-- Begin utility functions
553
554    private static boolean isWhiteSpace(char ch) {
555        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
556    }
557
558    private static boolean compareStrings(String lstring, String rstring,
559                                          int op, DOM dom) {
560        switch (op) {
561    case Operators.EQ:
562            return lstring.equals(rstring);
563
564    case Operators.NE:
565            return !lstring.equals(rstring);
566
567    case Operators.GT:
568            return numberF(lstring, dom) > numberF(rstring, dom);
569
570    case Operators.LT:
571            return numberF(lstring, dom) < numberF(rstring, dom);
572
573    case Operators.GE:
574            return numberF(lstring, dom) >= numberF(rstring, dom);
575
576    case Operators.LE:
577            return numberF(lstring, dom) <= numberF(rstring, dom);
578
579        default:
580            runTimeError(RUN_TIME_INTERNAL_ERR, "compare()");
581            return false;
582        }
583    }
584
585    /**
586     * Utility function: node-set/node-set compare.
587     */
588    public static boolean compare(DTMAxisIterator left, DTMAxisIterator right,
589                                  int op, DOM dom) {
590        int lnode;
591        left.reset();
592
593        while ((lnode = left.next()) != DTMAxisIterator.END) {
594            final String lvalue = dom.getStringValueX(lnode);
595
596            int rnode;
597            right.reset();
598            while ((rnode = right.next()) != DTMAxisIterator.END) {
599                // String value must be the same if both nodes are the same
600                if (lnode == rnode) {
601                    if (op == Operators.EQ) {
602                        return true;
603                    } else if (op == Operators.NE) {
604                        continue;
605                    }
606                }
607                if (compareStrings(lvalue, dom.getStringValueX(rnode), op,
608                                   dom)) {
609                    return true;
610                }
611            }
612        }
613        return false;
614    }
615
616    public static boolean compare(int node, DTMAxisIterator iterator,
617                                  int op, DOM dom) {
618        //iterator.reset();
619
620        int rnode;
621        String value;
622
623        switch(op) {
624    case Operators.EQ:
625            rnode = iterator.next();
626            if (rnode != DTMAxisIterator.END) {
627                value = dom.getStringValueX(node);
628                do {
629                    if (node == rnode
630                          || value.equals(dom.getStringValueX(rnode))) {
631                       return true;
632                    }
633                } while ((rnode = iterator.next()) != DTMAxisIterator.END);
634            }
635            break;
636    case Operators.NE:
637            rnode = iterator.next();
638            if (rnode != DTMAxisIterator.END) {
639                value = dom.getStringValueX(node);
640                do {
641                    if (node != rnode
642                          && !value.equals(dom.getStringValueX(rnode))) {
643                        return true;
644                    }
645                } while ((rnode = iterator.next()) != DTMAxisIterator.END);
646            }
647            break;
648    case Operators.LT:
649            // Assume we're comparing document order here
650            while ((rnode = iterator.next()) != DTMAxisIterator.END) {
651                if (rnode > node) return true;
652            }
653            break;
654    case Operators.GT:
655            // Assume we're comparing document order here
656            while ((rnode = iterator.next()) != DTMAxisIterator.END) {
657                if (rnode < node) return true;
658            }
659            break;
660        }
661        return(false);
662    }
663
664    /**
665     * Utility function: node-set/number compare.
666     */
667    public static boolean compare(DTMAxisIterator left, final double rnumber,
668                                  final int op, DOM dom) {
669        int node;
670        //left.reset();
671
672        switch (op) {
673    case Operators.EQ:
674            while ((node = left.next()) != DTMAxisIterator.END) {
675                if (numberF(dom.getStringValueX(node), dom) == rnumber)
676                    return true;
677            }
678            break;
679
680    case Operators.NE:
681            while ((node = left.next()) != DTMAxisIterator.END) {
682                if (numberF(dom.getStringValueX(node), dom) != rnumber)
683                    return true;
684            }
685            break;
686
687    case Operators.GT:
688            while ((node = left.next()) != DTMAxisIterator.END) {
689                if (numberF(dom.getStringValueX(node), dom) > rnumber)
690                    return true;
691            }
692            break;
693
694    case Operators.LT:
695            while ((node = left.next()) != DTMAxisIterator.END) {
696                if (numberF(dom.getStringValueX(node), dom) < rnumber)
697                    return true;
698            }
699            break;
700
701    case Operators.GE:
702            while ((node = left.next()) != DTMAxisIterator.END) {
703                if (numberF(dom.getStringValueX(node), dom) >= rnumber)
704                    return true;
705            }
706            break;
707
708    case Operators.LE:
709            while ((node = left.next()) != DTMAxisIterator.END) {
710                if (numberF(dom.getStringValueX(node), dom) <= rnumber)
711                    return true;
712            }
713            break;
714
715        default:
716            runTimeError(RUN_TIME_INTERNAL_ERR, "compare()");
717        }
718
719        return false;
720    }
721
722    /**
723     * Utility function: node-set/string comparison.
724     */
725    public static boolean compare(DTMAxisIterator left, final String rstring,
726                                  int op, DOM dom) {
727        int node;
728        //left.reset();
729        while ((node = left.next()) != DTMAxisIterator.END) {
730            if (compareStrings(dom.getStringValueX(node), rstring, op, dom)) {
731                return true;
732            }
733        }
734        return false;
735    }
736
737
738    public static boolean compare(Object left, Object right,
739                                  int op, DOM dom)
740    {
741        boolean result = false;
742        boolean hasSimpleArgs = hasSimpleType(left) && hasSimpleType(right);
743
744    if (op != Operators.EQ && op != Operators.NE) {
745            // If node-boolean comparison -> convert node to boolean
746            if (left instanceof Node || right instanceof Node) {
747                if (left instanceof Boolean) {
748                    right = new Boolean(booleanF(right));
749                    hasSimpleArgs = true;
750                }
751                if (right instanceof Boolean) {
752                    left = new Boolean(booleanF(left));
753                    hasSimpleArgs = true;
754                }
755            }
756
757            if (hasSimpleArgs) {
758                switch (op) {
759        case Operators.GT:
760                    return numberF(left, dom) > numberF(right, dom);
761
762        case Operators.LT:
763                    return numberF(left, dom) < numberF(right, dom);
764
765        case Operators.GE:
766                    return numberF(left, dom) >= numberF(right, dom);
767
768        case Operators.LE:
769                    return numberF(left, dom) <= numberF(right, dom);
770
771        default:
772                    runTimeError(RUN_TIME_INTERNAL_ERR, "compare()");
773                }
774            }
775            // falls through
776        }
777
778        if (hasSimpleArgs) {
779            if (left instanceof Boolean || right instanceof Boolean) {
780                result = booleanF(left) == booleanF(right);
781            }
782            else if (left instanceof Double || right instanceof Double ||
783                     left instanceof Integer || right instanceof Integer) {
784                result = numberF(left, dom) == numberF(right, dom);
785            }
786            else { // compare them as strings
787                result = stringF(left, dom).equals(stringF(right, dom));
788            }
789
790            if (op == Operators.NE) {
791                result = !result;
792            }
793        }
794        else {
795            if (left instanceof Node) {
796                left = new SingletonIterator(((Node)left).node);
797            }
798            if (right instanceof Node) {
799                right = new SingletonIterator(((Node)right).node);
800            }
801
802            if (hasSimpleType(left) ||
803                left instanceof DOM && right instanceof DTMAxisIterator) {
804                // swap operands and operator
805                final Object temp = right; right = left; left = temp;
806                op = Operators.swapOp(op);
807            }
808
809            if (left instanceof DOM) {
810                if (right instanceof Boolean) {
811                    result = ((Boolean)right).booleanValue();
812                    return result == (op == Operators.EQ);
813                }
814
815                final String sleft = ((DOM)left).getStringValue();
816
817                if (right instanceof Number) {
818                    result = ((Number)right).doubleValue() ==
819                        stringToReal(sleft);
820                }
821                else if (right instanceof String) {
822                    result = sleft.equals((String)right);
823                }
824                else if (right instanceof DOM) {
825                    result = sleft.equals(((DOM)right).getStringValue());
826                }
827
828                if (op == Operators.NE) {
829                    result = !result;
830                }
831                return result;
832            }
833
834            // Next, node-set/t for t in {real, string, node-set, result-tree}
835
836            DTMAxisIterator iter = ((DTMAxisIterator)left).reset();
837
838            if (right instanceof DTMAxisIterator) {
839                result = compare(iter, (DTMAxisIterator)right, op, dom);
840            }
841            else if (right instanceof String) {
842                result = compare(iter, (String)right, op, dom);
843            }
844            else if (right instanceof Number) {
845                final double temp = ((Number)right).doubleValue();
846                result = compare(iter, temp, op, dom);
847            }
848            else if (right instanceof Boolean) {
849                boolean temp = ((Boolean)right).booleanValue();
850                result = (iter.reset().next() != DTMAxisIterator.END) == temp;
851            }
852            else if (right instanceof DOM) {
853                result = compare(iter, ((DOM)right).getStringValue(),
854                                 op, dom);
855            }
856            else if (right == null) {
857                return(false);
858            }
859            else {
860                final String className = right.getClass().getName();
861                runTimeError(INVALID_ARGUMENT_ERR, className, "compare()");
862            }
863        }
864        return result;
865    }
866
867    /**
868     * Utility function: used to test context node's language
869     */
870    public static boolean testLanguage(String testLang, DOM dom, int node) {
871        // language for context node (if any)
872        String nodeLang = dom.getLanguage(node);
873        if (nodeLang == null)
874            return(false);
875        else
876            nodeLang = nodeLang.toLowerCase();
877
878        // compare context node's language agains test language
879        testLang = testLang.toLowerCase();
880        if (testLang.length() == 2) {
881            return(nodeLang.startsWith(testLang));
882        }
883        else {
884            return(nodeLang.equals(testLang));
885        }
886    }
887
888    private static boolean hasSimpleType(Object obj) {
889        return obj instanceof Boolean || obj instanceof Double ||
890            obj instanceof Integer || obj instanceof String ||
891            obj instanceof Node || obj instanceof DOM;
892    }
893
894    /**
895     * Utility function: used in StringType to convert a string to a real.
896     */
897    public static double stringToReal(String s) {
898        try {
899            return Double.valueOf(s).doubleValue();
900        }
901        catch (NumberFormatException e) {
902            return Double.NaN;
903        }
904    }
905
906    /**
907     * Utility function: used in StringType to convert a string to an int.
908     */
909    public static int stringToInt(String s) {
910        try {
911            return Integer.parseInt(s);
912        }
913        catch (NumberFormatException e) {
914            return(-1); // ???
915        }
916    }
917
918    private static final int DOUBLE_FRACTION_DIGITS = 340;
919    private static final double lowerBounds = 0.001;
920    private static final double upperBounds = 10000000;
921    private static DecimalFormat defaultFormatter, xpathFormatter;
922    private static String defaultPattern = "";
923
924    static {
925        NumberFormat f = NumberFormat.getInstance(Locale.getDefault());
926        defaultFormatter = (f instanceof DecimalFormat) ?
927            (DecimalFormat) f : new DecimalFormat();
928        // Set max fraction digits so that truncation does not occur. Setting
929        // the max to Integer.MAX_VALUE may cause problems with some JDK's.
930        defaultFormatter.setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
931        defaultFormatter.setMinimumFractionDigits(0);
932        defaultFormatter.setMinimumIntegerDigits(1);
933        defaultFormatter.setGroupingUsed(false);
934
935        // This formatter is used to convert numbers according to the XPath
936        // 1.0 syntax which ignores locales (http://www.w3.org/TR/xpath#NT-Number)
937        xpathFormatter = new DecimalFormat("",
938            new DecimalFormatSymbols(Locale.US));
939        xpathFormatter.setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
940        xpathFormatter.setMinimumFractionDigits(0);
941        xpathFormatter.setMinimumIntegerDigits(1);
942        xpathFormatter.setGroupingUsed(false);
943    }
944
945    /**
946     * Utility function: used in RealType to convert a real to a string.
947     * Removes the decimal if null. Uses a specialized formatter object
948     * for very large and very small numbers that ignores locales, thus
949     * using always using "." as a decimal separator.
950     */
951    public static String realToString(double d) {
952        final double m = Math.abs(d);
953        if ((m >= lowerBounds) && (m < upperBounds)) {
954            final String result = Double.toString(d);
955            final int length = result.length();
956            // Remove leading zeros.
957            if ((result.charAt(length-2) == '.') &&
958                (result.charAt(length-1) == '0'))
959                return result.substring(0, length-2);
960            else
961                return result;
962        }
963        else {
964            if (Double.isNaN(d) || Double.isInfinite(d))
965                return(Double.toString(d));
966
967            //Convert -0.0 to +0.0 other values remains the same
968            d = d + 0.0;
969
970            // Use the XPath formatter to ignore locales
971            StringBuffer result = threadLocalStringBuffer.get();
972            result.setLength(0);
973            xpathFormatter.format(d, result, _fieldPosition);
974            return result.toString();
975        }
976    }
977
978    /**
979     * Utility function: used in RealType to convert a real to an integer
980     */
981    public static int realToInt(double d) {
982        return (int)d;
983    }
984
985    /**
986     * Utility function: used to format/adjust  a double to a string. The
987     * DecimalFormat object comes from the 'formatSymbols' map in
988     * AbstractTranslet.
989     */
990    private static FieldPosition _fieldPosition = new FieldPosition(0);
991
992    public static String formatNumber(double number, String pattern,
993                                      DecimalFormat formatter) {
994        // bugzilla fix 12813
995        if (formatter == null) {
996            formatter = defaultFormatter;
997        }
998        try {
999            StringBuffer result = threadLocalStringBuffer.get();
1000        result.setLength(0);
1001            if (pattern != defaultPattern) {
1002                formatter.applyLocalizedPattern(pattern);
1003            }
1004        formatter.format(number, result, _fieldPosition);
1005            return result.toString();
1006        }
1007        catch (IllegalArgumentException e) {
1008            runTimeError(FORMAT_NUMBER_ERR, Double.toString(number), pattern);
1009            return(EMPTYSTRING);
1010        }
1011    }
1012
1013    /**
1014     * Utility function: used to convert references to node-sets. If the
1015     * obj is an instanceof Node then create a singleton iterator.
1016     */
1017    public static DTMAxisIterator referenceToNodeSet(Object obj) {
1018        // Convert var/param -> node
1019        if (obj instanceof Node) {
1020            return(new SingletonIterator(((Node)obj).node));
1021        }
1022        // Convert var/param -> node-set
1023        else if (obj instanceof DTMAxisIterator) {
1024            return(((DTMAxisIterator)obj).cloneIterator().reset());
1025        }
1026        else {
1027            final String className = obj.getClass().getName();
1028            runTimeError(DATA_CONVERSION_ERR, className, "node-set");
1029            return null;
1030        }
1031    }
1032
1033    /**
1034     * Utility function: used to convert reference to org.w3c.dom.NodeList.
1035     */
1036    public static NodeList referenceToNodeList(Object obj, DOM dom) {
1037        if (obj instanceof Node || obj instanceof DTMAxisIterator) {
1038            DTMAxisIterator iter = referenceToNodeSet(obj);
1039            return dom.makeNodeList(iter);
1040        }
1041        else if (obj instanceof DOM) {
1042          dom = (DOM)obj;
1043          return dom.makeNodeList(DTMDefaultBase.ROOTNODE);
1044        }
1045        else {
1046            final String className = obj.getClass().getName();
1047            runTimeError(DATA_CONVERSION_ERR, className,
1048                "org.w3c.dom.NodeList");
1049            return null;
1050        }
1051    }
1052
1053    /**
1054     * Utility function: used to convert reference to org.w3c.dom.Node.
1055     */
1056    public static org.w3c.dom.Node referenceToNode(Object obj, DOM dom) {
1057        if (obj instanceof Node || obj instanceof DTMAxisIterator) {
1058            DTMAxisIterator iter = referenceToNodeSet(obj);
1059            return dom.makeNode(iter);
1060        }
1061        else if (obj instanceof DOM) {
1062          dom = (DOM)obj;
1063          DTMAxisIterator iter = dom.getChildren(DTMDefaultBase.ROOTNODE);
1064          return dom.makeNode(iter);
1065        }
1066        else {
1067            final String className = obj.getClass().getName();
1068            runTimeError(DATA_CONVERSION_ERR, className, "org.w3c.dom.Node");
1069            return null;
1070        }
1071    }
1072
1073    /**
1074     * Utility function: used to convert reference to long.
1075     */
1076    public static long referenceToLong(Object obj) {
1077        if (obj instanceof Number) {
1078            return ((Number) obj).longValue();    // handles Integer and Double
1079        }
1080        else {
1081            final String className = obj.getClass().getName();
1082            runTimeError(DATA_CONVERSION_ERR, className, Long.TYPE);
1083            return 0;
1084        }
1085    }
1086
1087    /**
1088     * Utility function: used to convert reference to double.
1089     */
1090    public static double referenceToDouble(Object obj) {
1091        if (obj instanceof Number) {
1092            return ((Number) obj).doubleValue();   // handles Integer and Double
1093        }
1094        else {
1095            final String className = obj.getClass().getName();
1096            runTimeError(DATA_CONVERSION_ERR, className, Double.TYPE);
1097            return 0;
1098        }
1099    }
1100
1101    /**
1102     * Utility function: used to convert reference to boolean.
1103     */
1104    public static boolean referenceToBoolean(Object obj) {
1105        if (obj instanceof Boolean) {
1106            return ((Boolean) obj).booleanValue();
1107        }
1108        else {
1109            final String className = obj.getClass().getName();
1110            runTimeError(DATA_CONVERSION_ERR, className, Boolean.TYPE);
1111            return false;
1112        }
1113    }
1114
1115    /**
1116     * Utility function: used to convert reference to String.
1117     */
1118    public static String referenceToString(Object obj, DOM dom) {
1119        if (obj instanceof String) {
1120            return (String) obj;
1121        }
1122        else if (obj instanceof DTMAxisIterator) {
1123            return dom.getStringValueX(((DTMAxisIterator)obj).reset().next());
1124        }
1125        else if (obj instanceof Node) {
1126            return dom.getStringValueX(((Node)obj).node);
1127        }
1128        else if (obj instanceof DOM) {
1129            return ((DOM) obj).getStringValue();
1130        }
1131        else {
1132            final String className = obj.getClass().getName();
1133            runTimeError(DATA_CONVERSION_ERR, className, String.class);
1134            return null;
1135        }
1136    }
1137
1138    /**
1139     * Utility function used to convert a w3c Node into an internal DOM iterator.
1140     */
1141    public static DTMAxisIterator node2Iterator(org.w3c.dom.Node node,
1142        Translet translet, DOM dom)
1143    {
1144        final org.w3c.dom.Node inNode = node;
1145        // Create a dummy NodeList which only contains the given node to make
1146        // use of the nodeList2Iterator() interface.
1147        org.w3c.dom.NodeList nodelist = new org.w3c.dom.NodeList() {
1148            public int getLength() {
1149                return 1;
1150            }
1151
1152            public org.w3c.dom.Node item(int index) {
1153                if (index == 0)
1154                    return inNode;
1155                else
1156                    return null;
1157            }
1158        };
1159
1160        return nodeList2Iterator(nodelist, translet, dom);
1161    }
1162
1163    /**
1164     * In a perfect world, this would be the implementation for
1165     * nodeList2Iterator. In reality, though, this causes a
1166     * ClassCastException in getDTMHandleFromNode because SAXImpl is
1167     * not an instance of DOM2DTM. So we use the more lengthy
1168     * implementation below until this issue has been addressed.
1169     *
1170     * @see org.apache.xml.dtm.ref.DTMManagerDefault#getDTMHandleFromNode
1171     */
1172    private static DTMAxisIterator nodeList2IteratorUsingHandleFromNode(
1173                                        org.w3c.dom.NodeList nodeList,
1174                                        Translet translet, DOM dom)
1175    {
1176        final int n = nodeList.getLength();
1177        final int[] dtmHandles = new int[n];
1178        DTMManager dtmManager = null;
1179        if (dom instanceof MultiDOM)
1180            dtmManager = ((MultiDOM) dom).getDTMManager();
1181        for (int i = 0; i < n; ++i) {
1182            org.w3c.dom.Node node = nodeList.item(i);
1183            int handle;
1184            if (dtmManager != null) {
1185                handle = dtmManager.getDTMHandleFromNode(node);
1186            }
1187            else if (node instanceof DTMNodeProxy
1188                     && ((DTMNodeProxy) node).getDTM() == dom) {
1189                handle = ((DTMNodeProxy) node).getDTMNodeNumber();
1190            }
1191            else {
1192                runTimeError(RUN_TIME_INTERNAL_ERR, "need MultiDOM");
1193                return null;
1194            }
1195            dtmHandles[i] = handle;
1196            System.out.println("Node " + i + " has handle 0x" +
1197                               Integer.toString(handle, 16));
1198        }
1199        return new ArrayNodeListIterator(dtmHandles);
1200    }
1201
1202    /**
1203     * Utility function used to convert a w3c NodeList into a internal
1204     * DOM iterator.
1205     */
1206    public static DTMAxisIterator nodeList2Iterator(
1207                                        org.w3c.dom.NodeList nodeList,
1208                                        Translet translet, DOM dom)
1209    {
1210        // First pass: build w3c DOM for all nodes not proxied from our DOM.
1211        //
1212        // Notice: this looses some (esp. parent) context for these nodes,
1213        // so some way to wrap the original nodes inside a DTMAxisIterator
1214        // might be preferable in the long run.
1215        int n = 0; // allow for change in list length, just in case.
1216        Document doc = null;
1217        DTMManager dtmManager = null;
1218        int[] proxyNodes = new int[nodeList.getLength()];
1219        if (dom instanceof MultiDOM)
1220            dtmManager = ((MultiDOM) dom).getDTMManager();
1221        for (int i = 0; i < nodeList.getLength(); ++i) {
1222            org.w3c.dom.Node node = nodeList.item(i);
1223            if (node instanceof DTMNodeProxy) {
1224                DTMNodeProxy proxy = (DTMNodeProxy)node;
1225                DTM nodeDTM = proxy.getDTM();
1226                int handle = proxy.getDTMNodeNumber();
1227                boolean isOurDOM = (nodeDTM == dom);
1228                if (!isOurDOM && dtmManager != null) {
1229                    try {
1230                        isOurDOM = (nodeDTM == dtmManager.getDTM(handle));
1231                    }
1232                    catch (ArrayIndexOutOfBoundsException e) {
1233                        // invalid node handle, so definitely not our doc
1234                    }
1235                }
1236                if (isOurDOM) {
1237                    proxyNodes[i] = handle;
1238                    ++n;
1239                    continue;
1240                }
1241            }
1242            proxyNodes[i] = DTM.NULL;
1243            int nodeType = node.getNodeType();
1244            if (doc == null) {
1245                if (dom instanceof MultiDOM == false) {
1246                    runTimeError(RUN_TIME_INTERNAL_ERR, "need MultiDOM");
1247                    return null;
1248                }
1249                try {
1250                    AbstractTranslet at = (AbstractTranslet) translet;
1251                    doc = at.newDocument("", "__top__");
1252                }
1253                catch (javax.xml.parsers.ParserConfigurationException e) {
1254                    runTimeError(RUN_TIME_INTERNAL_ERR, e.getMessage());
1255                    return null;
1256                }
1257            }
1258            // Use one dummy element as container for each node of the
1259            // list. That way, it is easier to detect resp. avoid
1260            // funny things which change the number of nodes,
1261            // e.g. auto-concatenation of text nodes.
1262            Element mid;
1263            switch (nodeType) {
1264                case org.w3c.dom.Node.ELEMENT_NODE:
1265                case org.w3c.dom.Node.TEXT_NODE:
1266                case org.w3c.dom.Node.CDATA_SECTION_NODE:
1267                case org.w3c.dom.Node.COMMENT_NODE:
1268                case org.w3c.dom.Node.ENTITY_REFERENCE_NODE:
1269                case org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE:
1270                    mid = doc.createElementNS(null, "__dummy__");
1271                    mid.appendChild(doc.importNode(node, true));
1272                    doc.getDocumentElement().appendChild(mid);
1273                    ++n;
1274                    break;
1275                case org.w3c.dom.Node.ATTRIBUTE_NODE:
1276                    // The mid element also serves as a container for
1277                    // attributes, avoiding problems with conflicting
1278                    // attributes or node order.
1279                    mid = doc.createElementNS(null, "__dummy__");
1280                    mid.setAttributeNodeNS((Attr)doc.importNode(node, true));
1281                    doc.getDocumentElement().appendChild(mid);
1282                    ++n;
1283                    break;
1284                default:
1285                    // Better play it safe for all types we aren't sure we know
1286                    // how to deal with.
1287                    runTimeError(RUN_TIME_INTERNAL_ERR,
1288                                 "Don't know how to convert node type "
1289                                 + nodeType);
1290            }
1291        }
1292
1293        // w3cDOM -> DTM -> DOMImpl
1294        DTMAxisIterator iter = null, childIter = null, attrIter = null;
1295        if (doc != null) {
1296            final MultiDOM multiDOM = (MultiDOM) dom;
1297            DOM idom = (DOM)dtmManager.getDTM(new DOMSource(doc), false,
1298                                              null, true, false);
1299            // Create DOMAdapter and register with MultiDOM
1300            DOMAdapter domAdapter = new DOMAdapter(idom,
1301                translet.getNamesArray(),
1302                translet.getUrisArray(),
1303                translet.getTypesArray(),
1304                translet.getNamespaceArray());
1305            multiDOM.addDOMAdapter(domAdapter);
1306
1307            DTMAxisIterator iter1 = idom.getAxisIterator(Axis.CHILD);
1308            DTMAxisIterator iter2 = idom.getAxisIterator(Axis.CHILD);
1309            iter = new AbsoluteIterator(
1310                new StepIterator(iter1, iter2));
1311
1312            iter.setStartNode(DTMDefaultBase.ROOTNODE);
1313
1314            childIter = idom.getAxisIterator(Axis.CHILD);
1315            attrIter = idom.getAxisIterator(Axis.ATTRIBUTE);
1316        }
1317
1318        // Second pass: find DTM handles for every node in the list.
1319        int[] dtmHandles = new int[n];
1320        n = 0;
1321        for (int i = 0; i < nodeList.getLength(); ++i) {
1322            if (proxyNodes[i] != DTM.NULL) {
1323                dtmHandles[n++] = proxyNodes[i];
1324                continue;
1325            }
1326            org.w3c.dom.Node node = nodeList.item(i);
1327            DTMAxisIterator iter3 = null;
1328            int nodeType = node.getNodeType();
1329            switch (nodeType) {
1330                case org.w3c.dom.Node.ELEMENT_NODE:
1331                case org.w3c.dom.Node.TEXT_NODE:
1332                case org.w3c.dom.Node.CDATA_SECTION_NODE:
1333                case org.w3c.dom.Node.COMMENT_NODE:
1334                case org.w3c.dom.Node.ENTITY_REFERENCE_NODE:
1335                case org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE:
1336                    iter3 = childIter;
1337                    break;
1338                case org.w3c.dom.Node.ATTRIBUTE_NODE:
1339                    iter3 = attrIter;
1340                    break;
1341                default:
1342                    // Should not happen, as first run should have got all these
1343                    throw new InternalRuntimeError("Mismatched cases");
1344            }
1345            if (iter3 != null) {
1346                iter3.setStartNode(iter.next());
1347                dtmHandles[n] = iter3.next();
1348                // For now, play it self and perform extra checks:
1349                if (dtmHandles[n] == DTMAxisIterator.END)
1350                    throw new InternalRuntimeError("Expected element missing at " + i);
1351                if (iter3.next() != DTMAxisIterator.END)
1352                    throw new InternalRuntimeError("Too many elements at " + i);
1353                ++n;
1354            }
1355        }
1356        if (n != dtmHandles.length)
1357            throw new InternalRuntimeError("Nodes lost in second pass");
1358
1359        return new ArrayNodeListIterator(dtmHandles);
1360    }
1361
1362    /**
1363     * Utility function used to convert references to DOMs.
1364     */
1365    public static DOM referenceToResultTree(Object obj) {
1366        try {
1367            return ((DOM) obj);
1368        }
1369        catch (IllegalArgumentException e) {
1370            final String className = obj.getClass().getName();
1371            runTimeError(DATA_CONVERSION_ERR, "reference", className);
1372            return null;
1373        }
1374    }
1375
1376    /**
1377     * Utility function: used with nth position filters to convert a sequence
1378     * of nodes to just one single node (the one at position n).
1379     */
1380    public static DTMAxisIterator getSingleNode(DTMAxisIterator iterator) {
1381        int node = iterator.next();
1382        return(new SingletonIterator(node));
1383    }
1384
1385    /**
1386     * Utility function: used in xsl:copy.
1387     */
1388    private static char[] _characterArray = new char[32];
1389
1390    public static void copy(Object obj,
1391                            SerializationHandler handler,
1392                            int node,
1393                            DOM dom) {
1394        try {
1395            if (obj instanceof DTMAxisIterator)
1396      {
1397                DTMAxisIterator iter = (DTMAxisIterator) obj;
1398                dom.copy(iter.reset(), handler);
1399            }
1400            else if (obj instanceof Node) {
1401                dom.copy(((Node) obj).node, handler);
1402            }
1403            else if (obj instanceof DOM) {
1404                //((DOM)obj).copy(((com.sun.org.apache.xml.internal.dtm.ref.DTMDefaultBase)((DOMAdapter)obj).getDOMImpl()).getDocument(), handler);
1405                DOM newDom = (DOM)obj;
1406                newDom.copy(newDom.getDocument(), handler);
1407            }
1408            else {
1409                String string = obj.toString();         // or call stringF()
1410                final int length = string.length();
1411                if (length > _characterArray.length)
1412                    _characterArray = new char[length];
1413                string.getChars(0, length, _characterArray, 0);
1414                handler.characters(_characterArray, 0, length);
1415            }
1416        }
1417        catch (SAXException e) {
1418            runTimeError(RUN_TIME_COPY_ERR);
1419        }
1420    }
1421
1422    /**
1423     * Utility function to check if xsl:attribute has a valid qname
1424     * This method should only be invoked if the name attribute is an AVT
1425     */
1426    public static void checkAttribQName(String name) {
1427        final int firstOccur = name.indexOf(":");
1428        final int lastOccur = name.lastIndexOf(":");
1429        final String localName = name.substring(lastOccur + 1);
1430
1431        if (firstOccur > 0) {
1432            final String newPrefix = name.substring(0, firstOccur);
1433
1434            if (firstOccur != lastOccur) {
1435               final String oriPrefix = name.substring(firstOccur+1, lastOccur);
1436                if (!XML11Char.isXML11ValidNCName(oriPrefix)) {
1437                    // even though the orignal prefix is ignored, it should still get checked for valid NCName
1438                    runTimeError(INVALID_QNAME_ERR,oriPrefix+":"+localName);
1439                }
1440            }
1441
1442            // prefix must be a valid NCName
1443            if (!XML11Char.isXML11ValidNCName(newPrefix)) {
1444                runTimeError(INVALID_QNAME_ERR,newPrefix+":"+localName);
1445            }
1446        }
1447
1448        // local name must be a valid NCName and must not be XMLNS
1449        if ((!XML11Char.isXML11ValidNCName(localName))||(localName.equals(Constants.XMLNS_PREFIX))) {
1450            runTimeError(INVALID_QNAME_ERR,localName);
1451        }
1452    }
1453
1454    /**
1455     * Utility function to check if a name is a valid ncname
1456     * This method should only be invoked if the attribute value is an AVT
1457     */
1458    public static void checkNCName(String name) {
1459        if (!XML11Char.isXML11ValidNCName(name)) {
1460            runTimeError(INVALID_NCNAME_ERR,name);
1461        }
1462    }
1463
1464    /**
1465     * Utility function to check if a name is a valid qname
1466     * This method should only be invoked if the attribute value is an AVT
1467     */
1468    public static void checkQName(String name) {
1469        if (!XML11Char.isXML11ValidQName(name)) {
1470            runTimeError(INVALID_QNAME_ERR,name);
1471        }
1472    }
1473
1474    /**
1475     * Utility function for the implementation of xsl:element.
1476     */
1477    public static String startXslElement(String qname, String namespace,
1478        SerializationHandler handler, DOM dom, int node)
1479    {
1480        try {
1481            // Get prefix from qname
1482            String prefix;
1483            final int index = qname.indexOf(':');
1484
1485            if (index > 0) {
1486                prefix = qname.substring(0, index);
1487
1488                // Handle case when prefix is not known at compile time
1489                if (namespace == null || namespace.length() == 0) {
1490                    try {
1491                        // not sure if this line of code ever works
1492                        namespace = dom.lookupNamespace(node, prefix);
1493                    }
1494                    catch(RuntimeException e) {
1495                        handler.flushPending();  // need to flush or else can't get namespacemappings
1496                        NamespaceMappings nm = handler.getNamespaceMappings();
1497                        namespace = nm.lookupNamespace(prefix);
1498                        if (namespace == null) {
1499                            runTimeError(NAMESPACE_PREFIX_ERR,prefix);
1500                        }
1501                    }
1502                }
1503
1504                handler.startElement(namespace, qname.substring(index+1),
1505                                         qname);
1506                handler.namespaceAfterStartElement(prefix, namespace);
1507            }
1508            else {
1509                // Need to generate a prefix?
1510                if (namespace != null && namespace.length() > 0) {
1511                    prefix = generatePrefix();
1512                    qname = prefix + ':' + qname;
1513                    handler.startElement(namespace, qname, qname);
1514                    handler.namespaceAfterStartElement(prefix, namespace);
1515                }
1516                else {
1517                    handler.startElement(null, null, qname);
1518                }
1519            }
1520        }
1521        catch (SAXException e) {
1522            throw new RuntimeException(e.getMessage());
1523        }
1524
1525        return qname;
1526    }
1527
1528    /**
1529     * This function is used in the execution of xsl:element
1530     */
1531    public static String getPrefix(String qname) {
1532        final int index = qname.indexOf(':');
1533        return (index > 0) ? qname.substring(0, index) : null;
1534    }
1535
1536    /**
1537     * These functions are used in the execution of xsl:element to generate
1538     * and reset namespace prefix index local to current transformation process
1539     */
1540    public static String generatePrefix() {
1541        return ("ns" + threadLocalPrefixIndex.get().getAndIncrement());
1542    }
1543
1544    public static void resetPrefixIndex() {
1545        threadLocalPrefixIndex.get().set(0);
1546    }
1547
1548    private static final ThreadLocal<AtomicInteger> threadLocalPrefixIndex =
1549        new ThreadLocal<AtomicInteger>() {
1550            @Override
1551            protected AtomicInteger initialValue() {
1552                return new AtomicInteger();
1553            }
1554        };
1555
1556    public static final String RUN_TIME_INTERNAL_ERR =
1557                                           "RUN_TIME_INTERNAL_ERR";
1558    public static final String RUN_TIME_COPY_ERR =
1559                                           "RUN_TIME_COPY_ERR";
1560    public static final String DATA_CONVERSION_ERR =
1561                                           "DATA_CONVERSION_ERR";
1562    public static final String EXTERNAL_FUNC_ERR =
1563                                           "EXTERNAL_FUNC_ERR";
1564    public static final String EQUALITY_EXPR_ERR =
1565                                           "EQUALITY_EXPR_ERR";
1566    public static final String INVALID_ARGUMENT_ERR =
1567                                           "INVALID_ARGUMENT_ERR";
1568    public static final String FORMAT_NUMBER_ERR =
1569                                           "FORMAT_NUMBER_ERR";
1570    public static final String ITERATOR_CLONE_ERR =
1571                                           "ITERATOR_CLONE_ERR";
1572    public static final String AXIS_SUPPORT_ERR =
1573                                           "AXIS_SUPPORT_ERR";
1574    public static final String TYPED_AXIS_SUPPORT_ERR =
1575                                           "TYPED_AXIS_SUPPORT_ERR";
1576    public static final String STRAY_ATTRIBUTE_ERR =
1577                                           "STRAY_ATTRIBUTE_ERR";
1578    public static final String STRAY_NAMESPACE_ERR =
1579                                           "STRAY_NAMESPACE_ERR";
1580    public static final String NAMESPACE_PREFIX_ERR =
1581                                           "NAMESPACE_PREFIX_ERR";
1582    public static final String DOM_ADAPTER_INIT_ERR =
1583                                           "DOM_ADAPTER_INIT_ERR";
1584    public static final String PARSER_DTD_SUPPORT_ERR =
1585                                           "PARSER_DTD_SUPPORT_ERR";
1586    public static final String NAMESPACES_SUPPORT_ERR =
1587                                           "NAMESPACES_SUPPORT_ERR";
1588    public static final String CANT_RESOLVE_RELATIVE_URI_ERR =
1589                                           "CANT_RESOLVE_RELATIVE_URI_ERR";
1590    public static final String UNSUPPORTED_XSL_ERR =
1591                                           "UNSUPPORTED_XSL_ERR";
1592    public static final String UNSUPPORTED_EXT_ERR =
1593                                           "UNSUPPORTED_EXT_ERR";
1594    public static final String UNKNOWN_TRANSLET_VERSION_ERR =
1595                                           "UNKNOWN_TRANSLET_VERSION_ERR";
1596    public static final String INVALID_QNAME_ERR = "INVALID_QNAME_ERR";
1597    public static final String INVALID_NCNAME_ERR = "INVALID_NCNAME_ERR";
1598    public static final String UNALLOWED_EXTENSION_FUNCTION_ERR = "UNALLOWED_EXTENSION_FUNCTION_ERR";
1599    public static final String UNALLOWED_EXTENSION_ELEMENT_ERR = "UNALLOWED_EXTENSION_ELEMENT_ERR";
1600
1601    // All error messages are localized and are stored in resource bundles.
1602    private static ResourceBundle m_bundle;
1603
1604    public final static String ERROR_MESSAGES_KEY = "error-messages";
1605
1606    static {
1607        String resource = "com.sun.org.apache.xalan.internal.xsltc.runtime.ErrorMessages";
1608        m_bundle = SecuritySupport.getResourceBundle(resource);
1609    }
1610
1611    /**
1612     * Print a run-time error message.
1613     */
1614    public static void runTimeError(String code) {
1615        throw new RuntimeException(m_bundle.getString(code));
1616    }
1617
1618    public static void runTimeError(String code, Object[] args) {
1619        final String message = MessageFormat.format(m_bundle.getString(code),
1620                                                    args);
1621        throw new RuntimeException(message);
1622    }
1623
1624    public static void runTimeError(String code, Object arg0) {
1625        runTimeError(code, new Object[]{ arg0 } );
1626    }
1627
1628    public static void runTimeError(String code, Object arg0, Object arg1) {
1629        runTimeError(code, new Object[]{ arg0, arg1 } );
1630    }
1631
1632    public static void consoleOutput(String msg) {
1633        System.out.println(msg);
1634    }
1635
1636    /**
1637     * Replace a certain character in a string with a new substring.
1638     */
1639    public static String replace(String base, char ch, String str) {
1640        return (base.indexOf(ch) < 0) ? base :
1641            replace(base, String.valueOf(ch), new String[] { str });
1642    }
1643
1644    public static String replace(String base, String delim, String[] str) {
1645        final int len = base.length();
1646        final StringBuilder result = threadLocalStringBuilder.get();
1647        result.setLength(0);
1648
1649        for (int i = 0; i < len; i++) {
1650            final char ch = base.charAt(i);
1651            final int k = delim.indexOf(ch);
1652
1653            if (k >= 0) {
1654                result.append(str[k]);
1655            }
1656            else {
1657                result.append(ch);
1658            }
1659        }
1660        return result.toString();
1661    }
1662
1663
1664    /**
1665     * Utility method to allow setting parameters of the form
1666     * {namespaceuri}localName
1667     * which get mapped to an instance variable in the class
1668     * Hence  a parameter of the form "{http://foo.bar}xyz"
1669     * will be replaced with the corresponding values
1670     * by the BasisLibrary's utility method mapQNametoJavaName
1671     * and thus get mapped to legal java variable names
1672     */
1673    public static String mapQNameToJavaName (String base ) {
1674       return replace(base, ".-:/{}?#%*",
1675                      new String[] { "$dot$", "$dash$" ,"$colon$", "$slash$",
1676                                     "","$colon$","$ques$","$hash$","$per$",
1677                                     "$aster$"});
1678
1679    }
1680
1681    /**
1682     *  Utility method to calculate string-length as a number of code points,
1683     *  to avoid possible errors with string that contains
1684     *  complementary characters
1685     */
1686    public static int getStringLength(String str) {
1687        return str.codePointCount(0,str.length());
1688    }
1689
1690    //-- End utility functions
1691}
1692