UNPKG

kurl

Version:

It's a powerfull URL parser written in JavaScript and compatible with NodeJS with a lot of features that it will help you to modify/parse easily a URL without any problem.

1,267 lines (658 loc) 28.3 kB
/*! * @name JavaScript/NodeJS URL v1.2.7 * @author yeikos * @repository https://github.com/yeikos/js.url * Copyright 2014 yeikos - MIT license * https://raw.github.com/yeikos/js.url/master/LICENSE */ ;(function(isNode) { var Public = function URL(url, location) { // Si la instancia no ha sido creada if (!(this instanceof Public)) // Devolvemos una instancia return Public.instance(arguments); // Inicializamos el objeto contenedor de atributos this._attributes = {}; // Si la instancia sobre la que nos encontramos no es `location` if (!this.isLocation) // Creamos la instancia `location` con un marcador para evitar repetir código this.location = Public.instance((arguments.length > 1) ? [location] : [], function() { this.isLocation = true; }); // Establecemos la dirección if (arguments.length) this.href(url); return this; }, publicName = 'URL'; Public.prototype = { constructor: Public, // Atributos de la URL (interactuar con el método `attr`) _attributes: null, // Establece/Obtiene el valor de la URL href: function(input) { var temp; // Si no hay parámetros if (!arguments.length) { // Construimos la URL con los atributos y la devolvemos como cadena de texto return Public.build(this._attributes, this.location); // Si el parámetro es `null` } else if (input === null) { // Vaciamos los atributos this._attributes = {}; // Si el parámetro es una cadena de texto o un objecto } else if ((temp = typeof input) === 'string' || (input && temp === 'object')) { // Desmontamos la URL y guardamos su información en forma de objeto this._attributes = Public.unbuild(input, this.location); } return this; }, // Establece/Obtiene valores de atributos attr: function(name, value) { var temp = arguments.length; // Si no hay parámetros if (!temp) { // Devolvemos una copia sin referencias de los atributos actuales var buffer = {}; for (temp in this._attributes) buffer[temp] = _toString(this._attributes[temp]); return buffer; // Si hay un parámetro } else if (temp === 1) { // Si es una cadena de texto if ((temp = typeof name) === 'string') { // get() name = name.toLowerCase(); if (name === 'query') name = 'search'; // Devolvemos el valor del atributo en formato literal return _toString(this._attributes[name.toLowerCase()]); // Si es un objeto } else if (name && (temp = typeof name) === 'object') { // set(object) // Recorremos sus propiedades y las establecemos una a una for (temp in name) this.attr(temp, name[(temp || '').toLowerCase()]); } // Si hay dos parámetros y el nombre del atributo es una cadena de texto } else if (temp === 2 && typeof name === 'string' && (temp = name.toLowerCase())) { // set(name, value) // Si el atributo existe y el valor es válido if (((function(index) { while(index--) if (Public.attributes[index] === name) return true; })(Public.attributes.length))) { // Si se trata del atributo `host` o `hostname` if (name === 'host' || name === 'hostname' || name === 'port') { // Si el atributo es `host` actualizamos el `hostname` y el `port` (hostname:port) // Si el atributo es `hostname` actualizamos el `host` y dejamos el `port` intacto // Si el atributo es `port` actualizaremos `host` y `hostname` if (name === 'port') { temp = { hostname: this._attributes.hostname, port: Public.normalize.port(value) }; } else { temp = Public.normalize.host( _toString(value) + ( (name === 'hostname' && this._attributes.port) ? (':' + this._attributes.port) : '' ) ); } this._attributes.host = temp.hostname + (temp.port ? (':' + temp.port) : ''); this._attributes.hostname = temp.hostname; this._attributes.port = temp.port; } else { if (name === 'query') name = 'search'; // Establecemos el nuevo valor normalizado del atributo this._attributes[name] = Public.normalize[name](value); } } } return this; }, // Opera con el atributo `search` en forma de objeto search: function() { return _prototypeQuery.apply(this, ['search'].concat([].slice.apply(arguments))); }, // Opera con el atributo `hash` en forma de objeto hash: function() { return _prototypeQuery.apply(this, ['hash'].concat([].slice.apply(arguments))); }, // Comprueba si la URL es externa isExternal: function() { // Construimos los atributos en base a la localización var attr = Public.unbuild(this._attributes, this.location); // Si no se encuentra disponible la instancia localización if (!(this.location instanceof Public)) // La dirección será externa si tiene definido el atributo `protocol` o `host` return (attr.protocol || attr.host) ? true : false; // La URL será externa si `protocol` o `host` no coincide con el de localización return (this.location.attr('protocol') !== attr.protocol || this.location.attr('host') !== attr.host); }, // Construye la URL con los atributos seleccionados select: function() { return _prototypeSelect.apply(this, [arguments]); }, // Construye la URL sin los atributos seleccionados unselect: function() { var attr = Public.attributes.slice(0), argv = [].slice.apply(arguments), buffer = {}, x = argv.length, y = attr.length; // Convertimos la matriz de argumentos a objeto while (x--) buffer[_toString(argv[x]).toLowerCase()] = 1; // Si se desea eliminar el atributo `hostname` o `port` if (buffer.hostname || buffer.port) { // Eliminamos el atributo `host`, ya que contiene a ambos buffer.host = 1; // Si se desea eliminar el atributo `host` } else if (buffer.host) { // Eliminamos el atributo `hostname` y `port`, ya que contiene a ambos buffer.hostname = 1; buffer.port = 1; } // Recorremos los atributos while (y--) // Si se encuentra en la lista if (buffer[attr[y]]) // Lo eliminamos attr.splice(y, 1); return _prototypeSelect.apply(this, [attr]); }, // Construye la URL empezando por el atributo introducido from: function(input) { return _prototypeSelect.apply(this, [Public.attributes.slice(0).reverse(), input]); }, // Construye la URL empezando desde el principio hasta el atributo introducido to: function(input) { return _prototypeSelect.apply(this, [Public.attributes, input]); } }; Public.prototype.query = Public.prototype.search; Public.version = '1.2.7'; // Atributos válidos de la URL Public.attributes = ['protocol', 'auth', 'host', 'hostname', 'port', 'pathname', 'search', 'query', 'hash']; // Normalización de la URL Public.normalize = { // Clasificación de las partes de una URL regexp_split: /^(?:(?:(.+:)?\/\/)(?:(?:(.+)@)?([^\/:]+)(?::([^\/]+))?)?)?(?:(\/?[^?#]*)?(\?[^#]*)?(#.*)?)?/, // Validación de las diversas partes de una URL en base al estandar RFC3986 // http://tools.ietf.org/html/rfc3986 // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html (regex_rfc3986_*) regex_rfc3986_schema: /^[a-z][\w+\-.]*$/i, regex_rfc3986_userinfo: /^(?:[\w\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})*$/, regex_rfc3986_host: /^(?:\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\.[\w\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[\w\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*)$/, regex_rfc3986_port: /^\d+$/, // La validación del atributo `pathname` y `query` es innecesaria, ya que la función encodeURI se encarga de normalizarla // regex_rfc3986_path: /^(?:(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\/(?:(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[\w\-._~!$&'()*+,;=@]|%[0-9A-Fa-f]{2})+(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)$/, // regex_rfc3986_query: /^(?:[\w\-._~!$&'()*+,;=:@\/?]|%[0-9A-Fa-f]{2})*$/, // Normalización de los atributos de una URL protocol: function(input) { // Nos deshacemos del caracter final `:` para su validación var size = (input = _toString(input)).length; input = (input.substr(size-1, 1) === ':') ? input.substr(0, input.length-1) : input; // Si el protocolo es correcto añadimos `:` al final del mismo // si no lo es devolvemos una cadena vacía return Public.normalize.regex_rfc3986_schema.test((input = input.toLowerCase())) ? (input + ':') : ''; }, auth: function(input) { // Si la entrada no es válida deacuerdo al estandar devolvemos una cadena vacía return Public.normalize.regex_rfc3986_userinfo.test(input = _toString(input)) ? input : ''; }, host: function(input) { var port = '', hostname; input = _toString(input).split(':'); // Si se encuentra el caracter `:` if (input.length > 1) { // Obtenemos el puerto y el nombre del `host` port = Public.normalize.port(input.pop()); // Si el nombre del `host` no es correcto no habrá puerto if (!(hostname = Public.normalize.hostname(input.join(':')))) port = ''; } else { // Si no se ecuentra el caracter de delimitación no habrá puerto hostname = Public.normalize.hostname(input.shift()); } // Devolvemos los atributos `hostname` y `port` normalizados return { hostname: hostname, port: port }; }, hostname: function(input) { // Si la entrada no es válida deacuerdo al estandar devolvemos una cadena vacía return Public.normalize.regex_rfc3986_host.test(input = _toString(input).toLowerCase()) ? input : ''; }, port: function(input) { // Si la entrada no es válida deacuerdo al estandar devolvemos una cadena vacía return Public.normalize.regex_rfc3986_port.test(input = _toString(input)) ? input : ''; }, pathname: function(input) { // Decodificamos y volvemos a codificar caracteres especiales en la entrada de forma segura try { input = encodeURI(decodeURI(_toString(input))); } catch(e) { input = ''; } var items = input.split('/'), index = items.length, result = [], back = 0; while (index--) { // Retroceso de carpeta if (items[index] === '..') { back++; // Si solo contiene un punto es ignorado } else if (items[index] !== '.') { if (!back) { result.unshift(items[index]); } else { back--; } } } // Si la carpeta comenzaba con una barra, por mucho retroceso que haya, tendrá que comenzar con una barra return ((result = result.join('/')).substr(0, 1) !== '/' && input.substr(0, 1) === '/') ? ('/' + result) : result; }, search: function(input) { return _normalizeQuery.apply(this, ['search', input]); }, hash: function(input) { return _normalizeQuery.apply(this, ['hash', input]); } }; // Crea una nueva instancia con la posibilidad de interactuar con ella antes de llamar a su constructor original Public.instance = function(argv, callback) { var Foo = function URL(callback) { if (typeof callback === 'function') callback.call(this); }; argv = (typeof argv === 'object' && argv) ? [].slice.call(argv, 0) : argv; return (Foo.prototype = Public.prototype).constructor.apply( new Foo(callback), ((argv instanceof Array) ? argv : []) ); }; // Obtiene la URL de un elemento (form, a, base, link, img, script, iframe) Public.getElementURL = function(input) { if (!_isElement(input)) return ''; var temp = (typeof HTMLElement === 'function') ? (((temp = (input instanceof HTMLFormElement) ? 'action' : ( ((input instanceof HTMLAnchorElement) || (input instanceof HTMLBaseElement) || (input instanceof HTMLLinkElement)) ? 'href' : ( ((input instanceof HTMLImageElement) || (input instanceof HTMLScriptElement) || (input instanceof HTMLIFrameElement)) ? 'src' : 0 ) ))) ? temp : '') : ( (temp = ((temp = input.nodeName.toLowerCase()) === 'form') ? 'action' : ( (temp === 'a' || temp === 'base' || temp === 'link') ? 'href' : ( (temp === 'img' || temp === 'script' || temp === 'iframe') ? 'src' : 0 ) ) ) ? temp : ''); return ( (typeof input.getAttribute === 'function' || (typeof input.getAttribute === 'object' && input.getAttribute)) && (temp = input.getAttribute(temp)) ) ? temp : ''; }; // Convierte un objeto de atributos a una cadena literal URL (object -> string) Public.build = function(input, location) { var attr = this.unbuild(input, location); return (attr.protocol ? (attr.protocol + '//') : (attr.host ? '//' : '')) + ( // Es necesario el atributo `host` para añadir el atributo `auth` (attr.auth && attr.host) ? (attr.auth + '@') : '' ) + attr.host + // Si el atributo `pathname` se encuentra vacío, se trata de una dirección y le acompaña el atributo `search` o `hash` // debemos definir el atributo `pathname` como `/` para no corromper la estructura de la URL ((!attr.pathname && (attr.protocol || attr.host) && (attr.search || attr.hash)) ? '/' : '') + (attr.pathname + attr.search + attr.hash); }; // Convierte una cadena literal URL a un objeto de atributos (string -> object) Public.unbuild = function(input, location) { var normalize = Public.normalize, attr, matches, temp; // Si la localización está definida como una instancia URL // una cadena literal o un objecto lo convertimos a un objecto de atributos normalizados location = (location instanceof Public) ? location.attr() : ( (location && ((temp = typeof location) === 'string' || temp === 'object')) ? Public.unbuild(location) : false ); // Si es un elemento HTML if (_isElement(input)) // Extraemos su dirección input = Public.getElementURL(input); // Si la entrada es una cadena de texo if (typeof input === 'string') { // Decodificamos y codificar la entrada para que los caracteres no válidos queden codificados input = encodeURI(decodeURI(input)); // Clasificamos cada una de las partes de la URL if (!(matches = input.match(Public.normalize.regexp_split))) matches = []; // Normalización de los atributos attr = { protocol: normalize.protocol(matches[1]), auth: normalize.auth(matches[2]), hostname: normalize.hostname(matches[3]), port: normalize.port(matches[4]), pathname: normalize.pathname(matches[5]), search: normalize.search(matches[6]), hash: normalize.hash(matches[7]) }; // Si es una instancia de URL } else if (input instanceof Public) { // Obtenemos los atributos attr = input.attr(); } else { // Si no es un objeto forzamos a que lo sea if (!input || typeof input !== 'object') input = {}; // Normalización de los atributos attr = { protocol: normalize.protocol(input.protocol), auth: normalize.auth(input.auth), pathname: normalize.pathname(input.pathname), search: normalize.search(input.search), hash: normalize.hash(input.hash) }; // El atributo `host` tiene preferencia if ('host' in input) { attr.hostname = (temp = normalize.host(input.host)).hostname; attr.port = temp.port; } else if ('hostname' in input) { attr.hostname = normalize.hostname(input.hostname); attr.port = attr.hostname ? normalize.port(input.port) : ''; } else { attr.hostname = ''; attr.port = ''; } } // Si la localización se encuentra activada if (location) { // El atributo `host` tiene preferencia if ('host' in location) { attr._hostname = (temp = normalize.host(location.host)).hostname; attr._port = temp.port; } else if ('hostname' in location) { attr._hostname = normalize.hostname(location.hostname); attr._port = attr.hostname ? normalize.port(location.port) : ''; } // Escalonadamente y empezando por el primer atributo hasta llegar al último // Si no se encuentra definido el atributo será reemplazado por el de su localización si existe // Si se encuentra definido el atributo se terminará el escalonamiento y no habrá más reemplazos por parte de la localización if (!attr.protocol) { if ('protocol' in location) attr.protocol = normalize.protocol(location.protocol); if (!attr.hostname) { if ('_hostname' in attr) attr.hostname = attr._hostname; if (!attr.port) { if ('_port' in attr) attr.port = attr._port; if (!attr.auth) { if ('auth' in location) attr.auth = normalize.auth(location.auth); if (!attr.pathname) { if ('pathname' in location) attr.pathname = normalize.pathname(location.pathname); if (!attr.search) { if ('search' in location) attr.search = normalize.search(location.search); if (!attr.hash && 'hash' in location) attr.hash = normalize.search(location.hash); } // Si el atributo `pathname` no empieza con `/` } else if ('pathname' in location && attr.pathname.substr(0, 1) !== '/') { // Concatenamos los atributos `pathname` eliminando la última carpeta de la localización attr.pathname = normalize.pathname(location.pathname).split('/').slice(0, -1).concat(attr.pathname).join('/'); } } } } } delete attr._hostname; delete attr._port; } // Generamos el atributo `host` a partir de los atributos `hostname` y `port` attr.host = attr.hostname + (attr.port ? (':' + attr.port) : ''); // Devolvemos los atributos en forma de objeto return attr; }; // Convierte un objecto a una cadena de texto tipo `query` ({a: 1, b: 2} -> a=1&b=2) Public.param = function(input) { var recursive = function(input, prefix) { if (!input || typeof input !== 'object') return ''; var result = [], expSpace = /%20/g, empty = true, index, subindex, item, key; input = _object2array(input); for (index in input) { empty = false; // Si la matriz contiene un objeto se indicará el índice para // que no se pierda la referencia de sus descendientes subindex = (input instanceof Array && !(typeof input[index] === 'object' && input[index])) ? '' : encodeURIComponent(index); key = prefix ? (prefix + '[' + subindex + ']') : encodeURIComponent(index); result.push( ((item = input[index]) && typeof item === 'object') ? recursive(item, key) : ( key.replace(expSpace, '+') + (( item = encodeURIComponent( (typeof item === 'string' || typeof item === 'number') ? item : '' ).replace(expSpace, '+') ) ? ('=' + item) : '') ) ); } if (empty) result.push(prefix); return result.join('&'); }; return recursive(input); }; // Convierte una cadena de texto tipo `query` a objecto (a=1&b=2 -> {a: 1, b: 2}) Public.unparam = function(input) { // Descartamos entradas que no sean cadenas de texto if (typeof input !== 'string') return {}; // Variables de itineración var index, size, item, subindex, subsize, subitems, // Otras variables key, value, link, temp, subtemp, // Números enteros que no empiecen por cero expNumber = /^[0-9]d*/, isNumber = function(n) { // Es un número válido, no empieza por 0X (ej: 02) y solo contiene números y puntuación return !isNaN(parseFloat(n)) && isFinite(n) && /^(?!0\d)/.test(n) && /^[\d\.]+$/.test(n); }, // Nombre de la clave expKeyName = /^[^\[]+/, // Anidaciones de la clave expKeyNodes = /\[(.*?)\]/g, // Espacios expSpaces = /\+/g, // Contenedor de resultado result = {}; // Recorremos los conjuntos nombre=valor for (index = 0, size = (input = (input).split(/&+/)).length; index < size; ++index) { // Descartamos cadenas vacías if (!(item = input[index])) continue; // 1+1+1=2+1 -> 1 1 1=2 1 item = item.replace(expSpaces, ' '); // Obtenemos el nombre de la clave y su valor try { key = decodeURIComponent((temp = item.split('=')).shift()); } catch(e) { continue; } try { value = decodeURIComponent(temp.join('=').replace(expSpaces, ' ')); } catch(e) { continue; } // El nombre de la clave no ha de empezar por [ if (!(temp = key.match(expKeyName))) continue; // Obtenemos el nombre de la clave (reemplazamos los espacios) subitems = key; key = temp[0]; // Comprobamos si tiene anidaciones if ((subitems = subitems.match(expKeyNodes))) { if (!result[key]) result[key] = {}; // Enlazamos desde el nivel inicial link = result[key]; // Recorremos las anidaciones for (subindex = 0, subsize = subitems.length; subindex < subsize; ++subindex) { // Si el nombre de la animación se encuentra vacío if (!(item = (item = subitems[subindex]).substr(1, item.length-2))) { // Obtenemos el número más alto de las claves del objecto `link` y le sumamos uno item = subtemp = 0; for (temp in link) { temp = Number(temp); if (expNumber.test(temp) && temp >= item) { item = temp; subtemp = 1; } } if (subtemp) ++item; } item = decodeURIComponent((item+'').replace(expSpaces, ' ')); // Si se trata del último nivel if (subindex === subsize-1) { // Establecemos su valor final link[item] = isNumber(value) ? Number(value) : value; } else { // Si la anidación no existe se crea if (!link[item]) link[item] = {}; // Reenlazamos al nuevo nivel link = link[item]; } } } else { result[key] = isNumber(value) ? Number(value) : value; } } // Convertimos los objetos con índice numérico en serie a matrices temp = function(input) { for (var index in input) if (typeof input[index] === 'object' && input[index]) temp(input[index] = _object2array(input[index])); }; temp(result); return result; }; // Funciones privadas utilizadas para no repetir código function _toString(i) { return (typeof i === 'string' || typeof i === 'number') ? ('' + i) : ''; } function _prototypeQuery(type, name, value) { var size = arguments.length-1, query = Public.unparam(this.attr(type).substr(1)); if (!size) { // query() // Devolvemos el atributo en forma de objeto return query; } else if (size === 1) { // Si es un objeto if (name && typeof name === 'object') { // query({}) // Reemplazamos el atributo entero this.attr(type, name); } else { // query('name') // De lo contrario devolvemos el valor del atributo solicidado return query[name]; } } else if (size === 2) { // Si el valor es `null` if (value === null) { // query('name', null) // Eliminamos el valor delete query[name]; } else { // query('name', 'value') // De lo contrario establecemos el nuevo valor query[name] = value; } // Actualizamos el atributo this.attr(type, query); } return this; } function _prototypeSelect(attributes, name) { // Obtenemos los atributos actuales var attr = Public.unbuild(this._attributes, (this.location instanceof Public) ? this.location._attributes : false), // Contenedor de atributos result = {}, // Marcador ready = false, // Itineración index = 0, size = attributes.length, item; // Convertimos el nombre a cadena de texto name = _toString(name).toLowerCase(); // Si el límite se encuentra en el atributo `hostname` if (name === 'hostname') { // Eliminamos el puerto y el atributo `host` que también contiene el puerto delete attr.host; delete attr.port; } // Recorremos los atributos seleccionados for (index; index < size; ++index) { // Nombre del atributo item = attributes[index]; // Si el marcador se encuentra desactivado if (!ready) { // Guardamos el atributo if (item in attr) result[item] = attr[item]; // Si ha llegado a la posición límite if (item === name) // Activamos el marcador para que deje de guardar ready = true; } } // Devolvemos la URL construida con los atributos seleccionados return Public.build(result); } function _normalizeQuery(type, input) { var str = (type === 'hash') ? '#' : '?'; // Si la entrada es un objecto if (input && typeof input === 'object') { // Convertimos la entrada al formato literal `query` input = Public.param(input); } else { // De lo contrario forzamos a que sea literal y nos deshacemos del primer carácter si es el buscado input = ((input = _toString(input)) && input.substr(0, 1) === str) ? input.substr(1) : input; } // Decodificamos y volvemos a codificar caracteres especiales en la entrada de forma segura try { input = encodeURI(decodeURI(input)); } catch (e) { input = ''; } return input ? (str + decodeURI(input)) : ''; } // Convierte un objeto con claves númericas en serie (0, 1, 2, ...) a una matriz function _object2array(input) { var index, subindex = 0, result = []; if (typeof input !== 'object' || !input) return input; for (index in input) { if (subindex++ !== Number(index)) return input; result.push(input[index]); } return result; } // Comprueba si el objeto es un elemento HTML function _isElement(input) { return (typeof HTMLElement === 'function') ? (input instanceof HTMLElement) : (input && typeof input === 'object' && input.nodeType === 1 && typeof input.nodeName === 'string'); } // Acceso público a la clase (navegador y NodeJS) return isNode ? (module.exports = Public) : (window[publicName] = Public); })((typeof module === 'object' && module && typeof module.exports === 'object' && module.exports));