
  // Deze script staat in een aparte file omdat de HTML output die wordt gegenereerd
  // met Refocus() de script moet bevatten, terwijl Refocus() zelf onderdeel
  // van de script is...
  // Zouden we proberen om de script te integreren in de core HTML (waarmee de
  // 'show-all-my-data' output wordt gestart) dan ontstaat de situatie:
  // <HTML><HEAD><SCRIPT>
  //  function Refocus(index)
  //  { return '<HTML><HEAD><SCRIPT> en hier moet dan de script weer komen, etc
  // Nu de script in een js file staat, verandert de core HTML in:
  // <HTML><HEAD><SCRIPT SCR="xxx.js">...
  // en in de js file:
  //  function Refocus(index)
  //  { return '<HTML><HEAD><SCRIPT SCR="xxx.js">...

  // De volgende regels moeten worden gedefinieerd als onderdeel van de data:
  // var generalLabels = '<P>...</P> 
  // var bodyAttributes = 'bgcolor="#CEC0B3"' etc.
  // var maleColor =
  // var femaleColor =
  // var searchTxt = 'Search'
  // var searchExample = 'e.g. "Chr Visser"'
  // var charset = 'windows-1252'

//  var version      = '1.0'
// 	high ASCII niet langer doorgegeven als "?"
//  	vertaling van diacritics naar character entity references
//  var version      = '1.1'
//	deathSym veranderd
//      sexSymbol toegevoegd
//  var version      = '1.2'
//      Refocus() schrijft niet langer hele pagina, maar vervangt body.innerHTML
//      Globals blijven nu behouden en char encoding van diacritics probleemloos
//  var version      = '1.3'
//      '-' in zoeknaam vervangen door ' '
//  var version      = '1.5'
//      Aanpassingen t.b.v. M-M en F-F relaties

  var version      = '1.5'
  var nlcr         = String.fromCharCode(13)+String.fromCharCode(10)
  var birthSym     = '* : '
  var baptSym      = '~ : '
  var deathSym     = '+ : '  //'&#8224; : '
  var burialSym    = '# : '
  var marrSym      = 'x';

  var cadreWidth   = 150
  var cadreHeight  =  27

  var xCenter      = 500
  var xMultiply    = 100
  var yMultiply    =  40
  var yOffset      =  80

  var searchResultList   = ''
  var searchResultsIndex = -1

  var spanCorrection   = 0   // IE : 0; FF : 10

  var charTable    = new Array(256)

  function DefineDiacritic(char,htmlCode) { charTable[char.charCodeAt(0)]=htmlCode }

  function DefineCharTable()
  { var k
    for (k=0;k<256;k++) { charTable[k]='?' }
    charTable[9]=' '
    charTable[10]=String.fromCharCode(10)
    charTable[13]=String.fromCharCode(13)
    for (k=32;k<127;k++) { charTable[k]=String.fromCharCode(k) }
    DefineDiacritic('À','&Agrave;')
    DefineDiacritic('Á','&Aacute;')
    DefineDiacritic('Â','&Acirc;')
    DefineDiacritic('Ã','&Atilde;')
    DefineDiacritic('Ä','&Auml;')
    DefineDiacritic('Å','&Aring;')
    DefineDiacritic('Æ','&AElig;')
    DefineDiacritic('Ç','&Ccedil;')
    DefineDiacritic('È','&Egrave;')
    DefineDiacritic('É','&Eacute;')
    DefineDiacritic('Ê','&Ecirc;')
    DefineDiacritic('Ë','&Euml;')
    DefineDiacritic('Ì','&Igrave;')
    DefineDiacritic('Í','&Iacute;')
    DefineDiacritic('Î','&Icirc;')
    DefineDiacritic('Ï','&Iuml;')
    DefineDiacritic('Ð','&ETH;')
    DefineDiacritic('Ñ','&Ntilde;')
    DefineDiacritic('Ò','&Ograve;')
    DefineDiacritic('Ó','&Oacute;')
    DefineDiacritic('Ô','&Ocirc;')
    DefineDiacritic('Õ','&Otilde;')
    DefineDiacritic('Ö','&Ouml;')
    DefineDiacritic('Ø','&Oslash;')
    DefineDiacritic('Ù','&Ugrave;')
    DefineDiacritic('Ú','&Uacute;')
    DefineDiacritic('Û','&Ucirc;')
    DefineDiacritic('Ü','&Uuml;')
    DefineDiacritic('Ý','&Yacute;')
    DefineDiacritic('Þ','&THORN;')
    DefineDiacritic('ß','&szlig;')
    DefineDiacritic('à','&agrave;')
    DefineDiacritic('á','&aacute;')
    DefineDiacritic('â','&acirc;')
    DefineDiacritic('ã','&atilde;')
    DefineDiacritic('ä','&auml;')
    DefineDiacritic('å','&aring;')
    DefineDiacritic('æ','&aelig;')
    DefineDiacritic('ç','&ccedil;')
    DefineDiacritic('è','&egrave;')
    DefineDiacritic('é','&eacute;')
    DefineDiacritic('ê','&ecirc;')
    DefineDiacritic('ë','&euml;')
    DefineDiacritic('ì','&igrave;')
    DefineDiacritic('í','&iacute;')
    DefineDiacritic('î','&icirc;')
    DefineDiacritic('ï','&iuml;')
    DefineDiacritic('ð','&eth;')
    DefineDiacritic('ñ','&ntilde;')
    DefineDiacritic('ò','&ograve;')
    DefineDiacritic('ó','&oacute;')
    DefineDiacritic('ô','&ocirc;')
    DefineDiacritic('õ','&otilde;')
    DefineDiacritic('ö','&ouml;')
    DefineDiacritic('ø','&oslash;')
    DefineDiacritic('ù','&ugrave;')
    DefineDiacritic('ú','&uacute;')
    DefineDiacritic('û','&ucirc;')
    DefineDiacritic('ü','&uuml;')
    DefineDiacritic('ý','&yacute;')
    DefineDiacritic('þ','&thorn;')
    DefineDiacritic('ÿ','&yuml;')
  }

  function BrowserSpecifics()
  { if (navigator.appName == 'Netscape')
       { spanCorrection = 10
       }
    return true
  }

  function Xscreen(x) { return (xCenter+x*xMultiply) }
  function Yscreen(y) { return (yOffset+y*yMultiply) }

  function IsDefined(variable) { return (!(!( variable||false ))) }

  function DecryptName(index)
  // Werk met names[index] (versluierd)
  // Return de ontsluierde naam
  // NB: chars met waarde >127 zijn in de naam vervangen door ?
  // De afkortingen (aangegeven met ^n) blijven als afkorting staan
  { if (index<0) { return '' }
    var k = 0
    var p = (index % 26)+1
    var q,chCode
    var txt = ''
    name = names[index]
    do { chCode = name.charCodeAt(k)
         if (chCode>=97)
            { if (chCode<=122)                                  // a..z
                 { q = chCode-97-p
                   if (q<0) {q = q+26 }
                   txt = txt + String.fromCharCode(q+97)
                 }
              else { txt = txt + String.fromCharCode(chCode) }  // >z <127
            }
         else                                                   // <97
            { if (chCode > 90)                                  // (>Z  <a)
                 { txt = txt + String.fromCharCode(chCode) }
              else                                              // <=90
                 { if (chCode >= 65)                            // A..Z
                      { q = chCode-65-p
                        if (q<0) {q = q+26 }
                        txt = txt + String.fromCharCode(q+65)
                      }
                   else { txt = txt + String.fromCharCode(chCode) } // <65
                 }
            }  
         k++
       }
    while (k<name.length)
    return txt
  }


  function HTMLcodes(name)
  { var k,txt,chCode
    txt=''
    for (k=0;k<name.length;k++) { txt=txt+charTable[name.charCodeAt(k)] }
    return txt
  }

  function FormatDecryptedName1(name)
  // name kan nog afkorting bevatten ( ^n codes )
  // de functie neemt uit name de '/' symbolen weg en zorgt voor correcte spatiering
  // de outputstring heeft geen linebreaks
  { var surname = ""
    var txt = name
    var p1 = name.indexOf('/')
    if (p1>=0) {txt = name.substring(0,p1)}
    if (p1<0) { return txt }
    var p2 = name.indexOf('/',p1+1)
    if (p2<0) {surname = name.slice(p1+1)}
        else  {surname = name.substring(p1+1,p2)}
    var k = txt.length
    while ((k>0) && (txt.charAt(k-1)==' ')) { k = k-1 }
    if (k<txt.length-1) { txt = txt.slice(0,k-txt.length) }
    k=0
    while ((k<surname.length) && (surname.charAt(k) ==' ')) { k++ }
    if (k>0) { surname = surname.substr(k) }
    txt = txt + ' ' + surname
    txt = HTMLcodes(txt)
    return txt
  }

  function ReplaceCommonNames(txt)
  // zoek in txt de eerste code voor een afkorting (^n)
  // vervang de afkorting door de staring in Ncoomon
  // ga recursief door met het resterende deel van txt
  { var p = txt.indexOf('^')
    if (p<0) { return txt }
    var n = txt.charCodeAt(p+1)-35
    return txt.substr(0,p)+Ncommon[n]+ReplaceCommonNames(txt.slice(p+2))
  }

  function FormatName1(index)
  // werk met names[index] : ontsluier, vervang de afkortingen en formateer
  // voor output op een enkele regel
  { var name = DecryptName(index)
    name = ReplaceCommonNames(name)
    return FormatDecryptedName1(name)
  }

  function FormatName2(index)
  // werk met names[index] : ontsluier, vervang de afkortingen en formateer
  // voor output op twee regels: voornamen en chternamen gescheiden
  { var name = DecryptName(index)
    name = ReplaceCommonNames(name)
    var surname = ""
    var txt = name
    var p1 = name.indexOf('/')
    if (p1>=0) {txt = name.substring(0,p1)}
    if (txt.length > 20) { txt = txt.substring(0,20)+'...' }
    if (p1<0) { return txt }
    var p2 = name.indexOf('/',p1+1)
    if (p2<0) {surname = name.slice(p1+1)}
        else  {surname = name.substring(p1+1,p2)}
    if (surname.length > 20) { surname = surname.substring(0,20)+'...' }
    return HTMLcodes(txt)+ '<BR>'+HTMLcodes(surname)
  }

  function ClickToRefocus()
  // voor onMouseOver over zoekresultaten: toon een statusbericht
  // dit werkt niet langer als eenmaal in de resultatenlijst is geklikt omdat
  // dan de searchResultList string leeg is. Die wordt pas weer gevuld uit
  // 'storage' als in het veld wordt geklikt.
  // Werkt niet in FF omdat de statusbalk daar als een secure control wordt gezien
  // De gebruiker moet via options toestaan dat de statusbar wordt herschreven
  { if (searchResultList!='') { window.status = 'Click -> refocus'; return true }}

  function ClearStatus()
  // voor onMouseOut : schoon de statusregel
  { window.status = ''; return }

  function BaseCadre(left,top,index,extraStyle)
  // return de HTML code voor een kader voor persoon[index]: man/vrouw kleur met
  // de naam als link en popup-tekst voor een mouseOver.
  // positionering is in relatieve coordinaten
  // extraStyle kan worden benut om het kader te accentueren
  // de HREF van de naam roept Refocus() aan, zodat de proband zal wijzigen
  { var x = (Xscreen(left)-cadreWidth/2)
    var y = (Yscreen(top)-cadreHeight/2)
    var title = FormatName1(index)+nlcr+LifeEvents(index,nlcr)
    var txt = FormatName2(index)
    var type = sex.charAt(index).toLowerCase()
    if ((type != "m") && (type !="f")) {type="p"}
    var anchor = '<a href="javascript:void Refocus('+index+')" title="'+title+'" onMouseOver="ClearStatus(); return true">'+txt+'</a>'
    return '<div class="'+type+'" style="'+extraStyle+'top:'+y+';left:'+x+'">'+anchor+'</div>'
  }

  function Cadre(left,top,index)
  // de kader aanroep voor alle standaard situaties: de naam is bekend en index
  // wijst niet naar de proband
  { return BaseCadre(left,top,index,'') }

  function HighlightCadre(left,top,index)
  // kader voor de proband: de rand is extra dik
  { return BaseCadre(left,top,index,'border:2 px;') }

  function FixedCadre(type,left,top)
  // kader aanroep voor alle situaties waar geen naam bekend is
  // geen popup, '?' als naam
  // type is m/f voor de kleur (als geen van twee: wit)
  { var x = Xscreen(left)-cadreWidth/2
    var y = Yscreen(top)-cadreHeight/2
    if ((type!="m") && (type!="f")) {type="p"}
    return '<div class="'+type+'" style="top:'+y+'; left:'+x+'">?</div>'
  }

  function AncestorConnect(left,right,top,bottom)
  // HTML output voor een verbindingslijn bestaande uit een horizontaal stuk
  // en een vertikaal stuk, samengesteld uit een tabel
  // de coordianten zijn relatief
  { var x     = Xscreen(left)
    var width = Xscreen(right)-Xscreen(left)
    var y     = Yscreen(top)
    var height= Yscreen(bottom)-Yscreen(top)
    return '<table class=u style="top:'+y+'; left:'+x+'; width:'+width+'; height:'+height+'"><tr><td></table>'
  }

  function DateToStr(aDate)
  // ontleed de gecomprimeerde datum aDate en return de leesbare datum
  { if (aDate=='') { return '' }
    if (aDate==0)  { return '' }
    var txt,year,day
    var month = aDate.charCodeAt(0)-97;
    if (month != 0)
       { txt = month+'-'
         day = aDate.charCodeAt(1)-60;
         if (day != 0) { txt = day + '-'+ txt }
         year = '' + (aDate.charCodeAt(2)-97) + aDate.slice(3);
         txt = txt + year
       }
    else
       { year = (aDate.charCodeAt(2)-97);
         txt = '' + year + aDate.slice(3)
       }
   return txt
  }

  function UncompressDate(dateString)
  // neem een gecomprimeerde datumstring dateString en return de leesbare datum aanduiding
  // de dateString bevat in het algemeen een (gecomprimeerde) datum, voorafgegaan
  // door een qualifier (< > +/- of ?) of hij bestaat uit twee datums, gescheiden
  // door een '-' (voor een periode aanduiding)
  { if (dateString==0) { return '' }
    if (dateString.length<=0) { return '' }
    var thisDate,toDate,p,txt
    var qual = dateString.charAt(0);
    if (qual>='a')  // i.e. een getal
       { qual = ''
         p = dateString.indexOf('-')
         if (p<0) { txt = DateToStr(dateString) }
            else  { toDate = dateString.slice(p+1)
                    thisDate = dateString.substr(0,p)
                    txt=DateToStr(thisDate)+' -- '+DateToStr(toDate)
                  }
       }
    else
       { thisDate = dateString.slice(1)
         if (qual=='?') { qual = '&plusmn;' }
         txt = qual + ' ' + DateToStr(thisDate)
       }
    return txt
  }

  function Concat(s1,delim,s2)
  // als s1 en s2 allebei niet leeg zijn: return de samenvoeging van s1 en s2,
  // gescheiden door delim. Anders de string die niet leeg is.
  { if (s1 != '')
       { if (s2 != '') { return s1 + delim + s2 }
            else       { return s1 }
       }
    else { return s2 }
  }

  function ReplaceCommonPlaceWords(place)
  // place kan afkortingen bevatten ( ^n codes ) die verwijzen naar de lijst Pcommon
  // vervang de afkortingen (recursief) door hun overeenkomstige woorden
  { var p = place.indexOf('^')
    if (p<0) { return place }
    var n = place.charCodeAt(p+1)-35
    return place.substr(0,p)+Pcommon[n]+ReplaceCommonPlaceWords(place.slice(p+2))
  }

  function ExtractPlace(index)
  // return de leesbare plaatsnaam op positie index in het places array
  // een plaatsnaam kan afkortingen bevatten: die moeten worden vervangen
  // en de string kan beginnen met een herhaal-code ( #n). Deze geeft aan dat
  // n karakters van de vorige plaatsnaam in het array moeten worden overgenomen
  // dit laatste kan recursief moeten worden herhaald
  { if (index == '')          { return '' }
    if (index <= 0)           { return '' }
    var txt = places[index]
    txt = ReplaceCommonPlaceWords(txt)
    if (txt.length<2)       { return txt }
    if (txt.charAt(0)!='#') { return txt }
    var n = txt.charCodeAt(1)-48
    txt = ExtractPlace(index-1).substr(0,n) + txt.substr(2)
    return txt
  }

  function ShowMarriage(index)
  // voor de datum/plaats gegevens op positie index (voor een FAM):
  // return de datum/plaats gegevens over het huwelijk in leesbare tekst
  { var date = Fdates[index]
    var place = Fplaces[index]
    return Concat(UncompressDate(date),', ',ExtractPlace(place))
  }

  function ShowMarriageM(nr,max,index)
  // voor FAM[index]: return huwelijksgegevens voor de man
  // als max>1 : nummer het huwelijk
  { var txt = ' : '+Concat(ShowMarriage(index),': ',FormatName1(wife[index]))
    if (max>1) { txt = ' ('+nr+')'+txt }
    return marrSym + txt
  }
  function ShowMarriageF(nr,max,index)
  // voor FAM[index]: return huwelijksgegevens voor de vrouw
  // als max>1 : nummer het huwelijk
  { var txt = ' : '+Concat(ShowMarriage(index),': ',FormatName1(husb[index]))
    if (max > 1) { txt = ' ('+nr+')'+txt }
    return 'x'+txt
  }

  function LifeEvents(person,newlineSymbol)
  // return alle gegevens over een persoon: beroep, geboorte..begravenis, huwelijken
  { if (person < 0) { return '' }
    var txt = occupation[person]
    if   (txt==0) { txt = '' } else { txt = txt+newlineSymbol }
    var dateString = Idates[person]
    var eventDates,nEventDates
    if (dateString==0) { nEventDates = 0}
    else { eventDates = dateString.split(";")
           nEventDates = eventDates.length
         }
    var placeString = Iplaces[person]
    var eventPlaces,nEventPlaces
    if (placeString==0) { nEventPlaces = 0}
    else { eventPlaces = placeString.split(";")
           nEventPlaces = eventPlaces.length
         }
    var date,place
    if (nEventDates>0) { date = eventDates[0] } else { date = '' }
    if (nEventPlaces>0) { place = eventPlaces[0] } else { place = '' }
    if ((date!='') || (place!=''))
       {txt = txt+birthSym+Concat(UncompressDate(date),', ',ExtractPlace(place))+newlineSymbol}
    if (nEventDates>1) { date = eventDates[1] } else { date = '' }
    if (nEventPlaces>1) { place = eventPlaces[1] } else { place = '' }
    if ((date!='') || (place!=''))
       {txt = txt+baptSym+Concat(UncompressDate(date),', ',ExtractPlace(place))+newlineSymbol}
    if (nEventDates>2) { date = eventDates[2] } else { date = '' }
    if (nEventPlaces>2) { place = eventPlaces[2] } else { place = '' }
    if ((date!='') || (place!=''))
       {txt = txt+deathSym+Concat(UncompressDate(date),', ',ExtractPlace(place))+newlineSymbol}
    if (nEventDates>3) { date = eventDates[3] } else { date = '' }
    if (nEventPlaces>3) { place = eventPlaces[3] } else { place = '' }
    if ((date!='') || (place!=''))
       {txt = txt+burialSym+Concat(UncompressDate(date),', ',ExtractPlace(place))+newlineSymbol}
    var k
    var n = 0
    var marriage = new Array()
    switch(sex.charAt(person))
    { case "M": for (k=0; k<husb.length; k++)
                    { if (husb[k] == person) { marriage[n] = k; n++ } }
                for (k=0; k<n; k++)
                    { txt = txt+ShowMarriageM(k+1,n,marriage[k])+newlineSymbol }
                break
      case "F": for (k=0; k<wife.length; k++)
                    { if (wife[k] == person) { marriage[n] = k; n++ } }
                for (k=0; k<n; k++)
                    { txt = txt+ShowMarriageF(k+1,n,marriage[k])+newlineSymbol }
                break
      default:
    }
    return HTMLcodes(txt)
  }

  function FatherIndex(person)
  // zoek de birthFAM van person en return de (index in names) van de man uit die FAM
  // als onbekend, return -1
  { var hisBirthFAM = birthFAM[person]
    if (hisBirthFAM =='-1') {return -1} else {return husb[hisBirthFAM]}
  }

  function MotherIndex(person)
  // zoek de birthFAM van person en return de (index in names) van de vrouw uit die FAM
  // als onbekend, return -1
  { var herBirthFAM = birthFAM[person]
    if (herBirthFAM =='-1') {return -1} else {return wife[herBirthFAM]}
  }

  function Refocus(index)
  // Maak een compleet nieuw overzicht.
  // De proband is de persoon op positie index in names.
  { document.body.innerHTML=ShowNearbyFamily(index)+ShowSearchField(index) }

  function ShowAncestors(proband)
  // HTML output van de kaders en lijnen voor ouders en grootouders en de
  // proband zelf
  { var father,mother,grandfather,grandmother
    var txt = AncestorConnect(-3,-1,0,1) + AncestorConnect(+1,+3,0,1)
    if (proband<0)
       { father = -1; mother = -1}
    else
       { father = FatherIndex(proband)
         mother = MotherIndex(proband)
       }
    if (father<0)
       { txt = txt+FixedCadre("m",-3,0,"?") + FixedCadre("f",-1,0)}
    else
       { grandfather = FatherIndex(father)
         if (grandfather<0)
            { txt = txt + FixedCadre("m",-3,0) }
         else
            { txt = txt+Cadre(-3,0,grandfather) }
         grandmother = MotherIndex(father)
         if (grandmother<0)
            { txt = txt+FixedCadre("f",-1,0) }
         else
            { txt = txt+Cadre(-1,0,grandmother) }
       }
    if (mother<0)
       { txt = txt+FixedCadre("m",+1,0,"?") + FixedCadre("f",+3,0) }
    else
       { grandfather = FatherIndex(mother)
         if (grandfather<0)
            { txt = txt + FixedCadre("m",+1,0) }
         else
            { txt = txt + Cadre(+1,0,grandfather) }
         grandmother = MotherIndex(mother)
         if (grandmother<0)
            { txt = txt+FixedCadre("f",+3,0) }
         else
            { txt = txt + Cadre(+3,0,grandmother) }
       }
    txt = txt + AncestorConnect(-2,2,1,2)
    if (father<0)
       { txt = txt + FixedCadre("m",-2,1) }
    else
       { txt = txt+Cadre(-2,1,father) }
    if (mother<0)
       { txt = txt + FixedCadre("f",+2,1) }
    else
       { txt = txt + Cadre(+2,1,mother) }

    return txt + HighlightCadre(0,2,proband)
  }

  var nextChildPos   // vertikale indexering van de positie voor volgende kind

  function PartnerConnect(left,top)
  // verbinding tussen proband en partner: vertikale lijn omlaag + horizontale
  // lijn naar rechts of links
  // top  = vertikale positie van de partner (feitelijk "nextChildPos")
  // left = horizontale positie van de partner (-3 of +3)
  { var xLeft,width
    var yTop  = Yscreen(top)
    var height= yTop-Yscreen(2)
    if (left <0)
       { xLeft = Xscreen(left)
         width = -left*xMultiply
       }
    else
       { xLeft = xCenter
         width = left*xMultiply
       }
    return '<table class=v style="border-left:1px dashed; top:'+Yscreen(2)+'; left:'+xCenter+'; width:10; height:'+height+'"><tr><td></table>'+
           '<table class=h style="border-top:1px dashed; top:'+yTop+'; left:'+xLeft+'; height:10; width:'+width+'"><tr><td></table>'
  }

  function ShowChild(x,index)
  // output de code voor een kind en update de positie voor volgende kind
  { var txt = Cadre(x,nextChildPos,index)
    nextChildPos++
    return txt
  }

  function ShowChildren(x,index)
  // index = nummer van de FAM; als het gaat om de kinderen van husb[N], dan
  // is N de index
  // eerst kinderen verzamelen in array, dan vertikale lijn trekken en vervolgens
  // de kinderen daaroverheen tekenen
  { var txt = ""
    var child = new Array();
    var n = 0
    var k
    for (k=0; k<birthFAM.length; k++) { if (birthFAM[k]==index) { child[n] = k; n++ } }
    if (n>0)
       { txt = '<table class=v style="border-left:1px dashed; top:'+(yOffset+(nextChildPos-1)*yMultiply)+'; left:'+(xCenter+x*xMultiply)+'; width:10; height:'+(n*yMultiply)+'"><tr><td></table>'
         for (k=0; k<n; k++) { txt = txt + ShowChild(x,child[k]) }
       }
    return txt
  }

  function ShowPartner(index,partner)
  { var txt = PartnerConnect(3,nextChildPos) + Cadre(3,nextChildPos,partner)
    nextChildPos++
    return txt + ShowChildren(1,index)
  }

  function ShowRelations(proband)
  // maak de HTML code voor alle relaties (partners en kinderen)
  { var txt = ''
    var k
    nextChildPos = 3
//  V1.5: gewijzigd t.b.v. M-M en F-F relaties
//    switch(sex.charAt(proband))
//    { case "M": for (k=0; k<husb.length; k++)
//                { if (husb[k]==proband) {txt = txt + ShowPartner(k,wife[k])} }
//                break
//      case "F": for (k=0; k<wife.length; k++)
//                { if (wife[k]==proband) {txt = txt + ShowPartner(k,husb[k])} }
//                break
//      default:
//    }
      for (k=0; k<husb.length; k++)
      { if (husb[k]==proband) {txt = txt + ShowPartner(k,wife[k])} }
      for (k=0; k<wife.length; k++)
      { if (wife[k]==proband) {txt = txt + ShowPartner(k,husb[k])} }
      return txt
  }

  function EmptySide(proband)
  // return de relatieve x positie van de zijde van de proband waar de partner
  // niet is
  { return ((Xscreen(-3)-cadreWidth/2)) }

  function ShowProband(proband)
  // HTML code voor het veld waarin de gegevens van de proband worden getoond:
  // naam (bold), beroep, geboorte..begravenis, huwelijken
  { var left = EmptySide(proband)
    var sexSymbol
    switch(sex.charAt(proband))
    { case "M": sexSymbol='&#9794;'
                break
      case "F": sexSymbol='&#9792;'
                break
      default:  sexSymbol=''
    }
    return '<span style="position:absolute;border:1px solid black;padding:5px;font-size:12px;text-align:left;'+
                        'width:'+(Xscreen(3)-Xscreen(1)+cadreWidth-spanCorrection)+';'+
                        'left:'+left+';top:'+Yscreen(3)+';'+
                        'background-color:#F5F5F5">'+
              '<B>'+FormatName1(proband)+' &nbsp; '+sexSymbol+'</B><BR>'+LifeEvents(proband,'<BR>')+
           '</span>'
  }

  function Match(name,searchParts,commonMatch)
  // searchParts is een array met zoekwoorden (van de gebruiker)
  // commonMatch is een array dat aangeeft of een Ncommon afkorting matcht met
  // een zoekwoord
  // name is de naam waarvan wordt gekeken of alle zoekwoorden erin voorkomen
  // check of alle elementen van searchParts (lowerCase) worden teruggevonden aan het begin
  // van de woorden van name (UpperCase en LowerCase).
  // (iedere letter die wordt voorafgegaan door een niet-letter geeft het begin aan van een woord.)
  // NB: name kan nog afkortingen van woorden bevatten (^n). Deze afkortingen
  // hoeven niet te worden uitgevouwen. Het array commonMatch bevat
  // de informatie over matches voor deze afkortingen.
  { var nSearchParts = searchParts.length
    var nameLength = name.length
    var k,n,m,chCode,part,allEqual
    var found =0
    var index = 0
    var partMatched = new Array(nSearchParts)
    for (k=0;k<nSearchParts;k++) { partMatched[k] = false }
    while (index<nameLength-1)
    { // skip tot start van woord (eerstvolgende lowercase letter)
      while (index<nameLength)
      { chCode = name.charCodeAt(index)
        if (chCode<=122)
           { if (chCode>=97) { break }                  // a..z
             if (chCode>=65)
                { if(chCode<=90) {break}                // A..Z
                  if (chCode==94)                       // ^  i.e. afkorting
                     { index++      // stap over de index van de afkorting
                       m = name.charCodeAt(index)-35    // afkortingscode
                       n = commonMatch[m]               // -1 of match van afkorting
                       if ((n>=0) && (!partMatched[n])) // afkorting matcht
                          { found++
                            if (found>=nSearchParts) { return true }
                            partMatched[n] = true
                          }
                     }
                }
           }
        index++
      }
      // probeer alle searchParts
      for (k=0; k<nSearchParts; k++)
      { if (partMatched[k]) { continue }
        part = searchParts[k]
        m = part.length
        if (index+m > nameLength) { continue }
        allEqual = true
        for (j=0; (j<m); j++)
            { chCode=name.charCodeAt(index+j)
              if ((chCode>=65) && (chCode<=90)) { chCode = chCode+32 } // lowerCase
              if ((chCode!=63) && (chCode!=part.charCodeAt(j))) { allEqual=false; break }  // 63 = '?'
            }
        if (allEqual)
           { found++
             if (found>=nSearchParts) { return true }
             partMatched[k] = true
             index = index + m-1
             break
           }
      }
      // skip rest van woord
      index++
      while (index<nameLength)
      { chCode = name.charCodeAt(index)
        if ((chCode<97) || (chCode>122)) {break}
        index++
      }
    }
    return false
  }

  function StringCompare(str1,str2)
  // als str1 < str2 dan -1, anders 0 (gelijk) of +1
  { var diff
    var n = str1.length
    if (str2.length<n) { n = str2.length }
    for (k=0; k<n; k++)
        { diff = str1.charCodeAt(k) - str2.charCodeAt(k)
          if (diff<0) { return -1 }
          if (diff>0) { return 1 }
        }
    if (str1.length<str2.length) { return -1 }
    if (str1.length>str2.length) { return 1 }
    return 0
  }

  function MatchCommonNames(commonMatch,searchParts)
  // searchParts is een array met zoekwoorden (van de gebruiker)
  // in array commonMatch[k] wordt geschreven of Ncommon[k] begint met een van
  // de zoekwoorden. Zo ja, dan wordt de index van het zoekwoord geschreven,
  // zo niet dan -1.
  { var k,p,part,m,n,j,chCode,name
    var nSearchParts = searchParts.length
    for (p=Ncommon.length-1; p>=0; p--)
        { commonMatch[p] = -1
          name = Ncommon[p].toLowerCase()
          n = name.length
          for (k=0; k<nSearchParts; k++)
              { part = searchParts[k]
                m = part.length
                if (m>n) { continue }
                allEqual = true
                for (j=0; j<m; j++)
                    { chCode=name.charCodeAt(j)
                      if ((chCode!=63) && (chCode!=part.charCodeAt(j))) { allEqual=false; break }  // 63 = '?'
                    }
                if (allEqual) { commonMatch[p] = k; break }
              }
        }
    return
  }

  function PerformSearch()
  // zoek door alle namen naar degeen die alle zoekwoorden bevatten aan het begin
  // van een woord.
  // de output wordt gesorteerd op naam en dan toegevoegd als <OPTION> elementen
  // aan het SearchResults veld.
  { var inputField = document.forms[0].SearchInput
    var searchString = inputField.value.toLowerCase().replace(/-/g," ")
    if (searchString=='') { return }
    var searchParts = searchString.split(' ')
    var k,p,found,name,loName,formatName,part,newElement,child,s
    var toGo = names.length
    var interval = Math.floor(toGo/16)+1   // 16 updates on search progress

    searchResultList = ''  // hierin komen de indices van de gevonden namen
    var outputField = document.getElementById('SearchResults')
    outputField.options.length = 0
    var count = 0          // telt het aantal matches

    // check matches onder de Ncommon namen; plaats in commonMatch array
    var commonMatch = new Array(Ncommon.length)
    MatchCommonNames(commonMatch,searchParts)

    for (k=0; k<names.length; k++)
        { name = DecryptName(k)
          if (Match(name,searchParts,commonMatch))
            { formatName = FormatDecryptedName1(ReplaceCommonNames(name))
              newElement = document.createElement('OPTION')
              newElement.innerHTML = formatName
              newElement.value = k  // om later de searchResultList te kunnen construeren
              // insert in de child lijst zodanig dat de namen gesorteerd zijn
              child = outputField.firstChild
              if (!IsDefined(child)) { outputField.appendChild(newElement) }
              else { while ((IsDefined(child)) && (StringCompare(child.innerHTML,formatName)<0))
                            {child = child.nextSibling}
                     if (IsDefined(child)) { outputField.insertBefore(newElement,child) }
                         else              { outputField.appendChild(newElement) }
                   }
              count++
            }
          toGo--
          if (toGo % interval == 0)
             { s = ''
               for (p=Math.floor(toGo/interval); p>0; p--) { s = s + '===' }
               window.status = '('+count+')   '+s
             }
        }
    if (count==0)
       { newElement = document.createElement('OPTION')
         newElement.innerText = "== no match =="
         outputField.appendChild(newElement)
       }
    else // maak de searchResultList uit de values's van de child nodes
       { child = outputField.firstChild
         while (IsDefined(child))
               { searchResultList = searchResultList + child.value + ';'
                 child = child.nextSibling
               }
       }

    outputField.selectIndex=0
    outputField.focus()

    window.status = ''
    return
  }

  function ShowSearchResults(proband,resultList)
  // HTML output van een lijst met <OPTION> elementen die de resultaten van een
  // zoekaktie beschrijven.
  // deze output is bedoeld voor de code voor een nieuwe pagina (aangemaakt met
  // Refocus(). de output van PerformSearch wordt (als <OPTION> elementen)
  // geschreven in een bestaand document.
  { var k,index
    var txt = ''
    if (IsDefined(resultList))
       { var entries = resultList.split(';')
         for (k=0; k<entries.length; k++)
              { index = entries[k]
                if (IsDefined(index))
                   { txt = txt + '<OPTION>' + FormatName1(index)
                     if (index==proband) { searchResultsIndex = k }
                   }
              }
       }
    else
       { txt = '<OPTION>'+searchExample }
    return txt
  }

  function HandleSelectClick()
  // Reageer op een onClick in het SearchResults veld.
  // bepaal welke naam werd aangeklikt en Refocus() op die naam
  // searchResultList is een string met de indices die horen bij de namen in
  // het SearchResults veld.
  { var field = document.getElementById('SearchResults')
    var index = field.selectedIndex
    if (index<0) { return }
    if (searchResultList=='')
       { searchResultList = document.getElementById('storage').innerHTML
         if (searchResultList=='') { return }
       }
    var entries = searchResultList.split(';')
    if (index>entries.length) { return }
    Refocus(entries[index])
    return
  }

  function ShowSearchField(proband)
  // HTML voor de zoekhoek (input, knop resultatenlijst)
  { var left = EmptySide(proband)+100
//    if (left>Xscreen(0)) { left = left+100}
    return '<FORM style="position:absolute;left:'+left+';top:360;width:300" method="POST" onSubmit="return false">'+
             '<INPUT TYPE="text" ID="SearchInput" VALUE="" style="width:170">'+
             '<INPUT TYPE="submit" value="'+searchTxt+'" ID="SearchButton" style="width:80" onMouseOver="ClearStatus(); return true" onClick="PerformSearch()">'+
             '<BR>'+
             '<SELECT ID="SearchResults" SIZE="10" style="width:250" onClick="HandleSelectClick()" onMouseOver="ClickToRefocus(); return true" onMouseOut="ClearStatus()">'+ShowSearchResults(proband,searchResultList)+'</SELECT>'+
           '</FORM>'
  }

  function ShowNearbyFamily(proband)
  // HTML voor de nabije familie: proband, ouders, grootouders en partners plus kinderen
  // bovenaan de pagina komt de naam van de proband in bold tekst
  { DefineCharTable()
    return '<div style="font-size:14px;width:400;left:'+(Xscreen(0)-200)+';top:20"><B>'+FormatName1(proband)+'</B></div>'+
           ShowRelations(proband) +
           ShowAncestors(proband) +
           ShowProband(proband)
  }

