<% /*
     $Revision$
     $Id$
*/ %>
<!-- #include file="../shared/FlexFiles.inc" -->
<%
// Alleen nog in gebruik voor CAD/PrintFG.asp, daar nog eens uit-schrijven

// Stuur het resultaat rechtstreeks naar het Response object, essentieel om encoding te sturen
// params: mode, srtnotificatie
function STR2Stream(xmlstr, xslfile, Stream, params)
{
  params = params || {};
  var xslfile = Server.MapPath(xslfile);
  var fso = Server.CreateObject("Scripting.FileSystemObject");

  var xslt = new ActiveXObject("Msxml2.XSLTemplate.6.0");
  if (S("fac_msxml2domdocument") == 0)
    var xsldoc = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.6.0");
  else
    var xsldoc = new ActiveXObject("Msxml2.DOMDocument.6.0");
  var xslproc;
  xsldoc.async = false;
  xsldoc.setProperty("AllowXsltScript", true);
  xsldoc.load(xslfile);

  if (xsldoc.parseError.errorCode != 0)
  {
     var myErr = xsldoc.parseError;
     __Log("XML2STR3 Error in stylesheet ("+xslfile+"): " + myErr.reason);
     Response.Write ("<html><body><b>Error in stylesheet ("+xslfile+"):</b><br>" + myErr.reason + "</body></html>");
  }
  else
  {
     xsldoc.resolveExternals = true; // anders op de volgende regel al:
                                     // A reference to variable or parameter 'srtnotificatiecode' cannot be resolved
     xslt.stylesheet = xsldoc;
     var xmldoc = new ActiveXObject("Msxml2.DOMDocument.6.0");
     xmldoc.async = false;
     xmldoc.loadXML(xmlstr);
     if (xmldoc.parseError.errorCode != 0)
     {
        var myErr = xmldoc.parseError;
        __Log("XML2STR3 Error in xmlfile: " + myErr.reason);
        Response.Write ("<html><body><b>Error in xmlfile:</b><br>" + myErr.reason + "</body></html>");
     }
     else
     {
        xslproc = xslt.createProcessor();
        xslproc.input = xmldoc;
        xslproc.addParameter("mode", params.mode);
        xslproc.addParameter("srtnotificatiecode", params.srtnotificatie);
        xslproc.addParameter("rooturl", rooturl);
        xslproc.addParameter("custpath", custpath);

        // xslproc.output = Stream; niet rechtstreeks, we moeten nog nabewerken voor QRC
        __Log("Transform using {0}".format(xslfile));
        var startTime = us_timer();
        xslproc.transform();
        var ms_taken = (us_timer() - startTime) / 1000;
        __Log("Transform took {0} seconds".format(Math.round(ms_taken / 100) / 10));
        p_bodyhtml = xslproc.output;

        // Let op: dezelfde code in puo_xmltools.js
        // eerst lockeduser(xmlnode,key,bedrijf_key) vervangen
        // lockeduser(opdracht,12345,910) met 12345 opdracht_key en 910 bedrijf_key
        var hmacs = p_bodyhtml.match(/(lockeduser\([^\)]*\))/g); // heeft nu array van lockeduser(opdracht,12345,910)
        for (var i =0; hmacs && i < hmacs.length; i++)
        {
            var params = hmacs[i].match(/\(([^,]+)\,(\d+),(\d+)\)/);
            if (!params)
                throw { description: "lockeduser is used in xsl but empty opdracht_key or bedrijf_key" };
            if (params.length == 4)
            {
                var xmlnode = params[1]; // We ondersteunen alleen nog maar 'opdracht'
                var key = params[2];
                var bedrijf_key = params[3];
                // Let op: we pakken altijd het eerste bedrijfadres
                var sql = "SELECT prs_bedrijfadres_key,"
                        + "       prs_bedrijfadres_lockuser_key,"
                        + "       prs_bedrijfadres_locksecret"
                        + "  FROM prs_bedrijfadres"
                        + " WHERE prs_bedrijf_key = " + bedrijf_key
                        + "   AND prs_bedrijfadres_startdatum <= SYSDATE"
                        + "   AND prs_bedrijfadres_lockuser_key IS NOT NULL"
                        + " ORDER BY prs_bedrijfadres_startdatum DESC";
                var oRs = Oracle.Execute(sql);
                if (oRs.Eof)
                {   // Ergens in de bon staat
                    //     <xsl:attribute name="href">https://aqqa.facilitor.nl/lockeduser(opdracht,<xsl:value-of select="key"/>,
                    //                <xsl:value-of select="uitvoerende//bedrijf/key"/>)</xsl:attribute>
                    // maar bij de leverancier is geen 'Vaste gebruiker' en 'Gedeeld geheim' ingevuld
                    INTERNAL_ERROR_MISSING_LOCKSECRET;
                }
                var bdradr_key = oRs("prs_bedrijfadres_key").Value;
                var lockuser_key = oRs("prs_bedrijfadres_lockuser_key").Value;
                var locksecret = oRs("prs_bedrijfadres_locksecret").Value;
                oRs.Close()
                if (lockuser_key && !locksecret) {
                    var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
                    locksecret = oCrypto.hex_random(32);
                    var sql = "UPDATE prs_bedrijfadres"
                            + "   SET prs_bedrijfadres_locksecret = " + safe.quoted_sql(locksecret)
                            + " WHERE prs_bedrijfadres_key = " + bdradr_key;
                    Oracle.Execute(sql);
                }
                var url = "?u={0}&k={1}&lbdr={2}".format(xmlnode, key, bdradr_key);
                var newurl = protectQS.create(url, { sleutel: locksecret, no_user_key: true });
                p_bodyhtml = p_bodyhtml.replace(hmacs[i], newurl);
            }
        }

        // Let op: dezelfde code in puo_xmltools.js
        // nu fclttoegang(xmlnode,key) vervangen
        // xmlnode mag zijn 'reservering', 'afspraak' of 'opdracht' zonder de quotes
        // fclttoegang(reservering,12345) geeft toegang tot rsv_ruimte_key 12345 (wat niet reservering 12345/1 hoeft te zijn)
        var hmacs = p_bodyhtml && p_bodyhtml.match(/(fclttoegang\([^\)]*\))/g); // heeft nu array van fclttoegang(reservering,12345)
        for (var i =0; hmacs && i < hmacs.length; i++)
        {
            var params = hmacs[i].match(/\(([^,]+)\,(\d+)\)/);
            if (!params)
                throw { description: "fclttoegang is used in xsl but empty xmlnode or key" };
            if (params.length == 3)
            {
                var xmlnode = params[1]; // We ondersteunen alleen nog maar 'opdracht'
                var key = parseInt(params[2], 10);
                // Let op: zoek de laagste fac_tracking_key als (onderdeel van) de 'sleutel'
                var sql = "SELECT MIN(fac_tracking_key) fac_tracking_key,"
                        + "       MIN(fac_tracking_datum) fac_tracking_datum"
                        + "  FROM fac_tracking ft, fac_srtnotificatie fsn"
                        + " WHERE ft.fac_srtnotificatie_key = fsn.fac_srtnotificatie_key"
                        + "   AND fac_srtnotificatie_xmlnode = " + safe.quoted_sql(xmlnode)
                        + "   AND fac_tracking_refkey = " + key;
                var oRs = Oracle.Execute(sql);
                if (oRs.Eof)
                {   // // Ergens in de bon staat zo'n fclttoegang maar geen tracking gevonden?
                    throw { description: "fclttoegang is used in xsl but no tracking found for " + xmlnode + "/" + key};
                }
                var locksecret = String(oRs("fac_tracking_key").Value) + "|" + toISODateTimeString(new Date(oRs("fac_tracking_datum").Value));
                oRs.Close();
                var url = "?u=toegang&k={1}&xmlnode={0}".format(xmlnode, key);
                var newurl = protectQS.create(url, { sleutel: locksecret, no_user_key: true });

                p_bodyhtml = p_bodyhtml.replace(hmacs[i], newurl);
            }
        }


        var startPos = p_bodyhtml.indexOf("qrc=") + 4;
        var imgstart = p_bodyhtml.lastIndexOf("<img", startPos); // moet wel binnen een <img> vallen
        var imgend   = p_bodyhtml.indexOf(">", imgstart);
        var quote = p_bodyhtml.substr(startPos, 1);
        var eindPos =  p_bodyhtml.indexOf(quote, startPos + 2);
        if (startPos > 4 && eindPos > startPos && imgstart > 0 && imgend > startPos )
        {
            var qrc = p_bodyhtml.substring(startPos + 1, eindPos);
            qrc = qrc.replace(/\&amp;/g, "&"); // Het was nog HTML/XML-encoded
            var xc = new ActiveXObject("SLNKDWF.QRCode");
            xc.Text = qrc;
            __Log("Creating QRC: " + qrc);
            var oXML = new ActiveXObject("Msxml2.DOMDocument.6.0");
            oNode = oXML.createElement("encodeddata");
            oNode.dataType = "bin.base64"; // Zeer snelle oplossing
            oNode.nodeTypedValue = xc.GetAsPNG();
            var b64 = oNode.text;

            var newbody = p_bodyhtml.substr(0, startPos - 4) + "src=\"data:image/png;base64," + b64 + "\"" + p_bodyhtml.substr(eindPos + 1);
            p_bodyhtml = newbody;
        }

        // Nu frc-codering opzoeken <img frc="M=SML:K=34417:R=R128x128"/>
        var startPos = p_bodyhtml.indexOf("frc=") + 4;
        var imgstart = p_bodyhtml.lastIndexOf("<img", startPos); // moet wel binnen een <img> vallen
        var imgend   = p_bodyhtml.indexOf(">", imgstart);
        var quote = p_bodyhtml.substr(startPos, 1); // enkele of dubbele quote
        var eindPos =  p_bodyhtml.indexOf(quote, startPos + 2);
        while (startPos > 4 && eindPos > startPos && imgstart > 0 && imgend > startPos)
        {
            var flexcode = p_bodyhtml.substring(startPos + 1, eindPos);
            if (flexcode.match(/^M=CAD/)) // Die roepen we gewoon on-the-fly op
            {
                // <xsl:element name="img">
                //   <xsl:attribute name="frc">M=CAD&amp;labelPos=2&amp;vKey=<xsl:value-of select="//afspraak/plaats/regio/district/locatie/gebouw/verdieping/key"/>&amp;highlight=<xsl:value-of select="//afspraak/plaats/regio/district/locatie/gebouw/verdieping/ruimte/key"/>&amp;mode=0&amp;discs=981&amp;sizeX=700&amp;sizeY=400&amp;offsetX=10&amp;offsetY=10&amp;scale=950&amp;paperColor=16777215
                //   </xsl:attribute>
                // </xsl:element>

                var newbody = p_bodyhtml.substr(0, startPos - 4) + "src=\"../cad/mySlnk2IMG.asp?" + flexcode.substr(10) + "\"" + p_bodyhtml.substr(eindPos + 1);
                p_bodyhtml = newbody;
            }
            else
            {
                var props = flexProps2(flexcode);
                if (!props.files.length)
                {   // Geen bestanden gevonden? Dan hele img-tag gewoon er uit halen
                    var eindPos = p_bodyhtml.indexOf(">", startPos + 2);
                    var backPos = p_bodyhtml.substr(0, startPos).lastIndexOf("<");
                    var newbody = p_bodyhtml.substr(0, backPos) + p_bodyhtml.substr(eindPos + 1);
                    p_bodyhtml = newbody;
                }
                else
                {
                    var fileIndex = 0;
                    if (props.files.length > 1) {
                        var codes = flexcode.split(":");
                        for (var i = 0; i < codes.length && !fileIndex; i++) {
                            var code = codes[i].substr(0,1);
                            var data = codes[i].substr(2);
                            if (code == "F") { // Dan is; data = filename
                                for (var j = 0; j < props.files.length; j++) {
                                    if (props.files[j].name == data) {
                                        fileIndex = j;
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    var filedata = props.files[fileIndex];
                    if (filedata.resized)
                        var file = filedata.resized;
                    else
                        var file = props.AttachPath + filedata.name;


                    if (!fso.FileExists(file))
                    {
                        __DoLog("Flexcode {0}, file {1} not found".format(flexcode, file));
                        var newbody = p_bodyhtml.substr(0, imgstart) + "<img src=\"MISSING_" + safe.htmlattr(filedata.name) + "\">" + p_bodyhtml.substr(imgend + 1);
                        p_bodyhtml = newbody;
                    }
                    else
                    {
                        var fileStream = new ActiveXObject("ADODB.Stream")
                        fileStream.Open();
                        fileStream.Type = 1; //adTypeBinary
                        fileStream.LoadFromFile(file);
                        fileStream.Position = 0; // We gaan nu over in uitvoermode

                        var oXML = new ActiveXObject("Msxml2.DOMDocument.6.0");
                        oNode = oXML.createElement("encodeddata");
                        oNode.dataType = "bin.base64"; // Zeer snelle oplossing
                        oNode.nodeTypedValue = fileStream.Read(fileStream.Size);
                        fileStream.Close();
                        if (filedata.tempdelete)
                        {
                            fso.DeleteFile(filedata.tempdelete);
                        }

                        var b64 = oNode.text;
                        // TODO: Niet altijd als png, soms ook JPG, hoewel het goed lijkt te gaan in de meeste browsers
                        var newbody = p_bodyhtml.substr(0, startPos - 4) + "src=\"data:image/png;base64," + b64 + "\"" + p_bodyhtml.substr(eindPos + 1);
                        p_bodyhtml = newbody;
                    }
                }
            }
            var startPos = p_bodyhtml.indexOf("frc=", eindPos) + 4;
            var imgstart = p_bodyhtml.lastIndexOf("<img", startPos); // moet wel binnen een <img> vallen
            var imgend   = p_bodyhtml.indexOf(">", imgstart);
            var quote = p_bodyhtml.substr(startPos, 1);
            var eindPos =  p_bodyhtml.indexOf(quote, startPos + 2);
        }

        Stream.Write(p_bodyhtml);

        if (params.mailto) // vanuit fac_edit_xsl_save.asp voor testen
        {
            var mparams = { FromAddress: "notificatie@notexist.com",
                            ReplyAddress: params.mailto
                          };
            __Log(p_bodyhtml);
            putorders.sendMail(params.mailto, params.subject, p_bodyhtml, mparams);
        }

        if (params.log_postfix)
        {
            var log_file = __Log2FileName(params.log_postfix);
            var fileStream = Server.CreateObject("ADODB.Stream");
            fileStream.Open();
            fileStream.WriteText(p_bodyhtml);
            fileStream.SaveToFile(safe.UNC(log_file), 2); // 2=overwrite
            fileStream.Close();
        }
     }
  }
}

// returns the complete and appropriate XSL filename of my company, including path etc.
function GetStylesheet(xmlnode, concept)
{
    var fso = new ActiveXObject("Scripting.FileSystemObject");

    var resxslsheet = custpath + "\\xsl\\" + xmlnode + "_concept.xsl";
    if (!concept || !fso.FileExists(Server.MapPath(resxslsheet)))
        var resxslsheet = custpath + "\\xsl\\" + xmlnode + ".xsl";
    if (!fso.FileExists(Server.MapPath(resxslsheet)))
        var resxslsheet = getCustXsl();
    if (!fso.FileExists(Server.MapPath(resxslsheet)))
        var resxslsheet = rooturl + "\\appl\\shared\\default.xsl";
    __Log('xsl-sheet to be used: '+resxslsheet);

    return resxslsheet;
}

function make_xml(params)
{
    var sql_xtra = 'NULL';
    // FCLT#81165 tijdelijke fix omdat xtrakey soms onverwacht een fac_tracking_key bevat
    //            wat 95% van de bonnen niet verwacht (die verwachten een note_key van de
    //            notitie die je bij het handmatig mailen hebt ingegeven)
    if (params.xtrakey > 0
            && (params.xmlnode != 'melding'  || params.srtnotificatie == 'MLDMAI')
            && (params.xmlnode != 'opdracht' || params.srtnotificatie == 'ORDMAI')
            && (params.xmlnode != 'contract' || params.srtnotificatie == 'CNTMAI')
            && (params.xmlnode != 'factuur'  || params.srtnotificatie == 'FINMAI')
       )
    {
        sql_xtra = String(params.xtrakey);
    }
    var sql_params = safe.quoted_sql(params.xmlnode)
                   + ", " + params.key + " , "
                   + safe.quoted_sql(customerId)
                   + ", '$AspSession$'"
                   + ", " + sql_xtra
                   + ", " + (params.where ? safe.quoted_sql(params.where) : "''");

    var sql = "SELECT xml.make_xml2(" + sql_params + ") xml_blob FROM dual";
    var oRs = Oracle.Execute(sql);
    var xml_content = oRs("xml_blob").Value;

    xml_content = addParameterNode(xml_content, params);
    xml_content = addLanguageNode(xml_content, params)

    if (!xml_content)
        shared.record_not_found();
    oRs.Close();
    __Log("XML blob is {0} karakters.".format(xml_content.length));

    if (__Logging & 1)
        __Log2File(xml_content, params.xmlnode + params.key);
    return xml_content;
}

// Stuurt het resultaat rechtstreeks naar het Response object
//function FCLT2XMLResponse (xmlnode, key, xtrakey, pcompany, mode, where, srtnotificatie)
function FCLT2XMLResponse (params) {
    // params.
    //  xmlnode
    //  key
    //  xtrakey
    //  company   bedrijf waarvan stylesheet gebruikt moet worden (-1=self), tenzij stylesheet is meegegeven
    //  mode
    //  where
    //  srtnotificatie
    //  stylesheet
    // of nostyle
    //  concept

    var sql = '';

    if (params.key != -1)
    {
        var xml_content = make_xml(params);

        if (params.nostyle)
        {
            Response.Clear;
            Response.ContentType = "text/xml";
            Response.Write(xml_content);
        }
        else
        {
            if (!params.stylesheet)
                stylesheet = GetStylesheet(params.xmlnode, params.concept);
            else
                stylesheet = params.stylesheet;

            STR2Stream(xml_content, stylesheet, Response, params);
        }
        __Log("Done writing XML");
    }
    return true;
}

function addParameterNode(p_xml, params)
{
    // Voeg aan de bestaande xml de node <parameters> toe
    // met daarin de parameters (de kolommen met datatype = P) van het rapport
    if (params.extraparams)
    {
        var xmlDoc = new ActiveXObject("MSXML2.DOMDocument.6.0");
        xmlDoc.async = false;
        xmlDoc.loadXML(p_xml);
        if (xmlDoc.parseError.errorCode != 0)
        {
            var myErr = xmlDoc.parseError;
            //var msg = myErr.reason;
            //Geen geldige xml, maar ga toch maar verder.
        }
        else
        {
            var paramNode = xmlDoc.createNode(1,"parameters", "");
            for (var i=0; i<params.extraparams.length; i++)
            {
                var paramField = xmlDoc.createElement(params.extraparams[i].name);
                var paramFieldText = xmlDoc.createTextNode(params.extraparams[i].value);
                paramField.appendChild(paramFieldText);
                paramNode.appendChild(paramField);
            }

            var param_parent = xmlDoc.selectSingleNode("//facilitor/" + params.xmlnode);                     // xmlnode is "rapport" of "sysrapport".
            var param_before = xmlDoc.selectSingleNode("//facilitor/" + params.xmlnode + "/rapport_header"); // xmlnode is "rapport" of "sysrapport".
            var param_params = param_parent.insertBefore(paramNode, param_before);
            p_xml = xmlDoc.documentElement.xml;
        }
    }
    return p_xml;
}


function addLanguageNode(p_xml, params)
{
    var lang_path = custpath + "\\xsl\\language_" + user_lang + ".xml";
    if (fso.FileExists(Server.MapPath(lang_path)))
    {
        // Eerst laden van bestaande xml.
        var xmlDoc = new ActiveXObject("MSXML2.DOMDocument.6.0");
        xmlDoc.async = false;
        xmlDoc.loadXML(p_xml);
        // Nu de in te voegen xml laden.
        var xmlIns = new ActiveXObject("Msxml2.DOMDocument.6.0");
        xmlIns.async = false;
        xmlIns.load(Server.MapPath(lang_path));
        if (xmlIns.parseError.errorCode != 0)
        {
            var myErr = xmlIns.parseError;
            _AiAi(myErr.reason + " in " + lang_path);
        }
        else
        {
            var langNode = xmlIns.selectSingleNode("/*");  // de root-node uit het zojuist geladen taal-bestand
            var clone = xmlDoc.importNode(langNode, true);
            var nodeParent = xmlDoc.selectSingleNode("//facilitor/lcl"); // In deze node moet het worden toegevoegd.
            nodeParent.appendChild(clone);
            __Log(lang_path + " added to XML");
            p_xml = xmlDoc.documentElement.xml;
        }
    }
    return p_xml;
}

// Veel gebruikte functies vanuit API's

// http://davidwalsh.name/convert-xml-json
function xmlToJson(xml)
{
  // Create the return object
  var obj = {};

  if (xml.nodeType == 1)
  { // element
    // do attributes
    if (xml.attributes.length > 0)
    {
      obj["@attributes"] = {};
      for (var j = 0; j < xml.attributes.length; j++)
      {
        var attribute = xml.attributes.item(j);
        obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
      }
    }
  }
  else if (xml.nodeType == 3)
  { // text
    obj = xml.nodeValue;
  }

  // do children
  if (xml.hasChildNodes())
  {
    for(var i = 0; i < xml.childNodes.length; i++)
    {
      var item = xml.childNodes.item(i);
      var nodeName = item.nodeName;
      if (typeof(obj[nodeName]) == "undefined")
      {
        // JGL Added: Only one Textnode is simplified
        if (item.nodeType == 3 && xml.childNodes.length == 1)
            return item.nodeValue;

        obj[nodeName] = xmlToJson(item);
      }
      else
      {
        if (typeof(obj[nodeName].push) == "undefined")
        {
          var old = obj[nodeName];
          obj[nodeName] = [];
          obj[nodeName].push(old);
        }
        obj[nodeName].push(xmlToJson(item));
      }
    }
  }
  return obj;
};

// Luie oplossing: Oracle kan dit simpeler dan JavaScript.
// Naamgeving is overigens misleidend: we gebruiken het meestal met data vanuit
// een XML maar eigenlijk is de bron een ISO8601 formaat datum
function XMLtoJsDate(xmlDate)
{
    var orgxmlDate = xmlDate; // voor logging
    xmlDate = xmlDate.replace(/^\s+|\s+$/g,""); // trim spaties
    if (xmlDate.length >= 20) // with offset
    {
        if (xmlDate.slice(-1) == 'Z') // 2020-10-08T21:59:59Z
        {
            var offset = "+00:00";
            xmlDate = xmlDate.slice(0, -1);
        }
        else // 2020-10-08T23:59:59+02:00
        {
            var offset = xmlDate.slice(-6);
            xmlDate = xmlDate.slice(0, -6);
        }
        var sql = "SELECT CAST ( "
                + "           FROM_TZ ( "
                + "               CAST ( "
                + "                   TO_DATE (REPLACE ("+safe.quoted_sql(xmlDate)+", 'T', ' '), "
                + "                            'YYYY-MM-DD HH24:MI:SS') AS TIMESTAMP), "
                + "               "+safe.quoted_sql(offset)+") "
                + "               AT TIME ZONE "+safe.quoted_sql(S("fac_server_timezone"))+" AS DATE) "
                + "           AS newDate "
                + "  FROM DUAL";
    }
    else
        var sql = "SELECT TO_DATE(REPLACE("+safe.quoted_sql(xmlDate)+",'T',' '), 'YYYY-MM-DD HH24:MI:SS') FROM DUAL";
    //var sql = "SELECT TO_CHAR (TRUNC (TO_DATE(REPLACE("+safe.quoted_sql(xmlDate)+",'T',' '), 'YYYY-MM-DD HH24:MI:SS'), 'HH') + (15 * ROUND (TO_CHAR (TRUNC (TO_DATE(REPLACE("+safe.quoted_sql(xmlDate)+",'T',' '), 'YYYY-MM-DD HH24:MI:SS'), 'MI'), 'MI') / 15)) / 1440, 'DD-MM-YYYY HH24:MI:SS') FROM DUAL";
    try {
      var oRs = Oracle.Execute(sql);
      var dt = new Date(oRs(0).Value);
    } catch (e) {
      __DoLog("XMLtoJsDate conversion failed: " + orgxmlDate);
      __DoLog("e.description: " + e.description);
      throw e;
    }
    oRs.Close();
    return dt;
}

function XMLval(xml, tag, magLeegZijn)
{
    if (!xml)
        return null;
    var xx = xml.getElementsByTagName(tag);
    if (!xx || !xx.length)
        return null;
    xx = xx[0].childNodes
    if (!xx || !xx.length) // De tag is er wel, maar hij is leeg (alex)
        return magLeegZijn ? "" : null;
    return xx[0].nodeValue;
}

%>
