Skip to content

13. Przykład 10 – Konwersja i walidacja liczb rzeczywistych

Nowa aplikacja umożliwia wprowadzanie liczb rzeczywistych:

  • w [1], formularz wprowadzania danych
  • na [2], a zwracana odpowiedź

Aplikacja działa podobnie jak w przypadku wprowadzania liczb całkowitych, dlatego omówimy tylko te elementy, które się różnią.

13.1. Projekt NetBeans

Projekt NetBeans wygląda następująco:

  • w [1], widoki aplikacji
    • [Accueil.JSP]: strona główna
    • [FormDouble.JSP]: formularz wprowadzania danych
    • [ConfirmationDouble.JSP]: strona potwierdzenia
  • w [2], plik komunikatów [messages.properties] oraz główny plik konfiguracyjny Struts
    • w [3]:
    • [FormDouble.java]: akcja wyświetlająca i przetwarzająca formularz
    • [FormDouble-validation.xml]: reguły walidacji akcji [FormDouble]. Plik ten przekazuje te walidacje do modelu zgodnie z metodą, którą właśnie omówiliśmy.
    • [FormDoubleModel]: model akcji [FormDouble]
    • [FormDoubleModel-validation.xml]: reguły walidacji modelu
    • [FormDoubleModel.properties]: plik komunikatów modelu
    • [example.xml]: dodatkowy plik konfiguracyjny Struts

13.2. Konfiguracja projektu

Projekt jest konfigurowany głównie za pomocą następującego pliku [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>

Jest on analogiczny do tego, który został omówiony w przypadku wprowadzania liczb całkowitych.

13.3. Pliki komunikatów

Plik [messages.properties] ma następującą postać:


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}".

Plik [FormDoubleModel.properties] ma następującą postać:


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]

Ważną rolę odgrywa wiersz 1. Wrócimy do niego później.

13.4. Formularz wprowadzania danych

Widok [FormDouble.JSP] wygląda następująco:


<%@ 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>

Wiersze od 13 do 18 to sześć pól do wprowadzania liczb rzeczywistych. Pola od double2 do double5 mają złożony atrybut value. Zazwyczaj sześć pól wprowadzania danych powinno wyglądać następująco:


      <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"/>

Aby rozwiązać pewne problemy napotkane podczas testów, konieczne było zwiększenie złożoności systemu. Na razie czytelnik może zignorować tę złożoność. Wyjaśnimy ją w późniejszym terminie.

13.5. Ekran potwierdzenia

Ekran potwierdzenia [ConfirmationFormDouble.JSP] wygląda następująco:

 

Jego kod wygląda następująco:


<%@ 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>

Widok [ConfirmationFormDouble.JSP] ogranicza się do wyświetlania szablonu [FormDoubleModel]. Wiersze 47–49 pokazują wyświetlanie liczby rzeczywistej. Chcemy, aby to wyświetlanie uwzględniało ustawienia localisation aplikacji. Zgodnie z tym modelem liczba rzeczywista nie będzie wyświetlana w ten sam sposób we Francji (10,7) i w Wielkiej Brytanii (10.7). W tym celu wykorzystujemy znacznik <s:text>, którego używaliśmy dotychczas do internacjonalizacji aplikacji. Znacznik ten służy zatem również do lokalizacji.

W tym przypadku używany jest klucz komunikatu double.format. Klucz ten znajduje się w pliku [FormDoubleModel.properties]:


double.format={0,number}

Wartość powiązana z tym kluczem nie jest w tym przypadku komunikatem, lecz formatem wyświetlania:

  • 0 to parametr reprezentujący wartość, która ma zostać wyświetlona. W tym przypadku będzie to pole double5 w szablonie.
  • number określa format liczbowy. Będzie on dostosowany do danego kraju. Bez tego formatu liczba 10,7 będzie zawsze wyświetlana jako 10,7, niezależnie od kraju.

Tag


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

sprawia, że wyświetlany jest komunikat {0, number}, w którym parametr double5 (wiersz 2) zastąpi parametr 0 formatu. W ten sposób szablon double5 zostanie wyświetlony w zlokalizowanym formacie liczbowym.

13.6. Szablon [FormDoubleModel]

Pola od double1 do double6 z formularza [FormDouble.JSP] są wstawiane do następującego szablonu [FormDoubleModel]:


package example;

public class FormDoubleModel {

  // konstruktor bez parametrów
  public FormDoubleModel() {
  }
  // pola
  private String double1;
  private Double double2;
  private Double double3;
  private Double double4;
  private Double double5;
  private String double6;

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

  // metody pobierające i ustawiające
   ...
}
  • Pola double1 i double6 są typu String
  • pozostałe pola są typu Double

13.7. Walidacja modelu

Walidacja modelu jest kontrolowana przez dwa pliki: [FormDouble-validation.xml] i [FormDoubleModel-validation.xml].

Plik [FormDouble-validation.xml] przekazuje zadania walidacji do pliku [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/przykład-10/example/xwork-validator-1.0.2.dtd">

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

Ten plik już wcześniej spotkaliśmy.

Plik [FormDoubleModel-validation.xml] zawiera następujące reguły walidacji:


<!--
<!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/przykład-10/example/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>
  • reguła w wierszach 12–21 sprawdza, czy pole wprowadzania danych double1 jest zgodne ze wzorcem wyrażenia regularnego reprezentującego liczbę rzeczywistą.
  • Reguła z wierszy 23–30 sprawdza, czy pole wprowadzania danych double2 można przekonwertować na liczbę rzeczywistą typu double.
  • Reguła w wierszach 32–43 sprawdza, czy pole wprowadzania danych double3 można przekonwertować na liczbę rzeczywistą typu double >=2,64. Należy zauważyć, że należy stosować anglosaską notację liczb rzeczywistych.
  • Reguła w wierszach 45–56 sprawdza, czy pole wprowadzania danych double4 można przekonwertować na liczbę rzeczywistą typu double < 8,32.
  • Reguła z wierszy 58–70 sprawdza, czy pole wprowadzania danych double5 można przekonwertować na liczbę rzeczywistą typu double z przedziału [2,64; 8,32[.
  • Reguła w wierszach 72–80 sprawdza, czy pole double6 jest zgodne ze wzorcem wyrażenia regularnego reprezentującego liczbę rzeczywistą.

Po przetworzeniu pliku [FormDoubleModel-validation.xml] przez moduł przechwytujący walidacji, moduł ten uruchamia metodę validate akcji [FormDouble], o ile taka istnieje. Przedstawimy ją wraz z całą akcją.

13.8. Akcja [FormDouble]

Akcja [FormDouble] wygląda następująco:


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 {

  // konstruktor bez parametrów
  public FormDouble() {
  }

  // szablon akcji
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormDoubleModel());
    }
    return session.get("model");
  }

  @SkipValidation
  public String clearModel() {
    // dane modelu
    ((FormDoubleModel) getModel()).clearModel();
    // wynik
    return INPUT;
  }

  public String cancel() {
    // oczyszczamy model
    ((FormDoubleModel) getModel()).clearModel();
    // wynik
    return "cancel";
  }
  // SessionAware
  Map<String, Object> session;

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

  // weryfikacja
  @Override
  public void validate() {
    // czy podwójne wprowadzenie danych 6 jest prawidłowe?
    if (getFieldErrors().get("double6") == null) {
      // w ciągu „double6” zastępujemy przecinek kropką
      String strDouble6 = (((FormDoubleModel) getModel()).getDouble6()).replace(',', '.');
      // Ciąg znaków --> double
      double double6 = Double.parseDouble(strDouble6);
      // weryfikacja
      if (double6 < 2.64 || double6 > 8.32) {
        addFieldError("double6", getText("double6.error"));
      }
    }
  }
}

Akcja [FormDouble] jest zbudowana na tym samym wzorze co akcja [FormInt]. Omówimy jedynie metodę validate. Przypominamy, że metoda validate jest wykonywana po przetworzeniu pliku walidacyjnego [FormDoubleModel-validation.xml] i przed wykonaniem metody execute.

  • wiersz 48: jeśli w polu double6 występują już błędy, nie podejmuje się żadnych dalszych działań.
  • wiersz 50: otrzymano ciąg znaków w postaci 45,67. Został on zapisany w polu double6 modelu. Zastępuje się przecinek kropką, uzyskując wartość 45.67.
  • wiersz 52: ciąg znaków 45.67 jest przekształcany na liczbę rzeczywistą typu double. Powinno to zadziałać, ponieważ ciąg znaków double6 jest zgodny z formatem liczby rzeczywistej.
  • wiersz 54: sprawdzamy, czy uzyskana liczba zmiennoprzecinkowa typu double mieści się w przedziale [2.64, 8.32].
  • wiersz 55: jeśli tak nie jest, komunikat o błędzie o kluczu double6.error jest dołączany do pola double6. Komunikat ten będzie znajdował się w pliku [FormDoubleModel.properties]. Zostanie on wyświetlony, gdy błędny formularz zostanie ponownie wyświetlony.

13.9. Ostatnie szczegóły

A teraz wróćmy do złożoności pól wprowadzania danych w formularzu [FormDouble.JSP]. Przeanalizujemy pole double2. Rozważania te dotyczą również pól od double3 do double5, które mają szablon typu Double. W przypadku pól double1 i double6, które mają szablon typu String, nie ma żadnego problemu.

Pole wprowadzania danych double2 wygląda następująco:

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

Zacznijmy od najprostszego znacznika:

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

i zobaczmy, co się dzieje:

  • w [1] sprawdzamy poprawność liczby double2. Na zrzucie ekranu nie widać tego zbyt dobrze, ale wpisaliśmy liczbę z przecinkiem.
  • W [2] – ekran potwierdzenia. Wprowadzona wartość double2 przeszła testy walidacji. Liczba jest wyświetlana z przecinkiem.
  • w [3] wracamy do formularza
  • w [4], formularz. Zrzut ekranu nie oddaje tego dobrze, ale liczba double2, która początkowo wynosiła 4,32, zmieniła się na 4.32 z kropką dziesiętną.
  • w [5] ponownie zatwierdzamy formularz bez wprowadzania zmian
  • w [6] pojawia się komunikat o błędzie w polu double2.

Problem polega na tym, że:

  • początkowo w [1] ciąg znaków „4,32” został pomyślnie przekształcony w liczbę rzeczywistą 4,32. Oznacza to, że operacja String → Double zakończyła się powodzeniem i że w tym kontekście Struts uwzględnia ustawienia regionalne, w tym przypadku Francji.
  • Nowy formularz [4] wyświetla w polu double2 wartość liczby rzeczywistej 4.32. Ponieważ nie zlokalizowano sposobu wyświetlania, domyślnie wyświetla się ono w formacie anglosaskim, c.a.d, z kropką jako separatorem dziesiętnym. Zatem w kierunku Double → String Struts nie uwzględnia już ustawień regionalnych, w przeciwnym razie wyświetliłby 4,32 z przecinkiem.

Jest to co najmniej niespójne. Niezależnie od tego, zlokalizujemy wyświetlanie liczby 4,32. Tag wprowadzania danych przyjmuje następującą postać:


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

Atrybut value określa wartość, która ma być wyświetlana w polu double2. Jest to wartość wyrażenia OGNL. Przypomnijmy definicję klucza double.format w pliku [FormDoubleModel.properties]:


double.format={0,number}

Metoda getText('klucz') pozwala uzyskać komunikat powiązany z danym kluczem. Komunikat ten jest wyszukiwany w pliku odpowiadającym bieżącej lokalizacji. Gdyby więc lokalizacją była „es” (Hiszpania), klucz double.format zostałby wyszukany w pliku [FormDoubleModel_es.properties].

Metoda getText('klucz', {param0, param1, ...}) pozwala na odzyskanie komunikatu z określonymi parametrami. Komunikat

{0, number}

jest komunikatem zdefiniowanym przez parametr 0. Jest to parametr pozycyjny. Metoda getText('double.format', {double2}) przypisze liczbę double2 do parametru 0. Ostatecznie żądamy wartości double2 w zlokalizowanym formacie liczbowym. We Francji liczba 4,56 zostanie zlokalizowana jako ciąg znaków „4,56”.

Po tej transformacji ponownie przeprowadzamy testy.

Już przy pierwszym wyświetleniu formularza pojawia się błąd w polu [1]. Wróćmy do tagu:


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

Przy pierwszym wyświetleniu wartość szablonu double2 wynosi null, co jest wartością niecyfrową. Zmieniamy znacznik w następujący sposób:


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

Tym razem sprawdzamy, czy wartość wynosi double2==null. Jeśli tak, wyświetlamy pusty ciąg znaków.

Po wprowadzeniu tej zmiany wznawiamy testy:

Ekrany od [1] do [4] pokazują, że problem, który staraliśmy się rozwiązać, został rozwiązany:

  • w ekranie [1] wpisujemy 4,67
  • w [2] liczba ta została zaakceptowana
  • w [3] jest ona ponownie wyświetlana jako 4,67 z przecinkiem, co potwierdza [4].

Niestety, to nie koniec problemów. Przyjrzyjmy się poniższemu fragmentowi:

  • w [5] dodajemy jeden znak, aby unieważnić double2
  • w [6] testy walidacyjne zadziałały i zgłoszono błąd. Jednak ciąg wyświetlany w [6] nie jest błędnym ciągiem, lecz aktualną wartością szablonu double2. Utracono wprowadzoną wartość.

Podczas przechodzenia z [5] do [6] zapytanie nie zostaje zrealizowane do końca. Zostaje ono zatrzymane przez interceptor walidacji. W [6] wyświetlono wartość modelu double2, który nie otrzymał nowej wartości z powodu tego zatrzymania. Wyświetlana jest zatem jego poprzednia wartość, podczas gdy należało wyświetlić wprowadzony ciąg znaków. Parametry zapytania są dostępne do wglądu za pomocą notacji #parameters['param']. Zmieniamy pole wprowadzania danych double2 w następujący sposób:


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

Wyświetlana wartość pola double2 jest obliczana w następujący sposób: jeśli istnieje parametr „double2”, to jest on wyświetlany, w przeciwnym razie wyświetlany jest szablon double2. Zachęcamy czytelnika do sprawdzenia, czy ta nowa wersja znacznika rozwiązuje napotkane problemy.

13.10. Conclusion

Zaskakujące jest, że wprowadzanie liczb rzeczywistych z kontrolą poprawności jest tak skomplikowane... Być może coś przeoczyłem w dokumentacji?