1 /**
  2  * ====================================================================
  3  * About
  4  * ====================================================================
  5  * Sarissa cross browser XML library - IE XPath Emulation 
  6  * @version ${project.version}
  7  * @author: Copyright 2004-2007 Emmanouil Batsis, mailto: mbatsis at users full stop sourceforge full stop net
  8  *
  9  * This script emulates Internet Explorer's selectNodes and selectSingleNode
 10  * for Mozilla. Associating namespace prefixes with URIs for your XPath queries
 11  * is easy with IE's setProperty. 
 12  * USers may also map a namespace prefix to a default (unprefixed) namespace in the
 13  * source document with Sarissa.setXpathNamespaces
 14  *
 15  * ====================================================================
 16  * Licence
 17  * ====================================================================
 18  * Sarissa is free software distributed under the GNU GPL version 2 (see <a href="gpl.txt">gpl.txt</a>) or higher, 
 19  * GNU LGPL version 2.1 (see <a href="lgpl.txt">lgpl.txt</a>) or higher and Apache Software License 2.0 or higher 
 20  * (see <a href="asl.txt">asl.txt</a>). This means you can choose one of the three and use that if you like. If 
 21  * you make modifications under the ASL, i would appreciate it if you submitted those.
 22  * In case your copy of Sarissa does not include the license texts, you may find
 23  * them online in various formats at <a href="http://www.gnu.org">http://www.gnu.org</a> and 
 24  * <a href="http://www.apache.org">http://www.apache.org</a>.
 25  *
 26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 27  * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 28  * WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE 
 29  * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
 30  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 31  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
 32  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 33  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 34  */
 35 if(Sarissa._SARISSA_HAS_DOM_FEATURE && document.implementation.hasFeature("XPath", "3.0")){
 36     /**
 37      * <p>SarissaNodeList behaves as a NodeList but is only used as a result to <code>selectNodes</code>,
 38      * so it also has some properties IEs proprietery object features.</p>
 39      * @private
 40      * @constructor
 41      * @argument i the (initial) list size
 42      */
 43     SarissaNodeList = function (i){
 44         this.length = i;
 45     };
 46     /** 
 47      * <p>Set an Array as the prototype object</p> 
 48      * @private
 49      */
 50     SarissaNodeList.prototype = [];
 51     /** 
 52      * <p>Inherit the Array constructor </p> 
 53      * @private
 54      */
 55     SarissaNodeList.prototype.constructor = Array;
 56     /**
 57      * <p>Returns the node at the specified index or null if the given index
 58      * is greater than the list size or less than zero </p>
 59      * <p><b>Note</b> that in ECMAScript you can also use the square-bracket
 60      * array notation instead of calling <code>item</code>
 61      * @argument i the index of the member to return
 62      * @returns the member corresponding to the given index
 63      * @private
 64      */
 65     SarissaNodeList.prototype.item = function(i) {
 66         return (i < 0 || i >= this.length)?null:this[i];
 67     };
 68     /**
 69      * <p>Emulate IE's expr property
 70      * (Here the SarissaNodeList object is given as the result of selectNodes).</p>
 71      * @returns the XPath expression passed to selectNodes that resulted in
 72      *          this SarissaNodeList
 73      * @private
 74      */
 75     SarissaNodeList.prototype.expr = "";
 76     /** dummy, used to accept IE's stuff without throwing errors */
 77     if(window.XMLDocument && (!XMLDocument.prototype.setProperty)){
 78         XMLDocument.prototype.setProperty  = function(x,y){};
 79     }
 80     /**
 81     * <p>Programmatically control namespace URI/prefix mappings for XPath
 82     * queries.</p>
 83     * <p>This method comes especially handy when used to apply XPath queries
 84     * on XML documents with a default namespace, as there is no other way
 85     * of mapping that to a prefix.</p>
 86     * <p>Using no namespace prefix in DOM Level 3 XPath queries, implies you
 87     * are looking for elements in the null namespace. If you need to look
 88     * for nodes in the default namespace, you need to map a prefix to it
 89     * first like:</p>
 90     * <pre>Sarissa.setXpathNamespaces(oDoc, "xmlns:myprefix'http://mynsURI'");</pre>
 91     * <p><b>Note 1 </b>: Use this method only if the source document features
 92     * a default namespace (without a prefix) or contains namespace declarations with
 93     * a scope that does not cover the entire document (i.e. declared but not within the 
 94     * root element node). Otherwise just use IE's setProperty. You will need to map that
 95     * namespace to a prefix for queries to work.
 96     * Moz/FF will resolve non-default namespaces automatically if those are declared 
 97     *  in the root element. </p>
 98     * <p><b>Note 2 </b>: This method calls IE's setProperty method to set the
 99     * appropriate namespace-prefix mappings, so you dont have to do that.</p>
100     * @param oDoc The target XMLDocument to set the namespace mappings for.
101     * @param sNsSet A whilespace-seperated list of namespace declarations as
102     *            those would appear in an XML document. E.g.:
103     *            <code>"xmlns:xhtml='http://www.w3.org/1999/xhtml'
104     * xmlns:'http://www.w3.org/1999/XSL/Transform'"</code>
105     * @throws An error if the format of the given namespace declarations is bad.
106     */
107     Sarissa.setXpathNamespaces = function(oDoc, sNsSet) {
108         //oDoc._sarissa_setXpathNamespaces(sNsSet);
109         oDoc._sarissa_useCustomResolver = true;
110         var namespaces = sNsSet.indexOf(" ")>-1?sNsSet.split(" "):[sNsSet];
111         oDoc._sarissa_xpathNamespaces = [];
112         for(var i=0;i < namespaces.length;i++){
113             var ns = namespaces[i];
114             var colonPos = ns.indexOf(":");
115             var assignPos = ns.indexOf("=");
116             if(colonPos > 0 && assignPos > colonPos+1){
117                 var prefix = ns.substring(colonPos+1, assignPos);
118                 var uri = ns.substring(assignPos+2, ns.length-1);
119                 oDoc._sarissa_xpathNamespaces[prefix] = uri;
120             }else{
121                 throw "Bad format on namespace declaration(s) given";
122             }
123         }
124     };
125     /**
126     * @private Flag to control whether a custom namespace resolver should
127     *          be used, set to true by Sarissa.setXpathNamespaces
128     */
129     XMLDocument.prototype._sarissa_useCustomResolver = false;
130     /** @private */
131     XMLDocument.prototype._sarissa_xpathNamespaces = [];
132     /**
133     * <p>Extends the XMLDocument to emulate IE's selectNodes.</p>
134     * @argument sExpr the XPath expression to use
135     * @argument contextNode this is for internal use only by the same
136     *           method when called on Elements
137     * @returns the result of the XPath search as a SarissaNodeList
138     * @throws An error if no namespace URI is found for the given prefix.
139     */
140     XMLDocument.prototype.selectNodes = function(sExpr, contextNode, returnSingle){
141         var nsDoc = this;
142         var nsresolver;
143         if(this._sarissa_useCustomResolver){
144             nsresolver = function(prefix){
145                 var s = nsDoc._sarissa_xpathNamespaces[prefix];
146                 if(s){
147                     return s;
148                 }
149                 else {
150                     throw "No namespace URI found for prefix: '" + prefix+"'";
151                 }
152             };
153         }
154         else{
155             nsresolver = this.createNSResolver(this.documentElement);
156         }
157         var result = null;
158         if(!returnSingle){
159             var oResult = this.evaluate(sExpr,
160                 (contextNode?contextNode:this),
161                 nsresolver,
162                 XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
163             var nodeList = new SarissaNodeList(oResult.snapshotLength);
164             nodeList.expr = sExpr;
165             for(var i=0;i<nodeList.length;i++){
166                 nodeList[i] = oResult.snapshotItem(i);
167             }
168             result = nodeList;
169         }
170         else {
171             result = this.evaluate(sExpr,
172                 (contextNode?contextNode:this),
173                 nsresolver,
174                 XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
175         }
176         return result;      
177     };
178     /**
179     * <p>Extends the Element to emulate IE's selectNodes</p>
180     * @argument sExpr the XPath expression to use
181     * @returns the result of the XPath search as an (Sarissa)NodeList
182     * @throws An
183     *             error if invoked on an HTML Element as this is only be
184     *             available to XML Elements.
185     */
186     Element.prototype.selectNodes = function(sExpr){
187         var doc = this.ownerDocument;
188         if(doc.selectNodes){
189             return doc.selectNodes(sExpr, this);
190         }
191         else{
192             throw "Method selectNodes is only supported by XML Elements";
193         }
194     };
195     /**
196     * <p>Extends the XMLDocument to emulate IE's selectSingleNode.</p>
197     * @argument sExpr the XPath expression to use
198     * @argument contextNode this is for internal use only by the same
199     *           method when called on Elements
200     * @returns the result of the XPath search as an (Sarissa)NodeList
201     */
202     XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode){
203         var ctx = contextNode?contextNode:null;
204         return this.selectNodes(sExpr, ctx, true);
205     };
206     /**
207     * <p>Extends the Element to emulate IE's selectSingleNode.</p>
208     * @argument sExpr the XPath expression to use
209     * @returns the result of the XPath search as an (Sarissa)NodeList
210     * @throws An error if invoked on an HTML Element as this is only be
211     *             available to XML Elements.
212     */
213     Element.prototype.selectSingleNode = function(sExpr){
214         var doc = this.ownerDocument;
215         if(doc.selectSingleNode){
216             return doc.selectSingleNode(sExpr, this);
217         }
218         else{
219             throw "Method selectNodes is only supported by XML Elements";
220         }
221     };
222     Sarissa.IS_ENABLED_SELECT_NODES = true;
223 }