Taal

Hier gaat het over mijn activiteiten rond (Stichting) OpenTaal, maar ook over niet direct daaraan gerelateerde aspecten van taal en talen.

Demonstratie van Hunspell spellingcontrole in een website

Voer in het vakje hieronder een woord in om het resultaat van de spellingcontrole en eventuele suggesties voor verbetering van een spelfout te zien.

Uitleg

Bij het opstarten van een programma als LibreOffice of Firefox wordt de Hunspell-bibliotheek geïnitialiseerd. Dit zorgt ervoor dat alle gegevensstructuren van Hunspell zo in het geheugen staan dat vervolgens efficiënt woorden kunnen worden gecheckt.

Als je op een webpagina wilt laten zien hoe Hunspell werkt, dan is het probleem dat er per zoekopdracht een programma (PHP-script) wordt gestart. Het is niet efficiënt als dat voor ieder te controleren woord Hunspell moet initialiseren, dat kost best veel tijd. Wat je eigenlijk wil is Hunspell één keer initialiseren, en het dan op verzoek woorden laten checken. En wel zo dat als er meerdere bezoekers van gebruik maken het van de respectieve sessies "tegelijk" verzoeken kan afhandelen zonder in de war te raken.

Mijn oplossing is de Hunspell-bibliotheek aanroepen in een programmaatje dat als server draait, en dat op een UDP-socket binnenkomende verzoeken voor het controleren van woorden beantwoordt. Het bij de webpagina horende PHP-script verstuurt dus via UDP het te controleren woord, en ontvangt het antwoord per kerende post.

Het grote voordeel van UDP ten opzichte van TCP is dat het implementeren van een zogenaamde "single process concurrent server" veel simpeler is, er hoeven immers geen meerdere connecties worden bijgehouden. Een mogelijk nadeel van UDP is dat het transport van pakketten niet gegarandeerd is, maar met eventueel verlies van pakketten is in het php-script rekening gehouden.

De relevante code van server en php-script wordt hieronder getoond.

Opmerking over builden van Hunspell: Als je de README in de source van Hunspell volgt wijst het builden zich eigenlijk vanzelf. Mogelijk moet je na het builden en installeren nog (als root) ldconfig uitvoeren om te zorgen dat de library kan worden gevonden.

spell check server

Dit is een klein C-programma dat continu draait (opstarten bijvoorbeeld in rc.local)

  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <errno.h>
  #include <string.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include "hunspell.h"

  #define MYPORT 5678

  #define MAXINBUFLEN 100
  #define MAXOUTBUFLEN 200

  int main(void)
  {
     int sockfd;
     struct sockaddr_in my_adress;
     struct sockaddr_in rem_adress;
     int adress_len, word_len, num_bytes;
     char inbuf[MAXINBUFLEN];
     char outbuf[MAXOUTBUFLEN];
     int count=0, i, result;
     Hunhandle *pHunspell;
     char **sugs;
     int numsugs;

     pHunspell=Hunspell_create("./new.aff", "./new.dic");

     if (pHunspell==NULL) {
         perror("Hunspell_create");
         exit(1);
     }

     if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
         perror("socket");
         exit(1);
     }

     my_adress.sin_family = AF_INET;
     my_adress.sin_port = htons(MYPORT);
     my_adress.sin_addr.s_addr = INADDR_ANY;
     memset(&(my_adress.sin_zero), '\0', 8);

     if (bind(sockfd, (struct sockaddr *)&my_adress, sizeof(struct sockaddr)) == -1) {
         perror("bind");
         exit(1);
     }

     printf ("spell check server running...\n");
     adress_len = sizeof(struct sockaddr);
     while (1)
     {
         if ((word_len = recvfrom(sockfd, inbuf, MAXINBUFLEN - 1 , 0, (struct sockaddr *)&rem_adress, (socklen_t *)&adress_len)) == -1) {
         perror("recvfrom");
         continue;
         }

         inbuf[word_len] = '\0';
         printf("%s\n",inbuf);
         if ((result=Hunspell_spell(pHunspell, inbuf))==0) {
              // printf("word not found\n");
              numsugs=Hunspell_suggest (pHunspell, &sugs, inbuf);
              //printf ("number of suggestions=%i\n", numsugs);
              sprintf(outbuf, "%s;0",inbuf);
              for (i=0; i<numsugs; i++) {
                  sprintf (outbuf+strlen(outbuf),",%s", sugs[i]);
              }
              Hunspell_free_list(pHunspell, &sugs, numsugs);
         }
         else {
              sprintf(outbuf, "%s;%i", inbuf, result); 
         }

         if ((num_bytes = sendto(sockfd, outbuf , strlen(outbuf) , 0, (struct sockaddr *)&rem_adress, sizeof(struct sockaddr))) == -1) {
         perror("sendto");
         } 
     }
     // should not come here 
     close(sockfd);
     return 0;
  }

De server zo compileren: gcc server.c -lhunspell-1.4

php spell check client

Dit is de html/php-pagina die aan de gebruiker wordt getoond.

Alleen de kern is hier getoond.

  Dit is een demonstratie van Hunspell 1.4.1. <br><br>
  <?php
  $buf = "";
  $name = "localhost";
  $port = 5678;

  $checkword = ($_POST['checkword']);
  echo "<br>";
  $socket = socket_create(AF_INET, SOCK_DGRAM, getprotobyname("udp"));
  if ($socket == FALSE) {
     echo "socket not created<br>";
  }
  socket_sendto ($socket, $checkword, strlen($checkword), 0, "localhost", 5678);

  $write=NULL;
  $except=NULL;
  $recword="";
  for ($i=0; $i<3; $i++) {
    $read=array($socket);
    $num_changed_sockets= socket_select ($read, $write, $except, 3);
    if ($num_changed_sockets==1) {
       socket_recvfrom($socket, $buf, 200, 0, $name, $port);
       $wordlen=strpos($buf, ";");
       $recword=substr ($buf , 0, $wordlen);
       if ($recword==$checkword) {
          break;
       }
    }
  }
  if ($recword!=$checkword) {
     echo "<i>geen respons van spellingserver</i><br>"; 
  }  
  else {
     echo substr ($buf , 0, $wordlen);
     $rescode =substr ($buf, $wordlen+1, 1);
     if ($rescode == "0") {
        echo ": niet goedgekeurd. <br>Suggesties: ";
        echo substr ($buf, $wordlen+3);
     }
     else if ($rescode == "1") {
        echo ": goedgekeurd.";
     }
     else if ($rescode == "2") {
        echo ": goedgekeurd (maar mogelijk verwarrend).";
     } 
  } 
  echo "<br>" ;
  socket_close ($socket);
  ?>

  <form method="post" action="hunspelldemo1.php">
     <p>Woord om te controleren:
         <input type="text" name="checkword">
     </p>
     <p>
         <input type="submit" name="submit" value="Controleren">
     </p>
  </form>