UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,106 lines (940 loc) 478 kB
/* * Copyright (c) Microsoft. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * datajs.js */ (function (window, undefined) { var datajs = window.datajs || {}; var odata = window.OData || {}; // AMD support if (typeof define === 'function' && define.amd) { define('datajs', datajs); define('OData', odata); // ##### BEGIN: MODIFIED BY SAP define('sap/ui/thirdparty/datajs', odata); // ##### END: MODIFIED BY SAP } else { window.datajs = datajs; window.OData = odata; } var activeXObject = function (progId) { /// <summary>Creates a new ActiveXObject from the given progId.</summary> /// <param name="progId" type="String" mayBeNull="false" optional="false"> /// ProgId string of the desired ActiveXObject. /// </param> /// <remarks> /// This function throws whatever exception might occur during the creation /// of the ActiveXObject. /// </remarks> /// <returns type="Object"> /// The ActiveXObject instance. Null if ActiveX is not supported by the /// browser. /// </returns> if (window.ActiveXObject) { return new window.ActiveXObject(progId); } return null; }; var assigned = function (value) { /// <summary>Checks whether the specified value is different from null and undefined.</summary> /// <param name="value" mayBeNull="true" optional="true">Value to check.</param> /// <returns type="Boolean">true if the value is assigned; false otherwise.</returns> return value !== null && value !== undefined; }; var contains = function (arr, item) { /// <summary>Checks whether the specified item is in the array.</summary> /// <param name="arr" type="Array" optional="false" mayBeNull="false">Array to check in.</param> /// <param name="item">Item to look for.</param> /// <returns type="Boolean">true if the item is contained, false otherwise.</returns> var i, len; for (i = 0, len = arr.length; i < len; i++) { if (arr[i] === item) { return true; } } return false; }; var defined = function (a, b) { /// <summary>Given two values, picks the first one that is not undefined.</summary> /// <param name="a">First value.</param> /// <param name="b">Second value.</param> /// <returns>a if it's a defined value; else b.</returns> return (a !== undefined) ? a : b; }; var delay = function (callback) { /// <summary>Delays the invocation of the specified function until execution unwinds.</summary> /// <param name="callback" type="Function">Callback function.</param> if (arguments.length === 1) { window.setTimeout(callback, 0); return; } var args = Array.prototype.slice.call(arguments, 1); window.setTimeout(function () { callback.apply(this, args); }, 0); }; var extend = function (target, values) { /// <summary>Extends the target with the specified values.</summary> /// <param name="target" type="Object">Object to add properties to.</param> /// <param name="values" type="Object">Object with properties to add into target.</param> /// <returns type="Object">The target object.</returns> for (var name in values) { target[name] = values[name]; } return target; }; var find = function (arr, callback) { /// <summary>Returns the first item in the array that makes the callback function true.</summary> /// <param name="arr" type="Array" optional="false" mayBeNull="true">Array to check in.</param> /// <param name="callback" type="Function">Callback function to invoke once per item in the array.</param> /// <returns>The first item that makes the callback return true; null otherwise or if the array is null.</returns> if (arr) { var i, len; for (i = 0, len = arr.length; i < len; i++) { if (callback(arr[i])) { return arr[i]; } } } return null; }; var isArray = function (value) { /// <summary>Checks whether the specified value is an array object.</summary> /// <param name="value">Value to check.</param> /// <returns type="Boolean">true if the value is an array object; false otherwise.</returns> return Object.prototype.toString.call(value) === "[object Array]"; }; var isDate = function (value) { /// <summary>Checks whether the specified value is a Date object.</summary> /// <param name="value">Value to check.</param> /// <returns type="Boolean">true if the value is a Date object; false otherwise.</returns> return Object.prototype.toString.call(value) === "[object Date]"; }; var isObject = function (value) { /// <summary>Tests whether a value is an object.</summary> /// <param name="value">Value to test.</param> /// <remarks> /// Per javascript rules, null and array values are objects and will cause this function to return true. /// </remarks> /// <returns type="Boolean">True is the value is an object; false otherwise.</returns> return typeof value === "object"; }; var parseInt10 = function (value) { /// <summary>Parses a value in base 10.</summary> /// <param name="value" type="String">String value to parse.</param> /// <returns type="Number">The parsed value, NaN if not a valid value.</returns> return parseInt(value, 10); }; var renameProperty = function (obj, oldName, newName) { /// <summary>Renames a property in an object.</summary> /// <param name="obj" type="Object">Object in which the property will be renamed.</param> /// <param name="oldName" type="String">Name of the property that will be renamed.</param> /// <param name="newName" type="String">New name of the property.</param> /// <remarks> /// This function will not do anything if the object doesn't own a property with the specified old name. /// </remarks> if (obj.hasOwnProperty(oldName)) { obj[newName] = obj[oldName]; delete obj[oldName]; } }; var throwErrorCallback = function (error) { /// <summary>Default error handler.</summary> /// <param name="error" type="Object">Error to handle.</param> throw error; }; var trimString = function (str) { /// <summary>Removes leading and trailing whitespaces from a string.</summary> /// <param name="str" type="String" optional="false" mayBeNull="false">String to trim</param> /// <returns type="String">The string with no leading or trailing whitespace.</returns> if (str.trim) { return str.trim(); } return str.replace(/^\s+|\s+$/g, ''); }; var undefinedDefault = function (value, defaultValue) { /// <summary>Returns a default value in place of undefined.</summary> /// <param name="value" mayBeNull="true" optional="true">Value to check.</param> /// <param name="defaultValue">Value to return if value is undefined.</param> /// <returns>value if it's defined; defaultValue otherwise.</returns> /// <remarks> /// This should only be used for cases where falsy values are valid; /// otherwise the pattern should be 'x = (value) ? value : defaultValue;'. /// </remarks> return (value !== undefined) ? value : defaultValue; }; // Regular expression that splits a uri into its components: // 0 - is the matched string. // 1 - is the scheme. // 2 - is the authority. // 3 - is the path. // 4 - is the query. // 5 - is the fragment. var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/; var uriPartNames = ["scheme", "authority", "path", "query", "fragment"]; var getURIInfo = function (uri) { /// <summary>Gets information about the components of the specified URI.</summary> /// <param name="uri" type="String">URI to get information from.</param> /// <returns type="Object"> /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available. /// </returns> var result = { isAbsolute: false }; if (uri) { var matches = uriRegEx.exec(uri); if (matches) { var i, len; for (i = 0, len = uriPartNames.length; i < len; i++) { if (matches[i + 1]) { result[uriPartNames[i]] = matches[i + 1]; } } } if (result.scheme) { result.isAbsolute = true; } } return result; }; var getURIFromInfo = function (uriInfo) { /// <summary>Builds a URI string from its components.</summary> /// <param name="uriInfo" type="Object"> An object with uri parts (scheme, authority, etc.).</param> /// <returns type="String">URI string.</returns> return "".concat( uriInfo.scheme || "", uriInfo.authority || "", uriInfo.path || "", uriInfo.query || "", uriInfo.fragment || ""); }; // Regular expression that splits a uri authority into its subcomponents: // 0 - is the matched string. // 1 - is the userinfo subcomponent. // 2 - is the host subcomponent. // 3 - is the port component. var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/; // Regular expression that matches percentage enconded octects (i.e %20 or %3A); var pctEncodingRegEx = /%[0-9A-F]{2}/ig; var normalizeURICase = function (uri) { /// <summary>Normalizes the casing of a URI.</summary> /// <param name="uri" type="String">URI to normalize, absolute or relative.</param> /// <returns type="String">The URI normalized to lower case.</returns> var uriInfo = getURIInfo(uri); var scheme = uriInfo.scheme; var authority = uriInfo.authority; if (scheme) { uriInfo.scheme = scheme.toLowerCase(); if (authority) { var matches = uriAuthorityRegEx.exec(authority); if (matches) { uriInfo.authority = "//" + (matches[1] ? matches[1] + "@" : "") + (matches[2].toLowerCase()) + (matches[3] ? ":" + matches[3] : ""); } } } uri = getURIFromInfo(uriInfo); return uri.replace(pctEncodingRegEx, function (str) { return str.toLowerCase(); }); }; var normalizeURI = function (uri, base) { /// <summary>Normalizes a possibly relative URI with a base URI.</summary> /// <param name="uri" type="String">URI to normalize, absolute or relative.</param> /// <param name="base" type="String" mayBeNull="true">Base URI to compose with.</param> /// <returns type="String">The composed URI if relative; the original one if absolute.</returns> if (!base) { return uri; } var uriInfo = getURIInfo(uri); if (uriInfo.isAbsolute) { return uri; } var baseInfo = getURIInfo(base); var normInfo = {}; var path; if (uriInfo.authority) { normInfo.authority = uriInfo.authority; path = uriInfo.path; normInfo.query = uriInfo.query; } else { if (!uriInfo.path) { path = baseInfo.path; normInfo.query = uriInfo.query || baseInfo.query; } else { if (uriInfo.path.charAt(0) === '/') { path = uriInfo.path; } else { path = mergeUriPathWithBase(uriInfo.path, baseInfo.path); } normInfo.query = uriInfo.query; } normInfo.authority = baseInfo.authority; } normInfo.path = removeDotsFromPath(path); normInfo.scheme = baseInfo.scheme; normInfo.fragment = uriInfo.fragment; return getURIFromInfo(normInfo); }; var mergeUriPathWithBase = function (uriPath, basePath) { /// <summary>Merges the path of a relative URI and a base URI.</summary> /// <param name="uriPath" type="String>Relative URI path.</param> /// <param name="basePath" type="String">Base URI path.</param> /// <returns type="String">A string with the merged path.</returns> var path = "/"; var end; if (basePath) { end = basePath.lastIndexOf("/"); path = basePath.substring(0, end); if (path.charAt(path.length - 1) !== "/") { path = path + "/"; } } return path + uriPath; }; var removeDotsFromPath = function (path) { /// <summary>Removes the special folders . and .. from a URI's path.</summary> /// <param name="path" type="string">URI path component.</param> /// <returns type="String">Path without any . and .. folders.</returns> var result = ""; var segment = ""; var end; while (path) { if (path.indexOf("..") === 0 || path.indexOf(".") === 0) { path = path.replace(/^\.\.?\/?/g, ""); } else if (path.indexOf("/..") === 0) { path = path.replace(/^\/\..\/?/g, "/"); end = result.lastIndexOf("/"); if (end === -1) { result = ""; } else { result = result.substring(0, end); } } else if (path.indexOf("/.") === 0) { path = path.replace(/^\/\.\/?/g, "/"); } else { segment = path; end = path.indexOf("/", 1); if (end !== -1) { segment = path.substring(0, end); } result = result + segment; path = path.replace(segment, ""); } } return result; }; // URI prefixes to generate smaller code. var http = "http://"; var w3org = http + "www.w3.org/"; // http://www.w3.org/ var xhtmlNS = w3org + "1999/xhtml"; // http://www.w3.org/1999/xhtml var xmlnsNS = w3org + "2000/xmlns/"; // http://www.w3.org/2000/xmlns/ var xmlNS = w3org + "XML/1998/namespace"; // http://www.w3.org/XML/1998/namespace var mozillaParserErroNS = http + "www.mozilla.org/newlayout/xml/parsererror.xml"; var hasLeadingOrTrailingWhitespace = function (text) { /// <summary>Checks whether the specified string has leading or trailing spaces.</summary> /// <param name="text" type="String">String to check.</param> /// <returns type="Boolean">true if text has any leading or trailing whitespace; false otherwise.</returns> var re = /(^\s)|(\s$)/; return re.test(text); }; var isWhitespace = function (text) { /// <summary>Determines whether the specified text is empty or whitespace.</summary> /// <param name="text" type="String">Value to inspect.</param> /// <returns type="Boolean">true if the text value is empty or all whitespace; false otherwise.</returns> var ws = /^\s*$/; return text === null || ws.test(text); }; var isWhitespacePreserveContext = function (domElement) { /// <summary>Determines whether the specified element has xml:space='preserve' applied.</summary> /// <param name="domElement">Element to inspect.</param> /// <returns type="Boolean">Whether xml:space='preserve' is in effect.</returns> while (domElement !== null && domElement.nodeType === 1) { var val = xmlAttributeValue(domElement, "space", xmlNS); if (val === "preserve") { return true; } else if (val === "default") { break; } else { domElement = domElement.parentNode; } } return false; }; var isXmlNSDeclaration = function (domAttribute) { /// <summary>Determines whether the attribute is a XML namespace declaration.</summary> /// <param name="domAttribute">Element to inspect.</param> /// <returns type="Boolean"> /// True if the attribute is a namespace declaration (its name is 'xmlns' or starts with 'xmlns:'; false otherwise. /// </returns> var nodeName = domAttribute.nodeName; return nodeName == "xmlns" || nodeName.indexOf("xmlns:") == 0; }; var safeSetProperty = function (obj, name, value) { /// <summary>Safely set as property in an object by invoking obj.setProperty.</summary> /// <param name="obj">Object that exposes a setProperty method.</param> /// <param name="name" type="String" mayBeNull="false">Property name.</param> /// <param name="value">Property value.</param> try { obj.setProperty(name, value); } catch (_) { } }; var msXmlDom3 = function () { /// <summary>Creates an configures new MSXML 3.0 ActiveX object.</summary> /// <remakrs> /// This function throws any exception that occurs during the creation /// of the MSXML 3.0 ActiveX object. /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns> var msxml3 = activeXObject("Msxml2.DOMDocument.3.0"); if (msxml3) { safeSetProperty(msxml3, "ProhibitDTD", true); safeSetProperty(msxml3, "MaxElementDepth", 256); safeSetProperty(msxml3, "AllowDocumentFunction", false); safeSetProperty(msxml3, "AllowXsltScript", false); } return msxml3; }; var msXmlDom = function () { /// <summary>Creates an configures new MSXML 6.0 or MSXML 3.0 ActiveX object.</summary> /// <remakrs> /// This function will try to create a new MSXML 6.0 ActiveX object. If it fails then /// it will fallback to create a new MSXML 3.0 ActiveX object. Any exception that /// happens during the creation of the MSXML 6.0 will be handled by the function while /// the ones that happend during the creation of the MSXML 3.0 will be thrown. /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns> try { var msxml = activeXObject("Msxml2.DOMDocument.6.0"); if (msxml) { msxml.async = true; } return msxml; } catch (_) { return msXmlDom3(); } }; var msXmlParse = function (text) { /// <summary>Parses an XML string using the MSXML DOM.</summary> /// <remakrs> /// This function throws any exception that occurs during the creation /// of the MSXML ActiveX object. It also will throw an exception /// in case of a parsing error. /// <returns type="Object">New MSXML DOMDocument node representing the parsed XML string.</returns> var dom = msXmlDom(); if (!dom) { return null; } dom.loadXML(text); var parseError = dom.parseError; if (parseError.errorCode !== 0) { xmlThrowParserError(parseError.reason, parseError.srcText, text); } return dom; }; var xmlThrowParserError = function (exceptionOrReason, srcText, errorXmlText) { /// <summary>Throws a new exception containing XML parsing error information.</summary> /// <param name="exceptionOrReason"> /// String indicatin the reason of the parsing failure or /// Object detailing the parsing error. /// </param> /// <param name="srcText" type="String"> /// String indicating the part of the XML string that caused the parsing error. /// </param> /// <param name="errorXmlText" type="String">XML string for wich the parsing failed.</param> if (typeof exceptionOrReason === "string") { exceptionOrReason = { message: exceptionOrReason }; }; throw extend(exceptionOrReason, { srcText: srcText || "", errorXmlText: errorXmlText || "" }); }; var xmlParse = function (text) { /// <summary>Returns an XML DOM document from the specified text.</summary> /// <param name="text" type="String">Document text.</param> /// <returns>XML DOM document.</returns> /// <remarks>This function will throw an exception in case of a parse error.</remarks> var domParser = window.DOMParser && new window.DOMParser(); var dom; if (!domParser) { dom = msXmlParse(text); if (!dom) { xmlThrowParserError("XML DOM parser not supported"); } return dom; } try { dom = domParser.parseFromString(text, "text/xml"); } catch (e) { xmlThrowParserError(e, "", text); } var element = dom.documentElement; var nsURI = element.namespaceURI; var localName = xmlLocalName(element); // Firefox reports errors by returing the DOM for an xml document describing the problem. if (localName === "parsererror" && nsURI === mozillaParserErroNS) { var srcTextElement = xmlFirstChildElement(element, mozillaParserErroNS, "sourcetext"); var srcText = srcTextElement ? xmlNodeValue(srcTextElement) : ""; xmlThrowParserError(xmlInnerText(element) || "", srcText, text); } // Chrome (and maybe other webkit based browsers) report errors by injecting a header with an error message. // The error may be localized, so instead we simply check for a header as the // top element or descendant child of the document. if (localName === "h3" && nsURI === xhtmlNS || xmlFirstDescendantElement(element, xhtmlNS, "h3")) { var reason = ""; var siblings = []; var cursor = element.firstChild; while (cursor) { if (cursor.nodeType === 1) { reason += xmlInnerText(cursor) || ""; } siblings.push(cursor.nextSibling); cursor = cursor.firstChild || siblings.shift(); } reason += xmlInnerText(element) || ""; xmlThrowParserError(reason, "", text); } return dom; }; var xmlQualifiedName = function (prefix, name) { /// <summary>Builds a XML qualified name string in the form of "prefix:name".</summary> /// <param name="prefix" type="String" maybeNull="true">Prefix string.</param> /// <param name="name" type="String">Name string to qualify with the prefix.</param> /// <returns type="String">Qualified name.</returns> return prefix ? prefix + ":" + name : name; }; var xmlAppendText = function (domNode, textNode) { /// <summary>Appends a text node into the specified DOM element node.</summary> /// <param name="domNode">DOM node for the element.</param> /// <param name="text" type="String" mayBeNull="false">Text to append as a child of element.</param> if (hasLeadingOrTrailingWhitespace(textNode.data)) { var attr = xmlAttributeNode(domNode, xmlNS, "space"); if (!attr) { attr = xmlNewAttribute(domNode.ownerDocument, xmlNS, xmlQualifiedName("xml", "space")); xmlAppendChild(domNode, attr); } attr.value = "preserve"; } domNode.appendChild(textNode); return domNode; }; var xmlAttributes = function (element, onAttributeCallback) { /// <summary>Iterates through the XML element's attributes and invokes the callback function for each one.</summary> /// <param name="element">Wrapped element to iterate over.</param> /// <param name="onAttributeCallback" type="Function">Callback function to invoke with wrapped attribute nodes.</param> var attributes = element.attributes; var i, len; for (i = 0, len = attributes.length; i < len; i++) { onAttributeCallback(attributes.item(i)); } }; var xmlAttributeValue = function (domNode, localName, nsURI) { /// <summary>Returns the value of a DOM element's attribute.</summary> /// <param name="domNode">DOM node for the owning element.</param> /// <param name="localName" type="String">Local name of the attribute.</param> /// <param name="nsURI" type="String">Namespace URI of the attribute.</param> /// <returns type="String" maybeNull="true">The attribute value, null if not found.</returns> var attribute = xmlAttributeNode(domNode, localName, nsURI); return attribute ? xmlNodeValue(attribute) : null; }; var xmlAttributeNode = function (domNode, localName, nsURI) { /// <summary>Gets an attribute node from a DOM element.</summary> /// <param name="domNode">DOM node for the owning element.</param> /// <param name="localName" type="String">Local name of the attribute.</param> /// <param name="nsURI" type="String">Namespace URI of the attribute.</param> /// <returns>The attribute node, null if not found.</returns> var attributes = domNode.attributes; if (attributes.getNamedItemNS) { return attributes.getNamedItemNS(nsURI || null, localName); } return attributes.getQualifiedItem(localName, nsURI) || null; }; var xmlBaseURI = function (domNode, baseURI) { /// <summary>Gets the value of the xml:base attribute on the specified element.</summary> /// <param name="domNode">Element to get xml:base attribute value from.</param> /// <param name="baseURI" mayBeNull="true" optional="true">Base URI used to normalize the value of the xml:base attribute.</param> /// <returns type="String">Value of the xml:base attribute if found; the baseURI or null otherwise.</returns> var base = xmlAttributeNode(domNode, "base", xmlNS); return (base ? normalizeURI(base.value, baseURI) : baseURI) || null; }; var xmlChildElements = function (domNode, onElementCallback) { /// <summary>Iterates through the XML element's child DOM elements and invokes the callback function for each one.</summary> /// <param name="element">DOM Node containing the DOM elements to iterate over.</param> /// <param name="onElementCallback" type="Function">Callback function to invoke for each child DOM element.</param> xmlTraverse(domNode, /*recursive*/false, function (child) { if (child.nodeType === 1) { onElementCallback(child); } // continue traversing. return true; }); }; var xmlFindElementByPath = function (root, namespaceURI, path) { /// <summary>Gets the descendant element under root that corresponds to the specified path and namespace URI.</summary> /// <param name="root">DOM element node from which to get the descendant element.</param> /// <param name="namespaceURI" type="String">The namespace URI of the element to match.</param> /// <param name="path" type="String">Path to the desired descendant element.</param> /// <returns>The element specified by path and namespace URI.</returns> /// <remarks> /// All the elements in the path are matched against namespaceURI. /// The function will stop searching on the first element that doesn't match the namespace and the path. /// </remarks> var parts = path.split("/"); var i, len; for (i = 0, len = parts.length; i < len; i++) { root = root && xmlFirstChildElement(root, namespaceURI, parts[i]); } return root || null; }; var xmlFindNodeByPath = function (root, namespaceURI, path) { /// <summary>Gets the DOM element or DOM attribute node under root that corresponds to the specified path and namespace URI.</summary> /// <param name="root">DOM element node from which to get the descendant node.</param> /// <param name="namespaceURI" type="String">The namespace URI of the node to match.</param> /// <param name="path" type="String">Path to the desired descendant node.</param> /// <returns>The node specified by path and namespace URI.</returns> /// <remarks> /// This function will traverse the path and match each node associated to a path segement against the namespace URI. /// The traversal stops when the whole path has been exahusted or a node that doesn't belogong the specified namespace is encountered. /// /// The last segment of the path may be decorated with a starting @ character to indicate that the desired node is a DOM attribute. /// </remarks> var lastSegmentStart = path.lastIndexOf("/"); var nodePath = path.substring(lastSegmentStart + 1); var parentPath = path.substring(0, lastSegmentStart); var node = parentPath ? xmlFindElementByPath(root, namespaceURI, parentPath) : root; if (node) { if (nodePath.charAt(0) === "@") { return xmlAttributeNode(node, nodePath.substring(1), namespaceURI); } return xmlFirstChildElement(node, namespaceURI, nodePath); } return null; }; var xmlFirstChildElement = function (domNode, namespaceURI, localName) { /// <summary>Returns the first child DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary> /// <param name="domNode">DOM node from which the child DOM element is going to be retrieved.</param> /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> /// <param name="localName" type="String" optional="true">Name of the element to match.</param> /// <returns>The node's first child DOM element that matches the specified namespace URI and local name; null otherwise.</returns> return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/false); }; var xmlFirstDescendantElement = function (domNode, namespaceURI, localName) { /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary> /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param> /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> /// <param name="localName" type="String" optional="true">Name of the element to match.</param> /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns> if (domNode.getElementsByTagNameNS) { var result = domNode.getElementsByTagNameNS(namespaceURI, localName); return result.length > 0 ? result[0] : null; } return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/true); }; var xmlFirstElementMaybeRecursive = function (domNode, namespaceURI, localName, recursive) { /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary> /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param> /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> /// <param name="localName" type="String" optional="true">Name of the element to match.</param> /// <param name="recursive" type="Boolean"> /// True if the search should include all the descendants of the DOM node. /// False if the search should be scoped only to the direct children of the DOM node. /// </param> /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns> var firstElement = null; xmlTraverse(domNode, recursive, function (child) { if (child.nodeType === 1) { var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(child) === namespaceURI; var isExpectedNodeName = !localName || xmlLocalName(child) === localName; if (isExpectedNamespace && isExpectedNodeName) { firstElement = child; } } return firstElement === null; }); return firstElement; }; var xmlInnerText = function (xmlElement) { /// <summary>Gets the concatenated value of all immediate child text and CDATA nodes for the specified element.</summary> /// <param name="domElement">Element to get values for.</param> /// <returns type="String">Text for all direct children.</returns> var result = null; var root = (xmlElement.nodeType === 9 && xmlElement.documentElement) ? xmlElement.documentElement : xmlElement; var whitespaceAlreadyRemoved = root.ownerDocument.preserveWhiteSpace === false; var whitespacePreserveContext; xmlTraverse(root, false, function (child) { if (child.nodeType === 3 || child.nodeType === 4) { // isElementContentWhitespace indicates that this is 'ignorable whitespace', // but it's not defined by all browsers, and does not honor xml:space='preserve' // in some implementations. // // If we can't tell either way, we walk up the tree to figure out whether // xml:space is set to preserve; otherwise we discard pure-whitespace. // // For example <a> <b>1</b></a>. The space between <a> and <b> is usually 'ignorable'. var text = xmlNodeValue(child); var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text); if (!shouldInclude) { // Walk up the tree to figure out whether we are in xml:space='preserve' context // for the cursor (needs to happen only once). if (whitespacePreserveContext === undefined) { whitespacePreserveContext = isWhitespacePreserveContext(root); } shouldInclude = whitespacePreserveContext; } if (shouldInclude) { if (!result) { result = text; } else { result += text; } } } // Continue traversing? return true; }); return result; }; var xmlLocalName = function (domNode) { /// <summary>Returns the localName of a XML node.</summary> /// <param name="domNode">DOM node to get the value from.</param> /// <returns type="String">localName of domNode.</returns> return domNode.localName || domNode.baseName; }; var xmlNamespaceURI = function (domNode) { /// <summary>Returns the namespace URI of a XML node.</summary> /// <param name="node">DOM node to get the value from.</param> /// <returns type="String">Namespace URI of domNode.</returns> return domNode.namespaceURI || null; }; var xmlNodeValue = function (domNode) { /// <summary>Returns the value or the inner text of a XML node.</summary> /// <param name="node">DOM node to get the value from.</param> /// <returns>Value of the domNode or the inner text if domNode represents a DOM element node.</returns> if (domNode.nodeType === 1) { return xmlInnerText(domNode); } return domNode.nodeValue; }; var xmlTraverse = function (domNode, recursive, onChildCallback) { /// <summary>Walks through the descendants of the domNode and invokes a callback for each node.</summary> /// <param name="domNode">DOM node whose descendants are going to be traversed.</param> /// <param name="recursive" type="Boolean"> /// True if the traversal should include all the descenants of the DOM node. /// False if the traversal should be scoped only to the direct children of the DOM node. /// </param> /// <returns type="String">Namespace URI of node.</returns> var subtrees = []; var child = domNode.firstChild; var proceed = true; while (child && proceed) { proceed = onChildCallback(child); if (proceed) { if (recursive && child.firstChild) { subtrees.push(child.firstChild); } child = child.nextSibling || subtrees.shift(); } } }; var xmlSiblingElement = function (domNode, namespaceURI, localName) { /// <summary>Returns the next sibling DOM element of the specified DOM node.</summary> /// <param name="domNode">DOM node from which the next sibling is going to be retrieved.</param> /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param> /// <param name="localName" type="String" optional="true">Name of the element to match.</param> /// <returns>The node's next sibling DOM element, null if there is none.</returns> var sibling = domNode.nextSibling; while (sibling) { if (sibling.nodeType === 1) { var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(sibling) === namespaceURI; var isExpectedNodeName = !localName || xmlLocalName(sibling) === localName; if (isExpectedNamespace && isExpectedNodeName) { return sibling; } } sibling = sibling.nextSibling; } return null; }; var xmlDom = function () { /// <summary>Creates a new empty DOM document node.</summary> /// <returns>New DOM document node.</returns> /// <remarks> /// This function will first try to create a native DOM document using /// the browsers createDocument function. If the browser doesn't /// support this but supports ActiveXObject, then an attempt to create /// an MSXML 6.0 DOM will be made. If this attempt fails too, then an attempt /// for creating an MXSML 3.0 DOM will be made. If this last attemp fails or /// the browser doesn't support ActiveXObject then an exception will be thrown. /// </remarks> var implementation = window.document.implementation; return (implementation && implementation.createDocument) ? implementation.createDocument(null, null, null) : msXmlDom(); }; var xmlAppendChildren = function (parent, children) { /// <summary>Appends a collection of child nodes or string values to a parent DOM node.</summary> /// <param name="parent">DOM node to which the children will be appended.</param> /// <param name="children" type="Array">Array containing DOM nodes or string values that will be appended to the parent.</param> /// <returns>The parent with the appended children or string values.</returns> /// <remarks> /// If a value in the children collection is a string, then a new DOM text node is going to be created /// for it and then appended to the parent. /// </remarks> if (!isArray(children)) { return xmlAppendChild(parent, children); } var i, len; for (i = 0, len = children.length; i < len; i++) { children[i] && xmlAppendChild(parent, children[i]); } return parent; }; var xmlAppendChild = function (parent, child) { /// <summary>Appends a child node or a string value to a parent DOM node.</summary> /// <param name="parent">DOM node to which the child will be appended.</param> /// <param name="child">Child DOM node or string value to append to the parent.</param> /// <returns>The parent with the appended child or string value.</returns> /// <remarks> /// If child is a string value, then a new DOM text node is going to be created /// for it and then appended to the parent. /// </remarks> if (child) { if (typeof child === "string") { return xmlAppendText(parent, xmlNewText(parent.ownerDocument, child)); } if (child.nodeType === 2) { parent.setAttributeNodeNS ? parent.setAttributeNodeNS(child) : parent.setAttributeNode(child); } else { parent.appendChild(child); } } return parent; }; // ##### BEGIN: MODIFIED BY SAP // polyfill for document.createAttributeNS which was removed from Chrome 34 // but will be added back in, see: // http://datajs.codeplex.com/workitem/1272 // https://code.google.com/p/chromium/issues/detail?id=347506 // https://codereview.chromium.org/243333003 var _createAttributeNS = function(namespaceURI, qualifiedName) { var dummy = document.createElement('dummy'); dummy.setAttributeNS(namespaceURI, qualifiedName, ''); var attr = dummy.attributes[0]; dummy.removeAttributeNode(attr); return attr; }; // ##### END: MODIFIED BY SAP var xmlNewAttribute = function (dom, namespaceURI, qualifiedName, value) { /// <summary>Creates a new DOM attribute node.</summary> /// <param name="dom">DOM document used to create the attribute.</param> /// <param name="prefix" type="String">Namespace prefix.</param> /// <param name="namespaceURI" type="String">Namespace URI.</param> /// <returns>DOM attribute node for the namespace declaration.</returns> // ##### BEGIN: MODIFIED BY SAP // added usage of _createAttributeNS as fallback (see function above) var attribute = dom.createAttributeNS && dom.createAttributeNS(namespaceURI, qualifiedName) || "createNode" in dom && dom.createNode(2, qualifiedName, namespaceURI || undefined) || _createAttributeNS(namespaceURI, qualifiedName); // ##### END: MODIFIED BY SAP attribute.value = value || ""; return attribute; }; var xmlNewElement = function (dom, nampespaceURI, qualifiedName, children) { /// <summary>Creates a new DOM element node.</summary> /// <param name="dom">DOM document used to create the DOM element.</param> /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element.</param> /// <param name="qualifiedName" type="String">Qualified name in the form of "prefix:name" of the new DOM element.</param> /// <param name="children" type="Array" optional="true"> /// Collection of child DOM nodes or string values that are going to be appended to the new DOM element. /// </param> /// <returns>New DOM element.</returns> /// <remarks> /// If a value in the children collection is a string, then a new DOM text node is going to be created /// for it and then appended to the new DOM element. /// </remarks> var element = dom.createElementNS && dom.createElementNS(nampespaceURI, qualifiedName) || dom.createNode(1, qualifiedName, nampespaceURI || undefined); return xmlAppendChildren(element, children || []); }; var xmlNewNSDeclaration = function (dom, namespaceURI, prefix) { /// <summary>Creates a namespace declaration attribute.</summary> /// <param name="dom">DOM document used to create the attribute.</param> /// <param name="namespaceURI" type="String">Namespace URI.</param> /// <param name="prefix" type="String">Namespace prefix.</param> /// <returns>DOM attribute node for the namespace declaration.</returns> return xmlNewAttribute(dom, xmlnsNS, xmlQualifiedName("xmlns", prefix), namespaceURI); }; var xmlNewFragment = function (dom, text) { /// <summary>Creates a new DOM document fragment node for the specified xml text.</summary> /// <param name="dom">DOM document from which the fragment node is going to be created.</param> /// <param name="text" type="String" mayBeNull="false">XML text to be represented by the XmlFragment.</param> /// <returns>New DOM document fragment object.</returns> var value = "<c>" + text + "</c>"; var tempDom = xmlParse(value); var tempRoot = tempDom.documentElement; var imported = ("importNode" in dom) ? dom.importNode(tempRoot, true) : tempRoot; var fragment = dom.createDocumentFragment(); var importedChild = imported.firstChild; while (importedChild) { fragment.appendChild(importedChild); importedChild = importedChild.nextSibling; } return fragment; }; var xmlNewText = function (dom, text) { /// <summary>Creates new DOM text node.</summary> /// <param name="dom">DOM document used to create the text node.</param> /// <param name="text" type="String">Text value for the DOM text node.</param> /// <returns>DOM text node.</returns> return dom.createTextNode(text); }; var xmlNewNodeByPath = function (dom, root, namespaceURI, prefix, path) { /// <summary>Creates a new DOM element or DOM attribute node as specified by path and appends it to the DOM tree pointed by root.</summary> /// <param name="dom">DOM document used to create the new node.</param> /// <param name="root">DOM element node used as root of the subtree on which the new nodes are going to be created.</param> /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element or attribute.</param> /// <param name="namespacePrefix" type="String">Prefix used to qualify the name of the new DOM element or attribute.</param> /// <param name="Path" type="String">Path string describing the location of the new DOM element or attribute from the root element.</param> /// <returns>DOM element or attribute node for the last segment of the path.</returns> /// <remarks> /// This function will traverse the path and will create a new DOM element with the specified namespace URI and prefix /// for each segment that doesn't have a matching element under root. /// /// The last segment of the path may be decorated with a starting @ character. In this case a new DOM attribute node /// will be created. /// </remarks> var name = ""; var parts = path.split("/"); var xmlFindNode = xmlFirstChildElement; var xmlNewNode = xmlNewElement; var xmlNode = root; var i, len; for (i = 0, len = parts.length; i < len; i++) { name = parts[i]; if (name.charAt(0) === "@") { name = name.substring(1); xmlFindNode = xmlAttributeNode; xmlNewNode = xmlNewAttribute; } var childNode = xmlFindNode(xmlNode, namespaceURI, name); if (!childNode) { childNode = xmlNewNode(dom, namespaceURI, xmlQualifiedName(prefix, name)); xmlAppendChild(xmlNode, childNode); } xmlNode = childNode; } return xmlNode; }; var xmlSerialize = function (domNode) { /// <summary> /// Returns the text representation of the document to which the specified node belongs. /// </summary> /// <param name="root">Wrapped element in the docum