Bug i JavaScript eller er weekenden bare for nær?

Jeg er stødt på noget rimelig besynderligt i dag – JavaScript fortolkeren i IE6, IE7 og FF2 opfører sig nemlig tilsyneladende sært når man opretter selvrefererende JavaScript Object Notation (JSON) objekter.

JSON screenshot fra Firebug

Betragt den første variabeltilskrivning jeg udfører i Firebug konsollen i Firefox:

>>> var json = { "foo": 42 };

Den evaluerer til et objekt der indeholder:

>>> json
Object foo=42

Hvis vi gerne vil have oprettet et JSON objekt der refererer til sig selv, kan man angive f.eks.:

>>> var json = { "foo": 42, "selfRef": json.foo };

Hvilket evaluerer til et objekt der indeholder:

>>> json
Object selfRef=42 foo=42

Omskriver man det en smule, så property’en foo tilgås på array form bliver værditilskrivningen til:

>>> var json = { "foo": 42, "selfRef": json["foo"] };

Og det går det lige så fint med, da det også evaluerer til:

>>> json
Object selfRef=42 foo=42

Filmen lader dog til at knække helt hvis den nøgle man anvender er et tal, hvilket er fint gyldigt iøvrigt:

>>> var json = { "2": 42, "selfRef": json["2"] };

Nu har vi nemlig kun objektet:

>>> json
Object 2=42

Så jeg tænkte at jeg enten havde fundet en generel implementationsfejl i JavaScript fortolkerne i IE og FF (hvilket må siges at være overvejende usandsynligt), eller også er mine forsøg herover fejlbehæftede. Sidste mulighed viste sig at være årsagen.

Det går nemlig fint at erklære cykliske JSON strukturer hvis de basis data der skal indgå i selvreferencerne allerede er erklærede på forhånd. Og det er de netop hvis man afprøver dette forløb:

>>> var json = { "foo": 42 };
>>> var json = { "foo": 42, "selfRef": json.foo };
>>> json
Object selfRef=42 foo=42

Hvis json.foo ikke indledningsvist havde været erklæret fejler den selvreferende erklæring:

>>> var json = { "foo": 42 };
>>> var json = "";
>>> var json = { "foo": 42, "selfRef": json.foo };
>>> json
Object foo=42

Så det var udelukkende på grund af weekenden der var lige om hjørnet, og fordi forsøget på at indsætte en hidtil utilskrevet talnøgle i JSON strukturen gjorde at “fejlen” fremstod. Man kan dog fortsat være lidt tvivlende overfor hvordan en tilskreven property bare kan forsvinde ud i den blå luft. Man kunne forvente at selfRef som minimum var tilstede og blot indeholdt værdien "undefined" eller endnu bedre null. Alternativt havde en kastet exception også været ok.

XSLT konvertering til store/små bogstaver

Den almene metode til at konvertere strenge til f.eks. små bogstaver (minuskler) for at udføre en sammenligning, eller blot for at præsentere det transformerede resultat på den vis, er at benytte sig af XPath funktionen translate. Flere steder finder man netop denne løsning implementeret og den fremføres ofte som en god måde at konvertere strenge til f.eks. minuskler, blandt andet i XSLT 2nd Edition Programmer’s Reference af Michael Kay, der må siges at være “biblen” indenfor XSLT-området.

En XSLT transformationsfil der skal kunne foretage konvertering af danske strenge vil typisk indeholde:

<!-- translate strenge -->
<xsl:variable name="lcletters">abcdefghijklmnopqrstuvwxyzæøå</xsl:variable>
<xsl:variable name="ucletters">ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ</xsl:variable>
...
<!-- til versaler -->
<xsl:value-of select="translate($toconvert,$lcletters,$ucletters)"/>
...
<!-- til minuskler -->
<xsl:value-of select="translate($toconvert,$ucletters,$lcletters)"/>

Problemet med translate-løsningen er, at den ikke tager højde for andre tegn end dem man eksplicit angiver – og det kan være en stor opgave hvis tegnsættet der anvendes er UTF-8 eller blot ISO-Latin-1. Der er henholdsvist flere tusinde / hundrede tegn i disse tegnsæt. Tegn der ikke er angivet slipper uændrede igennem transformationen med translate.

Med nedenstående XML-datafil i hånden vil førnævnte translate-strenge fejle, da “umlaut”-tegnene ikke er dækkede.

<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item>
    <title>Dette er danske tegn æøåÆØÅ</title>
  </item>
  <item>
    <title>Dette er andre tegn üöäÜÖÄ</title>
  </item>
</items>

En anden metode jeg derfor har nørklet med i dag, der dog kun fungerer i Internet Explorer, er at indlejre et JavaScript i XSLT transformationsfilen, og herfra udnytte JavaScript’s toLowerCase() funktion. Firefox har endnu ikke understøttelse for XSLT 1.1 <xsl:script>, hvorimod Internet Explorer tilbyder en Microsoft-proprietær implementation der fungerer ganske udemærket, nemlig <msxsl:script>.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:tns="urn:thisnamespace:tns"
exclude-result-prefixes="tns msxsl">
<msxsl:script language="JavaScript" implements-prefix="tns">
function lCase(item) {
  return (''+item).toLowerCase(); 
}
</msxsl:script>
<xsl:template match="/">
  <xsl:for-each select="items/item">
    <xsl:variable name="aparam" select="title"/>
    <xsl:value-of select="tns:lCase(string($aparam))"/><br />
  </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Du kan ved brug af, Internet Explorer (version 6 eller nyere), se resultatet af at udføre XSL + JavaScript transformationen her. Det forventede resultat er at samtlige tegn i XML-datafilen er konverteret til minuskler.

Jeg leder fortsat efter en elegant cross-browser løsning, men for nuværende er ovenstående dækkende pga. krav til opgaven specifikt kun er rettet imod slutbrugere der anvender Internet Explorer.