Source: ogc/openSearch/descriptionDocument/OpenSearchUrl.js

/*
 * Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
 * National Aeronautics and Space Administration. All rights reserved.
 *
 * The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @exports OpenSearchUrl
 */

define([
        '../../../util/Logger',
        '../OpenSearchConstants',
        '../OpenSearchNamespaces',
        './OpenSearchParameter',
        '../OpenSearchUtils'
    ],
    function (Logger,
              OpenSearchConstants,
              OpenSearchNamespaces,
              OpenSearchParameter,
              OpenSearchUtils) {
        'use strict';

        /**
         * Constructs an OpenSearchUrl.
         *
         * @alias OpenSearchUrl
         * @constructor
         * @classdesc Represents an OpenSearch URL.
         *
         * The OpenSearch URL describes an interface by which a client can make requests for an external resource,
         * such as search results, or additional description documents.
         *
         * This object holds as properties the fields for a OpenSearch URL node.
         * Most fields can be accessed as properties named according to their document names converted to camel case.
         */
        var OpenSearchUrl = function () {
            this._type = '';
            this._method = '';
            this._encType = '';
            this._template = '';
            this._parameters = [];
            this._indexOffset = 1;
            this._pageOffset = 1;
            this._relations = [];

            //Internal. The base url of the template.
            this._baseUrl = '';
            //Internal. A map of the parameters of this OpenSearchUrl
            this._paramsByName = Object.create(null);
            //Internal. A list of the parameters that are not meant to be replaced.
            this._staticParams = [];
        };

        Object.defineProperties(OpenSearchUrl.prototype, {
            /**
             * The mime type of the resource being described.
             * @memberof OpenSearchUrl.prototype
             * @type {String}
             */
            type: {
                get: function () {
                    return this._type;
                }
            },

            /**
             * A valid HTTP verb for this URL.
             * @memberof OpenSearchUrl.prototype
             * @type {String}
             */
            method: {
                get: function () {
                    return this._method;
                }
            },

            /**
             * The encoding for POST or PUT requests.
             * @memberof OpenSearchUrl.prototype
             * @type {String}
             */
            encType: {
                get: function () {
                    return this._encType;
                }
            },

            /**
             * The parameterized form of the URL by which a search engine is queried.
             * @memberof OpenSearchUrl.prototype
             * @type {String}
             */
            template: {
                get: function () {
                    return this._template;
                }
            },

            /**
             * The list of parameters.
             * @memberof OpenSearchUrl.prototype
             * @type {OpenSearchParameter[]}
             */
            parameters: {
                get: function () {
                    return this._parameters;
                }
            },

            /**
             * The index number of the first search result.
             * @memberof OpenSearchUrl.prototype
             * @type {Number}
             * @default 1
             */
            indexOffset: {
                get: function () {
                    return this._indexOffset;
                }
            },

            /**
             * The page number of the first set of search results.
             * @memberof OpenSearchUrl.prototype
             * @type {Number}
             * @default 1
             */
            pageOffset: {
                get: function () {
                    return this._pageOffset;
                }
            },

            /**
             * The role of the resource being described in relation to the description document.
             * @memberof OpenSearchUrl.prototype
             * @type {String[]}
             * @default ['results']
             */
            relations: {
                get: function () {
                    return this._relations;
                }
            },

            paramsByName: {
                get: function() {
                    return this._paramsByName;
                }
            },

            staticParams: {
                get: function() {
                    return this._staticParams;
                }
            }
        });

        /**
         * Internal use. Applications should not call this method.
         * Parses an URL node.
         *
         * @param {Node} node The URL node to parse.
         *
         * @return {OpenSearchUrl} The resulting OpenSearchUrl.
         */
        OpenSearchUrl.prototype.parse = function (node) {
            this.parseAttributes(node);
            var templateParams = this.parseTemplate(this._template);
            if (!templateParams) {
                return this;
            }
            var nodeParams = this.parseNodeParams(node);

            //params only in nodes, not in template
            var onlyNodeParams = nodeParams.filter(function (param) {
                var templateParam = OpenSearchUtils.arrayFind(templateParams, function(templateParamElement) {
                    return templateParamElement.name === param.name;
                });
                return templateParam == null;
            });

            var parameters = templateParams.map(function (templateParam) {
                var nodeParam = OpenSearchUtils.arrayFind(nodeParams, function (nodeParamElement) {
                    return nodeParamElement.name === templateParam.name;
                });
                if (nodeParam) {
                    return templateParam.merge(nodeParam);
                }
                return templateParam;
            }).concat(onlyNodeParams);

            this._parameters = parameters.filter(function (param) {
                return param.replaceable;
            });

            this._staticParams = parameters.filter(function (param) {
                return !param.replaceable;
            });

            this._parameters.forEach(function (param) {
                this._paramsByName[param.name] = param;
            }, this);

            return this;
        };

        /**
         * Internal use. Applications should not call this method.
         * Parses the attributes of an URL node and stores the result in this OpenSearchUrl instance.
         *
         * @param {Node} node The URL node to parse.
         */
        OpenSearchUrl.prototype.parseAttributes = function (node) {
            this._type = node.getAttribute('type');
            this._template = node.getAttribute('template');

            var rel = node.getAttribute('rel');
            if (rel) {
                this._relations = rel.split(' ');
            }
            else {
                this._relations = [OpenSearchConstants.RESULTS];
            }

            this._indexOffset = node.getAttribute('indexOffset');
            if (this._indexOffset) {
                this._indexOffset = +this._indexOffset;
            }
            else {
                this._indexOffset = 1;
            }

            this._pageOffset = node.getAttribute('pageOffset');
            if (this._pageOffset) {
                this._pageOffset = +this._pageOffset;
            }
            else {
                this._pageOffset = 1;
            }

            this._method = node.getAttributeNS(OpenSearchNamespaces.parameters, 'method') || 'GET';
            this._encType = node.getAttributeNS(OpenSearchNamespaces.parameters, 'enctype') || 'application/x-www-form-urlencoded';
        };

        /**
         * Internal use. Applications should not call this method.
         * Parses an URL template and extracts its search parameters.
         *
         * @param {String} template The URL template to parse.
         * @return {OpenSearchParameter[]|undefined} The list of search parameters.
         */
        OpenSearchUrl.prototype.parseTemplate = function (template) {
            if (!template) {
                return;
            }

            var params = [];
            var urlParser = OpenSearchUrl.createUrlParser();
            urlParser.href = template;
            var queryString = urlParser.search;
            this._baseUrl = urlParser.protocol + '//' + urlParser.host + urlParser.port + urlParser.pathname;
            this._baseUrl = urlParser.protocol + '//' + urlParser.hostname;
            if (urlParser.port) {
                this._baseUrl += ':' + urlParser.port;
            }
            if (urlParser.pathname) {
                if (urlParser.pathname[0] === '/') {
                    this._baseUrl += urlParser.pathname;
                }
                else {
                    this._baseUrl += '/' + urlParser.pathname;
                }
            }


            if (!queryString) {
                return params;
            }

            if (queryString[0] === '?') {
                queryString = queryString.slice(1);
            }
            queryString = queryString.replace(/&(?!amp;)/g, "&");
            var queries = queryString.split('&');
            for (var i = 0; i < queries.length; i++) {
                params.push(OpenSearchParameter.fromQuery(queries[i]));
            }

            return params;
        };

        /**
         * Internal use. Applications should not call this method.
         * Parses an URL node and extracts the parameters from the Parameter node introduced by the OpenSearch
         * Parameter extension.
         *
         * @param {Node} node The URL to parse.
         * @return {OpenSearchParameter[]} The list of search parameters.
         */
        OpenSearchUrl.prototype.parseNodeParams = function (node) {
            var paramNodes = OpenSearchUtils.getXmlElements(node, 'Parameter');
            return paramNodes.map(function (paramNode) {
                return OpenSearchParameter.fromNode(paramNode);
            });
        };

        /**
         * Internal use. Applications should not call this method.
         * Checks if this URL is compatible with the specified search parameters.
         *
         * @param {Array} searchParams The list of search parameters.
         * @return {Boolean}
         */
        OpenSearchUrl.prototype.isCompatible = function (searchParams) {
            var compatible = searchParams.every(function (searchParam) {
                return searchParam.name in this._paramsByName;
            }, this);

            if (!compatible) {
                return false;
            }

            var missingRequiredParams = this._parameters.filter(function (param) {
                return (
                    param.required &&
                    !searchParams.some(function (searchParam) {
                        return searchParam.name === param.name;
                    })
                );
            });

            if(missingRequiredParams.length > 0) {
                Logger.logMessage(Logger.LEVEL_WARNING, 'OpenSearchUrl', 'isCompatible', 'Missing required params: ', missingRequiredParams.map(function (param) {
                    return param.name + ': ' + param.value;
                }).join(','));
            }

            return missingRequiredParams.length === 0;
        };

        /**
         * Internal use. Applications should not call this method.
         * Creates a query URL for the supplied search parameters to be used for GET requests.
         *
         * @param {Array|null} searchParams The list of search parameters.
         * @return {String} The resulting URL.
         */
        OpenSearchUrl.prototype.createRequestUrl = function (searchParams) {
            searchParams = searchParams || [];

            var url = this._baseUrl;
            var queryParts = this._staticParams.map(function (param) {
                return param.name + '=' + param.value;
            }).concat(searchParams.map(function (param) {
                return param.name + '=' + this.serializeParam(param.value);
            }, this)).join('&');

            if (queryParts.length) {
                url += '?' + queryParts;
            }

            return url;
        };

        /**
         * Internal use. Applications should not call this method.
         * Creates the request body for the supplied search parameters to be used for POST or PUT requests.
         *
         * @param {Array|null} searchParams The list of search parameters.
         * @return {FormData} The resulting request body.
         */
        OpenSearchUrl.prototype.createRequestBody = function (searchParams) {
            var formData = new FormData();
            var queryParts = this.createQueryParts(searchParams);
            queryParts.forEach(function (query) {
                formData.append(query.name, query.value);
            });
            return formData;
        };

        /**
         * Internal use. Applications should not call this method.
         * Creates an key-value pair object with the name and value of the search parameters.
         *
         * @param {Array|null} searchParams The list of search parameters.
         * @return {Object} The resulting object.
         */
        OpenSearchUrl.prototype.createQueryParts = function (searchParams) {
            searchParams = searchParams || [];

            return this._staticParams.map(function (param) {
                return {name: param.name, value: this.serializeParam(param.value)};
            }, this).concat(searchParams.map(function (param) {
                return {name: param.name, value: this.serializeParam(param.value)};
            }, this));
        };

        /**
         * Internal use. Applications should not call this method.
         * Serializes a value to be used for a GET request.
         *
         * @param {Any} value The value to be serialized.
         * @return {String} The serialized value.
         */
        OpenSearchUrl.prototype.serializeParam = function (value) {
            if (typeof value === 'string') {
                return encodeURIComponent(value);
            }
            if (typeof value === 'number') {
                return value.toString();
            }
            if (value instanceof Date) {
                return value.toISOString();
            }
            if (value &&
                typeof value === 'object' &&
                value.minLongitude != null &&
                value.minLatitude != null &&
                value.maxLongitude != null &&
                value.maxLatitude != null) {
                return value.minLongitude + ',' + value.minLatitude + ',' + value.maxLongitude + ',' + value.maxLatitude;
            }

            return encodeURIComponent(String(value));
        };

        /**
         * Internal use. Applications should not call this method.
         * Creates an URL parser as an anchor element.
         *
         * @return {HTMLElement} The anchor element.
         */
        OpenSearchUrl.createUrlParser = function () {
            if (!OpenSearchUrl.urlParser) {
                OpenSearchUrl.urlParser = document.createElement('a');
            }
            return OpenSearchUrl.urlParser;
        };

        return OpenSearchUrl;

    });