Código de general.js

Índice de funciones:

El código expuesto en esta página es el que inicialmente desarrollé y ha sufrido muchas modificaciones.Aquí encontrará el código actualizado de general.js.

Código de general.js

/*
     =========================================================================
     Software: general.js
     Description: Módulo que contiene funciones para uso general en el sitio.
     Contact: http://www.wextensible.com
     Copyright (c) 2010, Andrés de la Paz

     Este archivo contiene funciones de uso general que podrían ser utilizadas
     en cualquier documento del sitio.

     Las abreviaturas para navegadores son:
     IE: Internet Explorer
     FF: Firefox
     OP: Opera
     SA: Safari

     ==========================================================================
*/
/* FUNCIONES PARA CONTROLES DE FORMULARIOS ----------------------------
 * En esta sección se incluye funciones para gestionar los controles de
 * formularios, especialmente con eventos onkeypress sobre los de tipo
 * texto.
 */
/* Limita el número máximo de caracteres en un textarea.
 * Se limita al parámetro 'maximoTamanyo', el resto se corta y devuelve
 * false para no permitir más entrada de caracteres
 * - elemento: textarea donde se va a tratar
 * - eventoEntrante: el evento onkeypress
 * - maximoTamanyo: número de caracteres máximo que permite
 */
function controlaTextarea(elemento, eventoEntrante, maximoTamanyo){
    try {
        var texto = elemento.value;
        var longitud = texto.length;
        texto = texto.substring(0, maximoTamanyo-1);
        elemento.value = texto;
        //no permite entrar más caracteres
        if (longitud > maximoTamanyo) {
            var evento = eventoEntrante || window.event;
            var codigoAscii = evento.charCode || evento.keyCode;
            var teclas = [8, 37, 38, 39, 40, 46];
            for (i in teclas){
                if (codigoAscii == teclas[i]) {
                    return true;
                }
            }
            return false;
        } else {
            return true;
        }
    } catch (e) {
        return false;
    }
}
/* Evalúa número real.
 * Esta función se usa con el evento keypress de tal forma
 * que si la tecla pulsada no está en los caracteres dados
 * retorna falso y no admite el caracter en el control.
 */
function evaluaNumero(eventoEntrante){
    try {
        var evento = eventoEntrante || window.event;
        var codigoAscii = evento.charCode || evento.keyCode;
        var caracter = String.fromCharCode(codigoAscii);
        var numeros = "-.0123456789";
        return numeros.indexOf(caracter) != -1;
    } catch (e) {
        return false;
    }
}
/* Evalúa número entero.
 * Esta función se usa con el evento keypress de tal forma
 * que si la tecla pulsada no está en los caracteres dados
 * retorna falso y no admite el caracter en el control.
 */
function evaluaNumeroEntero(eventoEntrante){
    try {
        var evento = eventoEntrante || window.event;
        var codigoAscii = evento.charCode || evento.keyCode;
        var caracter = String.fromCharCode(codigoAscii);
        var numeros = "0123456789";
        return numeros.indexOf(caracter) != -1;
    } catch (e) {
        return false;
    }
}
/* Con un control de texto podemos arreglar una entrada
 * para que sea un número real.
 * - numero es un elemento con propiedad value, normalmente
 *   un input.
 */
function arreglaNumero(numero) {
    try {
        var num = numero.value;
        if (num == "") num = 0;
        var numFloat = parseFloat(num);
        if (isNaN(numFloat)) {
            numero.value = 0;
        } else {
            numero.value = numFloat;
        }
    } catch (e) {}
}
/* Con un control de texto podemos arreglar una entrada
 * para que sea un número entero.
 - numero es un elemento con propiedad value, normalmente
 *   un input.
 */
function arreglaNumeroEntero(numero) {
    try {
        var num = numero.value;
        if (num == "") num = 0;
        var numInt = parseInt(num);
        if (isNaN(numInt)) {
            numero.value = 0;
        } else {
            numero.value = numInt;
        }
    } catch (e) {}
}
/* Con un control de texto podemos arreglar una entrada
 * para que sea un número real o entero y limitarlo a
 * un rango dado.
 * - numero: un control con propiedad value, normalmente un
 *           elemento input.
 * - tipo: "entero" o "real"
 * - desde: un número entero o real mínimo
 * - hasta: un número entero o real máximo
 */
function limitaValor(numero, tipo, desde, hasta) {
    try {
        var val = 0;
        if (isNaN(numero.value)) numero.value = 0;
        if (tipo == "entero") {
            val = parseInt(numero.value);
        } else {
            val = parseFloat(numero.value);
        }
        if (val < desde) {
            numero.value = desde;
        } else if (val > hasta) {
            numero.value = hasta;
        } else {
            numero.value = val;
        }
    } catch (e) {}
}
/* FUNCIONES PARA CADENAS DE TEXTO ----------------------------
 * Funciones genéricas para manejo de texto.
 */
/* Quita espacios antes y después de una cadena y convierte
 * más de un espacio en uno solo
 */
function quitaEspacios(cadena) {
    try{
        if (cadena != "") {
            cadena = cadena.replace(/[ \s]+/g, " ");
            cadena = cadena.replace(/^[ \s]+(.+)$/, "$1");
            cadena = cadena.replace(/^(.+)[ \s]+$/, "$1");
        }
    } catch (e) {}
    return cadena;
}
/* Quita todos los espacios de una cadena
 */
function anulaEspacios(cadena){
    try{
        if (cadena != "") cadena = cadena.replace( /[ \s]+/g, "");
    } catch(e) {}
    return cadena;
}
/* Sustituye barras invertidas por barras normales
 */
function escapaBarraUri(uri) {
    try {
        return uri.replace(/\\+/g, "/");
    } catch (e) {
        return uri;
    }

}
/* Agrega una segunda barra invertida para escaparlas
 */
function agregaEscapeRegExp(cadena) {
    try{
        cadena = cadena.replace(/\(/g, "\\(");
        cadena = cadena.replace(/\)/g, "\\)");
    } catch (e) {}
    return cadena;
}
/* Escapa caracteres reservados HTML devolviendo
 * las entidades de esos caracteres
 */
function escapaHtml(cadena) {
    try{
        cadena = cadena.replace(/&/g, "&amp;");
        cadena = cadena.replace(/</g, "&lt;");
        cadena = cadena.replace(/>/g, "&gt;");
        cadena = cadena.replace(/"/g, "&quot;");
        cadena = cadena.replace(/'/g, "&#39;");        
    } catch (e) {}
    return cadena;    
}
/* Escapa caracteres reservados HTML devolviendo
 * los literales de texto de las entidades de esos
 * caracteres
 */
function escapaHtmlLiteral(cadena) {
    try{
        cadena = cadena.replace(/&/g, "&amp;amp;");
        cadena = cadena.replace(/</g, "&amp;lt;");
        cadena = cadena.replace(/>/g, "&amp;gt;");
        cadena = cadena.replace(/"/g, "&amp;quot;");
        cadena = cadena.replace(/'/g, "&amp;#39;");        
    } catch (e) {}
    return cadena;    
}
/* FUNCIONES PARA UNIFICAR COMPORTAMIENTO NAVEGADORES ------------------
 * Funciones que unifican el comportamiento distinto de los
 * navegadores con Javascript o con gestión del DOM por ejemplo.
 */
/* En la medida de lo posible hay que hacer la diferenciación sin
 * usar que tipo de navegador es, pues así no tenemos que estar
 * rehaciendo las funciones de adecuación. Pero cuando no hay otra
 * forma podemos consultar que navegador es y nos devuelve una palabra
 * de las claves señaladas en el array.
 */
function queNavegador(){
    try {
        var arr = new Array("msie", "firefox", "opera", "safari");
        for (var clave in arr){
            if (navigator.userAgent.toLowerCase().indexOf(arr[clave]) > -1)
                return arr[clave];
        }
    } catch(e){}
    return "";
}
/* Esta es la operación inversa, pasando una clave, la busca en el
 * userAgent devolviendo cierto o falso en su caso. Es recomendable usar
 * las claves anteriores pero no imprescindible, cualquier palabra en
 * el userAgent que sepamos que es exclusiva de ese navegador.
 */
function esNavegador(clave){
    try {
        return (navigator.userAgent.toLowerCase().indexOf(clave) > -1);
    } catch(e){
        return false;
    }
}
/* ENCIENDE UN EVENTO.
 * Con Explorer se enciende un evento con fireEvent de tal forma que el
 * objeto evento viene con 4 propiedades con los siguientes valores
 * predeterminados:
 * - cancelBubble = false (impide que el evento se propage por el DOM)
 * - returnValue = true (en los casos en que el evento retorne algún valor)
 * - srcElement = elemento donde se encendió el evento
 * - type = el nombre del evento (blur, click, etc)
 * El cancelBubble viene false, pero lo ponemos a true para impedir posibles
 * burbujeos. Mientras que con Mozilla el encendido del evento se define así:
 * event.initEvent(type, bubbles, cancelable). El argumento bubbles es true por
 * defecto, lo que hará que el evento "burbujee" por todo el DOM (para aquellos
 * eventos que burbujean como, por ejemplo "onmousedown"), es decir, sigue
 * aplicándose a los nodos hijos, por ejemplo. Cómo no queremos este efecto,
 * lo pasamos false. El argumento cancelable si lo pasamos true por si se
 * necesita cancelarlo.
 */
function enciendeEvento(elemento, evento) {
    try {
        if ((elemento != null)&&(evento != "")){
            if (elemento.fireEvent) {//para Explorer
                //Impide burbujeo
                window.event.cancelBubble = true;
                elemento.fireEvent(evento);
            } else if (document.createEvent) {//para Firefox
                var evt = document.createEvent("HTMLEvents")
                evento = evento.substring(2, evento.length)
                //Se cancela el burbujeo
                evt.initEvent(evento, false, true)
                elemento.dispatchEvent(evt)
            } else {
                alert("Error, no se pudo encender un evento en 'general.js'")
            }
        }
    } catch (e) {
        alert("Error en enciendeEvento de 'general.js'");
    }
}
/* Obtiene el texto interior de un elemento.
 * Tiene uso en DOM HTML y también para DOM XML con elemento.text o
 * bien con elemento.selectSingleNode
 */
function getInnerText(elemento) {
    try {
        if (elemento == null) {
            return "";
        } else if (elemento.childNodes.length > 0) {
            //si tiene hijos buscamos una forma de sacar
            //el texto
            if (elemento.innerText) {//IE,OP,SA
                return elemento.innerText;
            } else if (elemento.textContent) {//FF
                return elemento.textContent;
            } else if (elemento.text) {
                return elemento.text;
            } else {
                var nodoTexto = elemento.selectSingleNode("text()");
                if (nodoTexto != null) {
                    return nodoTexto.nodeValue;
                } else {
                    return "";
                }
            }
        } else {//TODOS
            //si no tiene hijos no tiene texto
            return "";
        }
    } catch(e) {
        return "";
    }
}
/* Retorna el elemento o nulo si se pone o no el texto
 * Hay que observar que si es un elemento vacío no podrá
 * ponerse texto como en un <input />.
 * Tiene uso en DOM HTML y también para DOM XML con elemento.text o
 * bien con elemento.selectSingleNode.
 */
function setInnerText(elemento, texto) {
    try{
        if (elemento != null){
            if (elemento.innerText) {//IE,OP,SA
                elemento.innerText = texto;
            } else if (elemento.textContent) {//FF
                elemento.textContent = texto;
            } else if (elemento.innerHTML) {
                //si no hay otra posibilidad lo ponemos con
                //innerHTML que si lo ejecutan IE, OP, SA y FF
                elemento.innerHTML = texto;
            } else if (elemento.text) {
                elemento.text = texto;
            } else {
                var nodoTexto = elemento.selectSingleNode("text()");
                if (nodoTexto != null) {
                    nodoTexto.nodeValue = texto;
                } else {
                     return null;
                }
            }
        }
        return elemento;
    } catch(e){
        return null;
    }
}
/* Para obtener el innerHTML y también el propio literal
 * HTML del elemento. Es lo que hace IE con outerHTML que
 * no la hace FF.
 */
function getOuterHTML(elemento) {
    try{
        if (elemento == null) {
            return "";
        } else if (elemento.outerHTML) {//IE
            return elemento.outerHTML;
        } else {//FF
            var tag = elemento.tagName.toLowerCase();
            var outer =  "<" + tag;
            var atributos = elemento.attributes;
            var cadenaAtributos = "";
            for (var i=0; i<atributos.length; i++) {
                cadenaAtributos += " " + atributos[i].nodeName + "=\"" +
                atributos[i].nodeValue + "\"";
            }
            outer += cadenaAtributos;
            var conten = "";
            //La función contenido necesita del dtdar.js para saber
            // si es un elemento vacío. Si no está el dtdar.js entonces
            //lo ignora al captar el error
            try {
                conten = contenido("ELEMENT", tag);
                if (conten == "EMPTY") {
                    outer += " />";
                } else {
                    outer += ">";
                }
            } catch (e){
                outer += ">";
            }
            outer += elemento.innerHTML;
            if (conten != "EMPTY") {
                outer += "</" + tag + ">";
            }
            return outer;
        }
    } catch(e) {
        return "";
    }
}
/* Lo mismo pero para poner el outerHTML que FF no
 * lo hace
 */
function setOuterHTML(elemento, cadenaHtml) {
    try {
        if (elemento != null) {
            if (elemento.outerHTML) {//IE
                elemento.outerHTML = cadenaHtml;
            } else {//FF
                var frag = rango.createContextualFragment(cadenaHtml);
                elemento.parentNode.replaceChild(frag, elemento);
            }
        }
    } catch(e) {}
}
/* Para obtener el nodo padre.
 */
function nodoPadre(nodo) {
    try {
        if (nodo == null) {
            return null;
        } else if (nodo.parentElement) {//IE
            return nodo.parentElement;
        } else {//FF
            return nodo.parentNode;
        }
    } catch(e){
        return null;
    }
}
/* Para eliminar un nodo devolviendo el
 * nodo eliminado.
 */
function eliminaNodo(nodo, eliminaContenido) {
    try {
        if (nodo == null) {
            return null;
        } else if (nodo.removeNode) {//IE
            return nodo.removeNode(eliminaContenido);
        } else {//FF
            return nodoPadre(nodo).removeChild(nodo);
        }
    } catch(e){
        return null;
    }
}
/* Para obtener el atributo colspan de una celda
 */
function getColspan(celda) {
    try {
        if (celda == null) {
            return 0;
        } else {
            var colspanStr = celda.getAttribute("colspan")
            if (colspanStr == null) {//FF
                return 1;
            } else {//IE
                return parseInt(colspanStr);
            }
        }
    } catch(e){
        return 0;
    }
}
/* Para obtener el atributo rowspan de una celda
 */
function getRowspan(celda) {
    try {
        if (celda == null) {
            return 0;
        } else {
            var rowspanStr = celda.getAttribute("rowspan")
            if (rowspanStr == null) {//FF
                return 1;
            } else {//IE
                return parseInt(rowspanStr);
            }
        }
    } catch(e){
        return 0;
    }
}
/* Para obtener el texto de un rango
 * Sólo para IE y FF
 */
function rangoText(rango) {
    try {
        if (rango == null) {
            return "";
        } else if (rango.text) {
            return rango.text;
        } else if (rango.toString) {
            return rango.toString();
        } else {
            return "";
        }
    } catch(e){
        return "";
    }
}
/* Devuelve un objeto CSSStyleDeclaration que contiene las propiedades
 * de estilo y valores actuales o computados, usando las externas de
 * los archivos CSS y las internas declaradas en el elemento <style>.
 * Con Firefox puede usarse window o document.defaultView
 * Al objeto devuelto puede accederse con objeto[i] o bien con un bucle
 * for(var nombrePropiedad in objeto){...}
 * obteniendo los string de los nombres de la propiedad como "fontSize"
 * (sin guiones). Luego podemos consultar el valor con
 * propiedadEstiloActual(objeto, nombrePropiedad).
 * Probado para IE8, FF3.6, OP10.6
 */
function objetoEstiloActual(elemento){
    try {
        if (elemento != null) {
            if (elemento.currentStyle) {//IE
                return elemento.currentStyle;
            } else if (document.defaultView.getComputedStyle) {//FF, OP
                return document.defaultView.getComputedStyle(elemento, null);
            } else {
                return null;
            }
        } else {
            return null;
        }
    } catch(e){
        return null;
    }
}
/* Con el objetoEstiloActual() anterior podemos obtener el valor de una
 * propiedad, que debe venir sin guiones como "fontSize". Para Firefox es
 * necesaria con guiones, lo que se consigue con descambiaGuiones().
 * Funciona con IE8, FF3.6, OP10.6 pero no funciona con SA4.0
 */
function propiedadEstiloActual(objetoEstilo, propiedad){
    try {
        if ((objetoEstilo != null)&&(propiedad != "")) {
            if (objetoEstilo.getAttribute) {//IE
                //necesita "fontSize"
                return "" + objetoEstilo.getAttribute(propiedad);
            } else if (objetoEstilo.getPropertyValue) {//FF, OP
                //necesita "font-size"
                var propConGuiones = descambiaGuiones(propiedad);
                var valor = "" +
                objetoEstilo.getPropertyValue(propConGuiones);
                //Con Safari no funciona pues extrae cosas con
                //valores nulos
                if (valor != null) {
                    return valor;
                } else {
                    //Por si alguno necesita sin guiones "fontSize"
                    valor = objetoEstilo.getPropertyValue(propiedad);
                    if (valor != null) {
                        return valor;
                    } else {
                        return "";
                    }
                }
            } else {
                return "";
            }
        } else {
            return "";
        }
    } catch(e){
        return "";
    }
}
/* Obtiene el estilo actual de una propiedad de un elemento consultando
 * directamente al objeto CSSStyleDeclaration. La propiedad debe venir con
 * guiones como "font-size". Para IE se le quitan los guiones con
 * cambiaGuiones().
 */
function estiloActual(elemento, propiedad){
    try {
        if (window.getComputedStyle){//FF, Opera (con guiones)
            //también puede ser window.getComputedStyle...
            return document.defaultView.getComputedStyle(elemento,null).getPropertyValue(propiedad);
        } else if (elemento.currentStyle){//IE (sin guiones)
            var propSinGuiones = cambiaGuiones(propiedad);
            if (elemento.currentStyle.getAttribute) {//IE
                return elemento.currentStyle.getAttribute(propSinGuiones);
            } else {//
                return elemento.currentStyle[propSinGuiones];
            }
        } else {
            return "";
        }
    } catch(e) {
        return "";
    }

}
/* Cambia "font-size" a "fontSize" para usar en las funciones
 * anteriores y obtener el estilo actual
 */
function cambiaGuiones(cadena){
    try {
        var temp = "";
        if (cadena.indexOf("-")>-1){
            for (var i=0; i<cadena.length; i++) {
                var car = cadena.substring(i,i+1);
                if (car == "-"){
                    var carSgte = cadena.substring(i+1,i+2)
                    temp += carSgte.toUpperCase();
                    i++;
                } else {
                    temp += car;
                }
            }
            temp = temp.replace(/\-/g, "");
        } else {
            temp = cadena;
        }
        return temp;
    } catch (e) {
        return cadena;
    }
}
/* Cambia "fontSize" a "font-size" para usar en las funciones
 * anteriores y obtener el estilo actual
 */
function descambiaGuiones(cadena){
    try {
        var mayus = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        var temp = "";
        for (var i=0; i<cadena.length; i++) {
            var car = cadena.substring(i,i+1);
            if (mayus.indexOf(car) > -1){
                temp += "-" + car.toLowerCase();
            } else {
                temp += car;
            }
        }
        return temp;
    } catch (e) {
        return cadena;
    }
}
/* aplica getElementsByClassName para IE que no lo
 * ejecuta y FF sí.
 */
function arrayClassName(nombreClase, elemento){
    try {
        function recorreDom(el, arr){
            if (el.className == nombreClase) arr.push(el);
            var hijos = el.childNodes;
            for (var i=0; i<hijos.length; i++){
                recorreDom(hijos[i], arr);
            }
            return arr;
        }
        var nodo = document.body;
        if (elemento != null) nodo = elemento;
        if (document.getElementsByClassName){ //FF
            return nodo.getElementsByClassName(nombreClase);
        } else { //IE
            var arr = new Array;
            return recorreDom(nodo, arr);
        }
    } catch (e) {
        return null;
    }        
}
/* Esto recoge un objeto window.event y devuelve
 * un Object con las propiedades:
 * - Object.event: el propio objeto evento del argumento
 * - Object.element: el elemento que lo causó
 * Coordenadas respecto al elemento
 * - Object.x
 * - Object.y
 * Coordenadas respecto a la página (a la ventana)
 * - Object.pagx
 * - Object.pagy
 * El código ASCII de la tecla pulsada
 * - Object.keyAscii =  evt.charCode || evt.keyCode; 
 */
function recogeEvento(evento){
    try {
        var evt = event || window.event;
        var X = 0;
        var Y = 0;
        var pagX = 0;
        var pagY = 0;
        var elemento = null;
        if (evt.srcElement) {//IE,SA,CH,OP
            elemento = evt.srcElement;
        } else {//FF
            elemento = evt.target;
        }
        if (evt.x != undefined){//IE,SA,CH,OP
            pagX = evt.x;
            pagY = evt.y;
        } else {//FF
            pagX = evt.clientX;
            pagY = evt.clientY;
        }
        if (window.scrollX != undefined){//FF,OP,SA,CH
            pagX += window.scrollX;
            pagY += window.scrollY;  
        } else {//IE
            pagX += document.body.scrollLeft;
            pagY += document.body.scrollTop;           
        }
        X = pagX - elemento.offsetLeft;                    
        Y = pagY - elemento.offsetTop;                
        var objeto = new Object;
        objeto.event = evt;
        objeto.element = elemento;
        objeto.x = X;
        objeto.y = Y;
        objeto.pagx = pagX;
        objeto.pagy = pagY;
        objeto.keyAscii =  evt.charCode || evt.keyCode; 
        return objeto;
    } catch (e){
        return null;
    } 
}
/* FUNCIONES PARA ADECUAR ESTILO CSS NO ESTANDAR ------------------
 * Las propiedades de estilo que aún están en fase de borrador se
 * incluyen dinámicamente y así podemos validar los documentos.
 */
/* Devuelve una cadena "propiedad: valor" según los argumentos
 * pasados. Para evitar que ciertos navegadores como Opera
 * detecten la adjudicación dinámica de una propiedad no
 * esperada, hemos también de filtrarlas por navegador.
 * - propiedad: string con el nombre de la propiedad
 * - valor: el valor de la propiedad
 * Se puede incorporar a un elemento con:
 * elemento.style.cssText += ";" + estiloNoCss(propiedad, valor)
 */
function estiloNoCss(propiedad, valor) {
    var estilo = "";
    switch (propiedad){
        case "opacity": {//según CSS3 opacity
            if (esNavegador("msie")){//IE
                estilo = "filter: alpha(opacity=" + (valor * 100) + ")";            
            } else {//FF, OP, SA?
                estilo = "opacity: " + valor;
            }
            break;
        }
        case "user-select": {//CSS3 user-select
            if (esNavegador("firefox")){//Sólo FF
                estilo = "-moz-user-select: " + valor;
            }
            break; 
        }

    }    
    return estilo;    
}
/* FUNCIONES GENÉRICAS DEL SITIO WEB ------------------
 * Funciones varias que se usan en muchas páginas del sitio
 */
/* Función para cambiar el tamaño de fuente de toda la página.
 * Para que funcione establecemos en estilo externo en "cabeza_pie.css"
 * body{font-size: 1em} y también div#total{font-size: 1em} que es
 * el tamaño de la fuente de partida. Luego con este script le cambiamos
 * el tamaño al <div id="total"> que es quién contiene toda la página.
 * Sin embargo para acceder al estilo declarado externo necesitamos
 * usar elemento.currentStyle.fontSize en IE mientras que en FF se usa
 * otra cosa (getComputedStyle). Por lo tanto para evitar estas diferencias
 * ponemos en el HTML el elemento <div id="total" style="font-size: 1em">
 * volviendo a repetir el estilo. Así en todos los navegadores accedemos
 * con elemento.style.fontSize al valor del tamaño de la fuente.
 */
function cambiarFuente() {
    try {
        var boton = document.getElementById("boton-aaa");
        var total = document.getElementById("total")
        var fs = total.style.fontSize;
        if (fs == "0.75em") {
            total.style.fontSize = "1em";
            boton.title = "Zoom 125%";
        } else if (fs == "1em") {
            total.style.fontSize = "1.25em";
            boton.title = "Zoom 150%";
        } else if (fs == "1.25em") {
            total.style.fontSize = "1.5em";
            boton.title = "Zoom 75%";
        } else {
            total.style.fontSize = "0.75em";
            boton.title = "Zoom 100%";
        }
    } catch (e) {}
}