Skip to content

13. Ejemplo 10 – Conversión y validación de números reales

La nueva aplicación presenta la introducción de números reales:

  • en [1], el formulario de introducción
  • en [2], la respuesta devuelta

El funcionamiento de la aplicación es similar al de la introducción de números enteros, por lo que solo comentaremos los puntos en los que difiere.

13.1. El proyecto NetBeans

El proyecto Netbeans es el siguiente:

  • en [1], las vistas de la aplicación
  • [Accueil.jsp]: la página de inicio
  • [FormDouble.jsp]: el formulario de entrada
  • [ConfirmationDouble.jsp]: la página de confirmación
  • en [2], el archivo de mensajes [messages.properties] y el archivo de configuración principal de Struts
  • en [3]:
  • [FormDouble.java]: la acción que muestra y procesa el formulario
  • [FormDouble-validation.xml]: las reglas de validación de la acción [FormDouble]. Este archivo delega estas validaciones al modelo según el método que acabamos de ver.
  • [FormDoubleModel]: el modelo de la acción [FormDouble]
  • [FormDoubleModel-validation.xml]: las reglas de validación del modelo
  • [FormDoubleModel.properties]: el archivo de mensajes del modelo
  • [example.xml]: archivo de configuración secundario de Struts

13.2. La configuración del proyecto

El proyecto se configura principalmente mediante el siguiente archivo [example.xml]:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <package name="example" namespace="/example" extends="struts-default">
    <action name="Accueil">
      <result name="success">/example/Accueil.jsp</result>
    </action>
    <action name="FormDouble" class="example.FormDouble">
      <result name="input">/example/FormDouble.jsp</result>
      <result name="cancel" type="redirect">/example/Accueil.jsp</result>
      <result name="success">/example/ConfirmationFormDouble.jsp</result>
    </action>
  </package>
</struts>

Es similar al que se ha estudiado para la introducción de números enteros.

13.3. Los archivos de mensajes

El archivo [messages.properties] es el siguiente:


Accueil.titre=Accueil
Accueil.message=Struts 2 - Conversions et validations
Accueil.FormDouble=Saisie de nombres r\u00e9els
Form.titre=Conversions et validations
FormDouble.message=Struts 2 - Conversion et validation de nombres r\u00e9els
FormDouble.conseil=Tapez les nombres r\u00e9els avec une virgule comme 10,7
Form.submitText=Valider
Form.cancelText=Annuler
Form.clearModel=Raz mod\u00e8le
Confirmation.titre=Confirmation
Confirmation.message=Confirmation des valeurs saisies
Confirmation.champ=champ
Confirmation.valeur=valeur
Confirmation.lien=Formulaire de test
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

El archivo [FormDoubleModel.properties] es el siguiente:


double.format={0,number}
double1.prompt=1-Nombre r\u00E9el
double1.error=Tapez un nombre r\u00E9el
double2.prompt=2-Nombre r\u00E9el
double2.error=Tapez un nombre r\u00E9el
double3.prompt=3-Nombre r\u00E9el >=2.64
double3.error=Tapez un nombre r\u00E9el >=2.64
double4.prompt=4-Nombre r\u00E9el <8.32
double4.error=Tapez un nombre r\u00E9el <8.32
double5.prompt=5-Nombre r\u00E9el dans l''intervalle [2.64,8.32[
double5.error=Tapez un nombre r\u00E9el dans l''intervalle [2.64,8.32[
double6.prompt=6-Nombre r\u00E9el dans l''intervalle [2.64,8.32]
double6.error=Tapez un nombre r\u00E9el dans l''intervalle [2.64,8.32]

La línea 1 desempeña un papel importante. Volveremos sobre ella más adelante.

13.4. El formulario de entrada

La vista [FormDouble.jsp] es la siguiente:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Form.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="FormDouble.message"/></h2>
    <h4><s:text name="FormDouble.conseil"/></h4>
    <s:form name="formulaire" action="FormDouble">
      <s:textfield name="double1" key="double1.prompt"/>
      <s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' :getText('double.format',{double2})}"/>
      <s:textfield name="double3" key="double3.prompt" value="%{#parameters['double3']!=null ? #parameters['double3'] : double3==null ? '' :getText('double.format',{double3})}"/>
      <s:textfield name="double4" key="double4.prompt" value="%{#parameters['double4']!=null ? #parameters['double4'] : double4==null ? '' :getText('double.format',{double4})}"/>
      <s:textfield name="double5" key="double5.prompt" value="%{#parameters['double5']!=null ? #parameters['double5'] : double5==null ? '' :getText('double.format',{double5})}"/>
      <s:textfield name="double6" key="double6.prompt"/>
      <s:submit key="Form.submitText" method="execute"/>
    </s:form>
    <br/>
    <s:url id="url" action="FormDouble" method="cancel"/>
    <s:a href="%{url}"><s:text name="Form.cancelText"/></s:a>
    <br/>
    <s:url id="url" action="FormDouble" method="clearModel"/>
    <s:a href="%{url}"><s:text name="Form.clearModel"/></s:a>
  </body>
</html>

Las líneas 13 a 18 son los seis campos de entrada de números reales. Los campos double2 a double5 tienen un atributo complejo value. Normalmente, los seis campos de entrada deberían ser los siguientes:


      <s:textfield name="double1" key="double1.prompt"/>
      <s:textfield name="double2" key="double2.prompt"/>
      <s:textfield name="double3" key="double3.prompt"/>
      <s:textfield name="double4" key="double4.prompt"/>
      <s:textfield name="double5" key="double5.prompt"/>
<s:textfield name="double6" key="double6.prompt"/>

Para resolver algunos problemas encontrados durante las pruebas, ha sido necesario complicar las cosas. Por ahora, el lector puede ignorar esta complejidad. Lo explicaremos más adelante.

13.5. La vista de confirmación

La vista de confirmación [ConfirmationFormDouble.jsp] es la siguiente:

Image

Su código es el siguiente:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Confirmation.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="Confirmation.message"/></h2>
    <table border="1">
      <tr>
        <th><s:text name="Confirmation.champ"/></th>
        <th><s:text name="Confirmation.valeur"/></th>
      </tr>
      <tr>
        <td><s:text name="double1.prompt"/></td>
        <td><s:text name="double1"/></td>
      </tr>
      <tr>
        <td><s:text name="double2.prompt"/></td>
        <td>
          <s:text name="double.format">
            <s:param value="double2"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="double3.prompt"/></td>
        <td>
          <s:text name="double.format">
            <s:param value="double3"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="double4.prompt"/></td>
        <td>
          <s:text name="double.format">
            <s:param value="double4"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="double5.prompt"/></td>
        <td>
          <s:text name="double.format">
            <s:param value="double5"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="double6.prompt"/></td>
        <td><s:text name="double6"/></td>
      </tr>
    </table>
    <br/>
    <s:url id="url" action="FormDouble!input"/>
    <s:a href="%{url}"><s:text name="Confirmation.lien"/></s:a>
  </body>
</html>

La vista [ConfirmationFormDouble.jsp] se limita a mostrar la plantilla [FormDoubleModel]. Las líneas 47-49 muestran la visualización de un número real. Queremos que esta visualización tenga en cuenta la localisation de la aplicación. Según esta, un número real no se mostrará de la misma manera en Francia (10,7) y en Gran Bretaña (10.7). Para ello, se utiliza la etiqueta <s:text> que se había utilizado hasta ahora para la internacionalización de la aplicación. Por lo tanto, esta etiqueta también sirve para la localización.

En este caso, la clave de mensaje utilizada es double.format. Esta clave se encuentra en el archivo [FormDoubleModel.properties]:


double.format={0,number}

El valor asociado a la clave no es aquí un mensaje, sino un formato de visualización:

  • 0 es un parámetro que representa el valor que se va a mostrar. En este caso, será el campo double5 de la plantilla.
  • number representa el formato numérico. Se adaptará a cada país. Sin este formato, el número 10,7 siempre se mostrará como 10,7, independientemente del país.

La etiqueta


         <s:text name="double.format">
            <s:param value="double5"/>
</s:text>

muestra el mensaje {0, number}, donde el parámetro double5 (línea 2) sustituirá al parámetro 0 del formato. De este modo, la plantilla double5 se mostrará en el formato numérico localizado.

13.6. La plantilla [FormDoubleModel]

Los campos double1 a double6 del formulario [FormDouble.jsp] se insertan en la siguiente plantilla [FormDoubleModel]:


package example;

public class FormDoubleModel {

  // constructor sin parámetros
  public FormDoubleModel() {
  }
  // campos
  private String double1;
  private Double double2;
  private Double double3;
  private Double double4;
  private Double double5;
  private String double6;

  // borrar modelo
  public void clearModel() {
    double1 = null;
    double2 = null;
    double3 = null;
    double4 = null;
    double5 = null;
    double6 = null;
  }

  // getters y setters
   ...
}
  • los campos double1 y double6 son de tipo String
  • los demás campos son de tipo Double

13.7. Validación del modelo

La validación del modelo se controla mediante dos archivos: [FormDouble-validation.xml] y [FormDoubleModel-validation.xml].

El archivo [FormDouble-validation.xml] delega las validaciones a [FormDoubleModel-validation.xml]:


<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/ejemplo-10/ejemplo/xwork-validator-1.0.2.dtd">

<validators>
  <field name="model" >
    <field-validator type="visitor">
      <param name="appendPrefix">false</param>
      <message/>
    </field-validator>
  </field>
</validators>

Ya hemos encontrado este archivo.

El archivo [FormDoubleModel-validation.xml] contiene las siguientes reglas de validación:


<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/ejemplo-10/ejemplo/xwork-validator-1.0.2.dtd">


<validators>

  <field name="double1" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="double1.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^[+|-]*\s*\d+(,\d+)*$</param>
      <param name="trim">true</param>
      <message key="double1.error"/>
    </field-validator>
  </field>

  <field name="double2" >
    <field-validator type="required" short-circuit="true">
      <message key="double2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="double2.error"/>
    </field-validator>
  </field>

  <field name="double3" >
    <field-validator type="required" short-circuit="true">
      <message key="double3.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="double3.error"/>
    </field-validator>
    <field-validator type="double" short-circuit="true">
      <param name="minInclusive">2.64</param>
      <message key="double3.error"/>
    </field-validator>
  </field>

  <field name="double4" >
    <field-validator type="required" short-circuit="true">
      <message key="double4.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="double4.error"/>
    </field-validator>
    <field-validator type="double" short-circuit="true">
      <param name="maxExclusive">8.32</param>
      <message key="double4.error"/>
    </field-validator>
  </field>

  <field name="double5" >
    <field-validator type="required" short-circuit="true">
      <message key="double5.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="double5.error"/>
    </field-validator>
    <field-validator type="double" short-circuit="true">
      <param name="minInclusive">2.64</param>
      <param name="maxExclusive">8.32</param>
      <message key="double5.error"/>
    </field-validator>
  </field>

  <field name="double6" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="double6.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^[+|-]*\s*\d+(,\d+)*$</param>
      <param name="trim">true</param>
      <message key="double6.error"/>
    </field-validator>
  </field>
</validators>
  • La regla de las líneas 12-21 comprueba que el campo de entrada double1 sigue el patrón de una expresión regular que representa un número real.
  • La regla de las líneas 23-30 comprueba que el campo de entrada double2 se pueda convertir en un número real de doble precisión.
  • La regla de las líneas 32-43 comprueba que el campo de entrada double3 se pueda convertir en un número real de doble precisión >=2,64. Cabe señalar que hay que utilizar la notación anglosajona de los números reales.
  • La regla de las líneas 45-56 comprueba que el campo de entrada double4 se pueda convertir en un número real de tipo double <8,32.
  • La regla de las líneas 58-70 comprueba que el campo de entrada double5 se pueda convertir en un número real de tipo double en el intervalo [2,64; 8,32[.
  • La regla de las líneas 72-80 comprueba que el campo double6 sigue el patrón de una expresión regular que representa un número real.

Una vez que el interceptor de validación ha procesado el archivo [FormDoubleModel-validation.xml], este ejecuta el método validate de la acción [FormDouble], si existe. Lo presentaremos junto con el resto de la acción.

13.8. La acción [FormDouble]

La acción [FormDouble] es la siguiente:


package example;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.interceptor.validation.SkipValidation;

public class FormDouble extends ActionSupport implements ModelDriven, SessionAware {

  // constructor sin parámetros
  public FormDouble() {
  }

  // modelo de la acción
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormDoubleModel());
    }
    return session.get("model");
  }

  @SkipValidation
  public String clearModel() {
    // borrado del modelo
    ((FormDoubleModel) getModel()).clearModel();
    // resultado
    return INPUT;
  }

  public String cancel() {
    // se limpia el modelo
    ((FormDoubleModel) getModel()).clearModel();
    // resultado
    return "cancel";
  }
  // SessionAware
  Map<String, Object> session;

  public void setSession(Map<String, Object> session) {
    this.session = session;
  }

  // validación
  @Override
  public void validate() {
    // ¿Es válida la entrada doble6?
    if (getFieldErrors().get("double6") == null) {
      // se sustituye la coma por el punto en la cadena double6
      String strDouble6 = (((FormDoubleModel) getModel()).getDouble6()).replace(',', '.');
      // Cadena --> doble
      double double6 = Double.parseDouble(strDouble6);
      // verificación
      if (double6 < 2.64 || double6 > 8.32) {
        addFieldError("double6", getText("double6.error"));
      }
    }
  }
}

La acción [FormDouble] se basa en el mismo modelo que la acción [FormInt]. Solo comentaremos el método validate. Recordamos que el método validate se ejecuta tras el procesamiento del archivo de validación [FormDoubleModel-validation.xml] y antes de la ejecución del método execute.

  • línea 48: si ya hay errores en el campo double6, no se hace nada más.
  • línea 50: se ha obtenido una cadena con el formato 45,67. Se ha almacenado en el campo double6 del modelo. Se sustituye la coma por el punto para obtener 45,67.
  • línea 52: la cadena 45.67 se transforma en un número real de doble precisión. Debe funcionar, ya que la cadena double6 respeta el formato de un número real.
  • línea 54: se comprueba que el número real de doble precisión obtenido se encuentra en el intervalo [2.64, 8.32].
  • línea 55: si no es así, el mensaje de error de clave double6.error se adjunta al campo double6. Este mensaje se encontrará en el archivo [FormDoubleModel.properties]. Se mostrará cuando se vuelva a mostrar el formulario con errores.

13.9. Últimos detalles

Ahora, volvamos a la complejidad de los campos de entrada del formulario [FormDouble.jsp]. Analizaremos el campo double2. El razonamiento se extiende a los campos double3 a double5, que tienen un modelo de tipo Double. Para los campos double1 y double6, que tienen un modelo de tipo String, no hay ningún problema.

El campo de entrada double2 es el siguiente:


<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' :getText('double.format',{double2})}"/>

Empecemos por la etiqueta más sencilla:


<s:textfield name="double2" key="double2.prompt"/>

y veamos qué ocurre:

  • en [1], se valida el número correcto double2. En la captura de pantalla no se ve muy bien, pero se ha escrito el número con una coma.
  • en [2], la vista de confirmación. La entrada double2 ha superado las pruebas de validación. El número se muestra con una coma.
  • En [3], volvemos al formulario
  • en [4], el formulario. Lo que no se ve bien en la captura de pantalla es que el número double2, que inicialmente era 4,32, ha pasado a ser 4,32 con un punto decimal.
  • en [5], volvemos a validar el formulario sin cambiar nada
  • en [6], se señala un error en el campo double2.

El problema es el siguiente:

  • inicialmente, en [1], la cadena de entrada «4,32» se transformó correctamente en el número real 4,32. Esto significa que la operación String --> Double se realizó con éxito y que, por lo tanto, en este sentido Struts tiene en cuenta la configuración regional, en este caso Francia.
  • El nuevo formulario [4] muestra en el campo double2 el valor real 4,32. Como no se ha localizado la visualización, la muestra por defecto al estilo anglosajón, c.a.d, con un punto decimal. Por lo tanto, en la conversión de Double a String, Struts ya no tiene en cuenta la configuración regional; de lo contrario, habría mostrado 4,32 con una coma.

Esto es, como mínimo, incoherente. Pero no importa, vamos a localizar la visualización del número 4,32. La etiqueta de entrada queda así:


<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>

El atributo value especifica el valor que se debe mostrar en el campo double2. Este es el de una expresión OGNL. Recordemos la definición de la clave double.format en el archivo [FormDoubleModel.properties]:


double.format={0,number}

El método getText('clave') permite obtener el mensaje asociado a una clave. Este se busca en el archivo correspondiente a la configuración regional actual. Así, si esta fuera es (España), la clave double.format se habría buscado en el archivo [FormDoubleModel_es.properties].

El método getText('clave', {param0, param1, ...}) permite recuperar un mensaje parametrizado. El mensaje

{0, number}

es un mensaje parametrizado por el parámetro 0. Este es un parámetro posicional. El método getText('double.format', {double2}) asignará el número double2 al parámetro 0. Finalmente, se solicita el valor de double2 en formato numérico localizado. En Francia, el número 4,56 se localizará en la cadena «4,56».

Tras esta transformación, se vuelven a realizar las pruebas.

Desde la visualización inicial del formulario, se produce una anomalía en [1]. Volvamos a la etiqueta:


<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>

En la visualización inicial, el valor del modelo double2 es null, un valor no numérico. Modificamos la etiqueta de la siguiente manera:


<s:textfield name="double2" key="double2.prompt" value="%{double2==null ? '' : getText('double.format',{double2})}"/>

Esta vez, comprobamos si es double2==null. Si es así, mostramos una cadena vacía.

Una vez realizado este cambio, reanudamos las pruebas:

Las pantallas [1] a [4] muestran que el problema que buscábamos resolver está resuelto:

  • en [1], se introduce 4,67
  • en [2], este número ha sido aceptado
  • en [3], se vuelve a mostrar como 4,67 con la coma, lo que confirma [4].

Por desgracia, los problemas no terminan ahí. Veamos la siguiente secuencia:

  • en [5], se añade un carácter para invalidar double2
  • en [6], las pruebas de validación han funcionado y se señala el error. Sin embargo, la cadena que se muestra en [6] no es la cadena errónea, sino el valor actual del modelo double2. Se ha perdido el valor introducido.

Al pasar de [5] a [6], la consulta no llega a completarse. Es detenida por el interceptor de validación. En [6] se ha mostrado el valor del modelo double2, que no ha recibido un nuevo valor debido a esta interrupción. Por lo tanto, se muestra su valor anterior, cuando debería haberse mostrado la cadena introducida. Los parámetros de una consulta están disponibles para su visualización mediante la notación #parameters['param']. Modificamos el campo de entrada double2 de la siguiente manera:


      <s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' : getText('double.format',{double2})}"/>

El valor mostrado del campo double2 se calcula de la siguiente manera: si existe el parámetro «double2», se muestra; de lo contrario, se muestra la plantilla double2. Se invita al lector a comprobar que esta nueva versión de la etiqueta resuelve los problemas encontrados.

13.10. Conclusion

Es sorprendente que introducir números reales con control de validez sea tan complicado... ¿Quizás se me ha pasado algo por alto en la documentación?