Du är här: keryx/artikel/45. Hoppa till huvudinnehållet (h) Sidans menysektion:
Keryx logotype

Svårt att göra lätta siffror

Om denna artikel

Ett försök att göra stora tal lättlästa genom att gruppera siffrorna i tusental, men ändå inte svårare att förstå genom talsyntes. På slutet en funktion i PHP för att automatiskt skapa den kod som krävs.

Publicerad: 2006-06-04

Uppdaterad: 2006-06-04

 

Obs! Denna artikel är 7 år gammal och kan innehålla inaktuella uppgifter. Dubbelkolla dess innehåll!

Detta är ett försök att sy ihop en visning av siffror på en webbplats, där siffrorna är lättlästa för vanliga användare, tillgängliga för alla användare, inklusive blinda, och där det hela sköts via php, kodad enligt best practice. Jag förutsätter att du som läser detta redan är någorlunda bekant med HTML, CSS och (för dig som läser till slutet) PHP.

Vi står inför följande utmaningar:
  1. Siffror är lättast att läsa om de grupperas i tusental, typ: 123 432 659,14
  2. Sifferföljden blir svår att läsa om den radbryts. Vi kan exempelvis använda ett fast mellanslag som separator i stället för vanligt mellanslag, fast helst läser vi detta med CSS.
  3. Siffror som separeras på något sätt kommer kanske inte att läsas om korrekt av talsyntes. Vi vill att den skall säga etthundratjugotre miljoner fyrahundratrettiotvå tusen sexhundrafemtionio komma fjorton.
  4. PHP:s inbyggda funktion number_format() accepterar inte att man använder mer än ett tecken som skiljetecken mellan sina siffror.
  5. Att skapa läsbara siffror är en del av presentationslogiken i en webbapplikation. Den funktion vi skapar bör helst anropas av det mallsystem som används.

Användbarhetsaspekten

När jag läste Användbarhetsboken, så slog det mig att jag aldrig tänkt på bekymret med radbrytningar i sifferföljder. (Användbarhetsboken, sid. 97). Men visst är detta lättare att läsa:

Jag hörde talas om en man som hade en
123 432 659,14 kvadratmeter stor tomt…

Jämfört med det här:

Jag hörde talas om en man som hade en 123 432
659,14 kvadratmeter stor tomt…

Och naturligtvis också jämför med det här:

Jag hörde talas om en man som hade en
123432659,14 kvadratmeter stor tomt…

För att undvika radbryning mitt i ett stort tal, trots att det grupperats i tusental, så föreslås det i Användbarhetsboken att man använder fast mellanslag. Att använda punkt blir förbryllande för någon som är van vid engelska och dörför lätt misstolkar punkten som decimaltecknet. I engelsktalande länder är ju kommatecknet tusentalsavgränsare och punkten decimaltecken.

Fast mellanslag kan skrivas med följande entitet i HTML och XHTML: &nsp;. Denna entitet utläses Non Breaking Space. Alternativt kan vi använda den numeriska motsvarigheten  . Ett tredje alternativ är att hänvisa direkt till rätt tecken i Unicode, men det kräver att du använder just Unicode på din sida. Om du skickar texten som Latin-1 så fungerar inte Unicode-tecknet. Om du använder det inbyggda tecknet i Latin-1 i stället så fungerar det ju inte heller på alla plattformar. Under de flesta omständigheter bör vi alltså föredra att använda en entitet.

Det förordade sättet att skriva vårt tal är alltså så här: 123 432 659,14. Men eftersom det vi nu talar om är en ren designfråga, så borde man egentligen föredra CSS för att läsa detta. Det finns en mindre omtalad funktion i CSS som tycks som klippt och skuren för detta: whitespace: nowrap. Den kodsnutten stöds av alla webbläsare från Netscape 4 och Internet Explorer 5 och framåt. Även om få webbläsare stödjer alla tänkbara värden i övrigt för white-space, så stödjer de alltså detta. Genom att sätta vårt med mellanslag uppdelade tal i ett span-element, så kan vi alltså hålla ihop det på en rad, utan att använda någon entitet. Exempelvis kan vi skriva så här:

HTML
  … <span class="realNumber">123 432 659,14</span> …

CSS:
  .realNumber {
      white-space: nowrap;
   }

Tillgänglighetsaspekten

Användbarhet och tillgänglighet går oftast hand i hand. Det som är tillgängligt för alla blir oftast lättare att läsa för de flesta. Vårt tal – som nu aldrig kommer radbrytas, trots att vi grupperat siffrorna i tusental – är lättare att förstå för alla som ser siffrorna på skärmen. Användare med speciella behov, såsom synsvaga, dyslektiker och liknande är lika hjälpta som den vanlige användaren av att siffrorna grupperats. Men vi har tappat en grupp: De som använder talsyntes.

I det vanligaste talsyntesprogrammet, JAWS, så kommer troligen vöra grupperade siffror läsas upp som etthundratjugotre fyrahyndratrettiotvå sexhundrafemtionio komma fjorton. Jag har ingen tillgäng till JAWS, utan detta är bara testat med FANGS. Dör läses siffrorna upp på engelska, men jag antar att nya versioner av JAWS kan säga dem på svenska.

Utan ord som miljarder och miljoner så antar jag att talets storhet inte omedelbart blir begripligt för den blinde. Ett sätt att läsa detta skulle då kunna vara att använda sig av title-attributet: <span class="realNumber" title="123432659,14">123 432 659,14</span>. FANGS reagerar dock inte på det. Tester som gjorts av talsyntesprogram indikerar att deras stöd för title-attributet är minst sagt vacklande. Ibland kommer det användas tillsammans med elementets innehåll, ibland i stället för detsamma och ibland inte alls.

Det bästa beteendet i fallet med vört stora tal vore om talsyntesen sa vad som stod i title-attributet i stället för texten mellan span-taggarna. Med dagens talsyntesprogram verkar vi dock inte ha någon kontroll alls av den saken. Ett andra bekymmer är att seende användare som råkar föra musen över elementet kommer att få se den lilla gula pop-up-rutan, vilken är onödig i deras fall. För dem skulle vi alltså vilja stänga av den, vilket vi heller inte kan, utan en massa javaskript.

Alternativt innehåll – nej tack!

Det verkar som om detta är en situation då det inte är möjligt att skapa en optimal lösning för alla användare med hjälp av samma HTML-kod. Det finns emellertid så många negativa effekter av att skicka olika kod till olika användare, att det alternativet inte heller känns bra. Med dagens teknik så måste vi kompromissa.

En fjärran utopi

Det absolut bästa alternativet vore om det fanns en möjlighet att styra talens utseende endast med CSS. I CSS3 har vi en selektor som hänvisar till det i koden angivna språket. Den behöver vi komplettera med något som liknar mso-number-format. Vi måste anpassa oss efter språket, eftersom tusental och decimaler avgränsas på olika sätt i olika språk. Den för Microsoft Office proprietära CSS-koden mso-number-format i sin tur låter oss avgränsa siffror på en mängd sätt.

Om siffror kunde grupperas via CSS, så kunde vi kanske också lätt åstadkomma omvänd effekt, nämligen att talsyntes inte säger miljarder och miljoner när det rör sig om exempelvis personnummer. 66072-3519 utläses av de flesta som 66 07 21 35 19. I talsyntes blir det 660 tusen 721 streck 3 tusen 519. En icke önskvärd effekt och ganska förbryllande, eftersom det innehåller ett datum. Men denna utopi kräver alltså att:

  1. Alla webbläsare stödjer språk-pseudo-klassen i CSS.
  2. Alla talsyntesprogram stödjer mediareglerna i CSS.
  3. Att vi inför en helt ny regel, som kan styra hur siffror grupperas.

Internet Explorer till och med version 6 fallerar på första punkten, alla dagens talsyntesprogram fallerar på punkt nummer två. Och mig veterligen finns det inga förslag om att införa en standard som liknar Microsofts mso-number-format. Idag styr denna CSS-regel inte ens utseendet i Internet Explorer, utan är bara till för att Excel skall förstå formatteringen när data exporteras dit.

Mitt förslag till en lösning

Mitt förslag är att siffrorna skall grupperas, men att man i HTML-koden skjuter in orden miljarder och miljoner, fast dessa ord sedan döljs från seende personer med CSS, enligt följande kod (nytillkommen kod betonas):

HTML
  … <span class="realNumber">123 <span>miljoner </span>\
  432 <span>tusen </span>659,14</span>

(Obs! i verkligheten måste ovanstående två rader vara en enda.
Att samma rad egentligen fortsätter har markerats med \):

CSS:
  .realNumber {
      white-space: nowrap;
   }
  .realNumber span {
     position: absolute; /* Tag bort elementet ur det vanliga flödet av text */
     left: -5000px; /* Etablerat knep att dölja något för seende användare */
   }

Så här kommer resultatet av oformatterade HTML-kod att se ut:

123 miljoner 432 tusen 659,14

Och så här blir det med CSS-formattering påslagen:

123 miljoner 432 tusen 659,14

För tydlighetens skull så lägger vi det ena mellanslaget utanför span-elementet och det andra inuti, men eftersom vanliga mellanslag i följd ignoreras av webbläsarna, så kunde båda egentligen vara utanför.

Vad som återstår att göra med denna lösning

Testa den i fler webbläsare! I Firefox, Opera och MSIE så fungerar det som tänkt, men jag vet inte om introduktionen av ett absolut positionerat span-element kan få någon annan webbläsare att radbryta siffrorna. Jag har en testsida för detta. Kontakta mig gärna och meddela resultatet.

Så här långt har jag behandlat tillgänglighet och användbarhet och försökt skapa den genom HTML och CSS. I praktiken lär de siffror som blir aktuella för detta ofta vara skapade i någon CMS. Jag tänker därför försöka skapa en funktion i PHP som fixar detta automatiskt.

Best practice PHP

I en kommentar till php-manualens sidor om funktionen number_format kan man läsa följande:

If you use space as a separator, it will break on that space in HTML tables…

Furthermore, number_format doesn't like ' ' as a fourth parameter. I wrote the following function to display the numbers in an HTML table.

  function numberfix($number)
  {
   $number = number_format($number,0,","," ");
   return str_replace(" ", "&nbsp;", $number);
  }

For use in:
<table><tr><td><?php echo $number; ?></td></tr></table>

Personen som skrev detta kunde gärna få lagt till att problemet kan uppstå på fler ställen än i tabeller. Men jag vill ta denna idé och få den att skapa min tillgängliga HTML-kod. Jag förutsätter också att decimaltal är av variabeltypen flyttal och därför internt nyttjar punkt som decimalavskiljare.

/**
* Denna funktion går siffror lättare att läsa
*
* Den förutsätter att man döljer span-element inuti klassen realNumber med CSS.
*
* @param $number float/int Talet som skall gåras begripligt.
* @param $num_decimals int Antalet decimaler som skall visas.
* @return string Det omskrivna talet
* @version 0.10 alpha
* @todo Eventuell gruppering av decimaler om det visas fler än tre
* @copyright CC-nc-sa, Lars Gunther, Keryx
*/
function accessible_num_format($number,$num_decimals=0)
{
    if ( !is_numeric($number) || !is_numeric($num_decimals) ) {
        // Lämplig felhantering här, trigger_error, exception…
    }
    if ( $number > 1E12 ) {
        // Så här stora tal låter vi bli att hantera
        // eftersom de hanteras som flyttal
        // Vill du stödja större tal så fyll på i arrayerna
        // nedan och kör bara på 64-bitarsmaskiner?
        return number_format($number,$num_decimals,',',' ');
    }
    $integers = floor($number);

    // Vi hanterar decimaltalen först.
    // De grupperas i denna testversion aldrig
    $decimals = ''; // Tom sträng
    if ( $num_decimals > 0 ) {
        $decimals = $number - $integers;
        $decimals = round($decimals,$num_decimals) * pow(10,$num_decimals);
        $decimals = ',' . sprintf('%0'.$num_decimals.'u',$decimals);
    }

    // Förtydliga att vi skall jobba med siffrorna som textsträngar
    $integers = (string) $integers;

    $insert_position = -3; // Var skall text skjutas in, räknat från slutet
    $bignum_index = 0;  // Text som skall skjutas in från nedanstående arrayer

    // De största talen används inte, utan står med för framtida utbyggnad
    $bignums_plural   = array('tusen','miljoner','miljarder',
                              'biljoner','triljoner','triljarder');
    $bignums_singular = array('tusen','miljon','miljard',
                              'biljon','triljon','triljard');

    // mellanslag skall sättas in före span-elementets taggar.
    $in_str_start = ' <span>';
    $in_str_stop = ' </span>';

    // Antal varv loopen skall köras
    $iterations = floor(strlen($integers)/3 - 0.1 ) ;
    for ( $i = 0; $i < $iterations; $i++ ) {
        $current_size = substr($integers,$insert_position - 3, $insert_position);
        if ( 1 == $current_size ) {
            $num_word = $bignums_singular[$bignum_index];
        } else {
            $num_word = $bignums_plural[$bignum_index];
        }
        $replacement = $in_str_start . $num_word . $in_str_stop;
        // Nästa rad fungerar som en inskjutning
        $integers = substr_replace($integers,$replacement,$insert_position,0);

        // Nu måste vi räkna ut var nästa inskjutning skall ske
        $insert_position -= ( strlen($replacement) + 3 );
        $bignum_index++;
    }
    return '<span class="realNumber">' . $integers . $decimals . '</span>';
}

Denna funktion används flitigt på testsidan. Ett alternativt sätt att gåra den vore att använda vsprintf(), men eftersom det kräver att man spegelvänder både talet i sig och alla de strängar som skall skjutas in, så tror jag inte den lösningen blir så mycket bättre.

Vad fattas denna funktion?

Funktionen ovan är inte perfekt. Som den står så fungerar den bara så länge språket på webbsidan är svenska. Den är med andra ord i behov av arbete på det område som ofta kallas i18n.

När skall man använda denna funktion?

På moderna webbplatser så skiljer man mellan datalagrings-, affärs- och presentationslogik. En webbapplikation bör minst vara uppdelad i dessa tre skikt. Helt klart är det lättare att lagra och bearbeta siffror som inte innehåller mellanslag och span-element. Den naturliga platsen att anropa denna funktion är alltså i presentationslogiken.

Det mest förekommande mallsystemet för PHP är (förutom rå PHP-kod) Smarty. I normala fall kan man använda alla tillgängliga funktioner som modifierare i Smarty, och just den funktion jag skrivit har sina argument i rätt ordning för att detta skall bli enkelt:

{* Vi har en smarty-variabel som anger årtal
   och några som anger omsättning och vinst för ett visst företag *}
<p>Koncernvinst är {$year}: {$earnings|accessible_num_format}
på en omsättning om {$sales|accessible_num_format}.</p>

Om man kör Smarty i säkert läge, så måste man först registrera funktionen.

Avslutning

Jag har försökt skapa en metod som går visningen av stora siffror tillgänglig för alla och lättförståelig för så många som möjligt. En diskussion om detta kan föras på Användbarhetsbokens sida om att inte avdela tal. Vill du påpeka något för mig, så använd kontaktsidan. Förslag på förbättringar och utvärdering av någon del av mitt förslag (HTML, CSS eller PHP) mottages tacksamt.

Artikelinfo
Publicerad:2006-06-04 08:31     Författare:itpastorn
Uppdaterad:2006-06-04 08:31     Ämne:Webbteknik
Uppdaterad: 2006-06-04 08:31    © Keryx