import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * @version 20070318 derde soort output: xml
 * @version 20060319 tweede soort output: meest waarschijnlijke vertaling met alt-hints
 * @version 20051224 woorden tussen () worden voortaan overgeslagen, niet vertaald
 * @version 20051222 schrijft ook een Vertaler.log met stammen, verbuigingen en tellingen
 * @version 20051116 weer sneller door toepassing van vertaalde-woordenlijst htHs
 */
class Vertaler{
  static boolean bTablesLoaded= false;
  static final String SPACES= "                                        "
      + "                                        ";
  static final String CSSHEADER=
      "\n<style type=\"text/css\">"
      + "\ni {color:black; font-style:normal}"
      + "\nb {color:#ccbb99; font-weight:normal; font-family:courier}"
      + "\ntd {vertical-align:top; color:#ccbb99}"
      + "\ntd.a {vertical-align:top; color:blue}"
      + "\ntd.b {font-family:sans-serif; color:#000; width:20%; font-family:courier}"
      + "\ntd.c {font-family:sans-serif; color:#000}"
      + "\na {color:black; text-decoration:none}"
      + "\n</style>";
  static final String CSSHEADER2=
      "\n<style type=\"text/css\">"
      + "\nb {color: #777; font-style: italic; font-weight: normal;}"
      + "\ni {font-style: normal; cursor: default; border: 1px solid #def;}"
      + "\ni:hover {background-color: #def; border: 1px solid #fff;}"
      + "\n</style>";
  /** aantallen per vertaald woord (kern[TAB]uitvoering) */
  Hashtable<String,Integer> htTrans/*= new Hashtable()*/;
  Hashtable<String,Integer> htUntrans= new Hashtable<String,Integer>();
  /** dit zijn mogelijke vertalingen per woordstring */
  Hashtable<String,Vector<Woord>> htHs= new Hashtable<String,Vector<Woord>>();
  /** het hele boek, bestaand uit regels gescheiden door newlines */
  String sBoek;
  /** kernwoorden als index; lijsten Woorden als waarden */
  static Hashtable<String,Vector<Woord>> htCores;
  /** Hebreeuwse woorden met plaatsen van voorkomen (nog te maken?) */
  static Hashtable<String,StringBuffer> htHConcord= new Hashtable<String,StringBuffer>();
  /** Nederlandse vertalingen met Hebreeuwse woorden in een Vector */
  static Hashtable<String,Vector<String>> htNlHe= new Hashtable<String,Vector<String>>();
  int iHoofdstuk;
  boolean bPlainText;
  /** aramees boek, bijvoorbeeld daniel of ezra */
  boolean bAramText;
  static Woord NULL_WOORD= new Woord('\0', "", "", "{...}");
  static String[] asPrefix;
  static String[] asPostfix;
  static boolean bWriteWHtml;
  //----------------------------------------------------------------------------

  /**
   *  Deze methode leest vertaaltabellen
   */
  static{
    htCores= new Hashtable<String,Vector<Woord>>();
    asPrefix= Woord.getPrefixes();
    asPostfix= Woord.getPostfixes();
    //SortedMap map= java.nio.charset.Charset.availableCharsets();
    //System.out.println("Available charset: " + map.firstKey()) ;
    try{
      FileInputStream fis= new FileInputStream("wwoordenlijst");
      byte[] abBuf= new byte[fis.available()];
      System.out.print("wwoordenlijst  lezen... ");
      fis.read(abBuf);
      System.out.print("regels... ");
      String[] asLine= new String(abBuf, "ISO-8859-1").split("[\\n]");
      System.out.print(asLine.length + "; ");
      for (int i= 0; i < asLine.length; i++){
        addTableLine(asLine[i]);
      }
      System.out.println("OK");

      fis= new FileInputStream("woordenlijst");
      abBuf= new byte[fis.available()];
      System.out.print("woordenlijst   lezen... ");
      fis.read(abBuf);
      System.out.print("regels... ");
      asLine= new String(abBuf, "ISO-8859-1").split("[\\n]");
      System.out.print(asLine.length + "; ");
      for (int i= 0; i < asLine.length; i++){
        addTableLine(asLine[i]);
      }
      System.out.println("OK");

      fis= new FileInputStream("wwoordenlijstv");
      abBuf= new byte[fis.available()];
      System.out.print("wwoordenlijstv lezen... ");
      fis.read(abBuf);
      System.out.print("regels... ");
      asLine= new String(abBuf, "ISO-8859-1").split("[\\n]");
      System.out.print(asLine.length + "; ");
      for (int i= 0; i < asLine.length; i++){
        addTableLine(asLine[i]);
      }
      System.out.println("OK");
      bTablesLoaded= true;
    } catch(Exception e){
      e.printStackTrace();
    }
  }
  //----------------------------------------------------------------------------

  /**
   *  Voegt een vertaaltabelregel toe aan onze interne vertaaltabel
   */
  static void addTableLine(
      String sLine){
    if (sLine.trim().length()==0){
      return;
    }
    if (sLine.length() > 1 && sLine.charAt(1)=='_'){
      // onverbogen werkwoordsvorm, apart behandelen
      if (!bWriteWHtml){
        return;
      }
      StringTokenizer st= new StringTokenizer(sLine, "\t");
      String sRoot= st.nextToken();
      String sN= st.nextToken();
      String sI= st.hasMoreTokens() ? st.nextToken() : null;
      Integer oiRoot= Woord.htRoots.get(sRoot);
      if (oiRoot==null){
        oiRoot= new Integer(Woord.htRoots.size());
        Woord.htRoots.put(sRoot, oiRoot);
        Woord.log(sRoot, sN, sI);
      }
      return;
    }
    char cType= sLine.charAt(0);
    if (!Woord.isValidType(cType)){
      if (cType != '/'){ // commentaarregel
        System.err.println("Opmaakfout in regel: [" + sLine + "]");
      }
      return;
    }
    int iT1= sLine.indexOf("\t");
    if (iT1 < 0){
      System.err.println("\nTabloze regel: [" + sLine + "]");
      return;
    }
    String sH= sLine.substring(1, iT1);
    String sN= sLine.substring(sLine.indexOf("\t") + 1).trim();
    int iTab2= sN.indexOf("\t");
    String sInfo= null;
    if (iTab2 >= 0){
      sInfo= sN.substring(iTab2 + 1);
      sN= sN.substring(0, iTab2);
    }
    Woord w0= new Woord(cType, sH, sN, sInfo);
    String[] asCore= w0.getCores();
    for (int j= 0; j < asCore.length; j++){
      Vector<Woord> v= htCores.get(asCore[j]);
      if (v==null){
        v= new Vector<Woord>();
        htCores.put(asCore[j], v);
      }
      v.add(w0);
    }
  }
  //----------------------------------------------------------------------------

  /**
   * Leest het gegeven bestand uit de map ./heb/
   * en schrijft een bestand in ./heb-nl/
   */
  public static void main(
      String[] asArg){
    String sInFile= asArg[0];
    String sInDir= "./heb/";
    Vertaler v= new Vertaler();
    Woord.setOutDir("./heb-nl/");
    for (int i= 0; i < asArg.length; i++){
      sInFile= asArg[i];
      if (sInFile.startsWith("-")){
        /* optie! */
        if (sInFile.equals("-w")){ // schrijf w.html
           bWriteWHtml= true;
           Woord.setWriteW(true);
        }
        continue;
      }
      if (sInFile.startsWith(sInDir)){
        sInFile= sInFile.substring(sInDir.length());
      }
      if (!new File(sInDir + sInFile).canRead()){
        System.err.println("Kon " + sInDir + sInFile + " niet lezen.");
        return;
      }
      v.bAramText= (sInFile.indexOf("daniel")>=0 || sInFile.indexOf("ezra")>=0);
      v.vertaal(sInFile, sInDir, "./heb-nl/");
    }
    System.out.println();
    Woord.log(null, null, null); // afluiten html-woordenlijst w.html

    // Concordantie van Hebreeuwse woorden uitschrijven
    String[] asHWoord= (String[]) htHConcord.keySet().toArray(new String[0]);
    Arrays.sort(asHWoord);
    // Schrijf de woorden uit die in de vertaling ook echt voorkomen
    try{
      PrintStream outCIndex= new PrintStream(
          new FileOutputStream("./Concordantie.idx"), false, "ISO-8859-1");
      PrintStream outCData= new PrintStream(
          new FileOutputStream("./Concordantie.txt"), false, "ISO-8859-1");
      long lCOffset= 0;
      for (int i= 0; i < asHWoord.length; i++){
        String sHWoord= asHWoord[i];
        StringBuffer sbPlaatsen= (StringBuffer) htHConcord.get(sHWoord);
        String sPlaatsen= sbPlaatsen.toString();
        outCIndex.println(sHWoord + "\t" + lCOffset);
        outCData.println(sPlaatsen);
        lCOffset += sPlaatsen.length() + 1; // + LF
      }
      outCData.close();
      outCIndex.close();
    } catch(Exception e){
      e.printStackTrace();
    }

    // omgekeerde vertalingenlijst
    String[] asNlTrans= htNlHe.keySet().toArray(new String[0]);
    Arrays.sort(asNlTrans);
    // Schrijf de vertalingen uit met de vertaalde woorden komma-gescheiden
    try{
      PrintStream outNData= new PrintStream(
          new FileOutputStream("./Vertalingen.txt"), false, "ISO-8859-1");
      for (int i= 0; i < asNlTrans.length; i++){
        String sNl= asNlTrans[i];
        Vector<String> vHes= htNlHe.get(sNl);
        outNData.print(sNl + "\t" + vHes.get(0));
        for (int j= 1; j < vHes.size(); j++){ 
          outNData.print("," + vHes.get(j));
        }
        outNData.println();
      }
      outNData.close();
    } catch(Exception e){
      e.printStackTrace();
    }
    
    
  }
  //----------------------------------------------------------------------------

  /**
   *  Vertaalt een boek (of een gewoon stuk tekst)
   */
  void vertaal(
      String sFile, 
      String sInDir, 
      String sOutDir){
    bPlainText= false; // tenzij anders bewezen, ga uit van translit-output
    Vector<Woord> vWoorden= htCores.get("OL");
    if (vWoorden==null){
      System.err.println("Testwoord 'OL' niet gevonden.");
      return;
    }
    String[] asLine;
    try{
      FileInputStream fis= new FileInputStream(sInDir + sFile);
      byte[] abBuf= new byte[fis.available()];
      System.out.print("\n" + sFile + " lezen... ");
      fis.read(abBuf);
      sBoek= new String(abBuf, "ISO-8859-1");
      asLine= sBoek.split("[\n]");
      System.out.print(asLine.length + " regels; ");
    } catch(Exception e){
      e.printStackTrace();
      return;
    }

    System.out.print(" -> " + sOutDir + sFile + ".html ");
    PrintStream out= null;
    PrintStream out2= null;
    PrintStream2 out3= null;
    try{
      out= new PrintStream(
          new FileOutputStream(sOutDir + sFile + ".html"), false, "ISO-8859-1");
      out2= new PrintStream(
          new FileOutputStream(sOutDir + "_" + sFile + ".html"), false, "ISO-8859-1");
      out3= new PrintStream2(sOutDir + sFile + ".xml");
    } catch(Exception e){
      e.printStackTrace();
      return;
    }
    out.print("<html>\n<head>\n");
    out.print(CSSHEADER);
    out.print("\n<title>" + sFile + "</title>");
    out.print("\n</head>\n<body>");
    out.print("<base target=\"w\"></base>");
    out2.print("<html>\n<head>\n");
    out2.print(CSSHEADER2);
    out2.print("\n<title>" + sFile + "</title>");
    out2.print("\n</head>\n<body bgcolor=\"bbddff\">");
    out3.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
    out3.println("<?xml-stylesheet type=\"text/xsl\" href=\"./heb-nl.xsl\"?>\n");
    out3.println("<book name=\"" + sFile + "\">");

    iHoofdstuk= 0;
    int nWoorden= 0;
    int nVertaald= 0;
    for (int i= 0; i < asLine.length; i++){
      String sLine= asLine[i];
      if (sLine.trim().length() == 0){
        continue;
      }
      Stats st= vertaalRegel(sFile, asLine[i], out, out2, out3);
      nWoorden+= (st.nFound + st.nNotFound);
      nVertaald+= (st.nFound);
    }
    out3.println("</chapter>");
    out3.println("</book>");
    try{
      out.close();
      out2.close();
      out3.close();
    } catch(Exception e){
    }
    /* Onvertaalbare woordenlijst uitschrijven */
    System.out.print(" " + ((nVertaald * 100)/nWoorden) + "% ");
    if (new File("Vertaler.err.log").canRead()){
      new File("Vertaler.err.log").delete();
    }
    try{
      RandomAccessFile rafU= new RandomAccessFile("Vertaler.err.log", "rw");
      //PrintStream outU= new PrintStream(new FileOutputStream("Vertaler.err.log"),
      //    false, "ISO-8859-1");
      Enumeration<String> enmKeys= htUntrans.keys();
      while (enmKeys.hasMoreElements()){
        String sKey= enmKeys.nextElement();
        Integer oin= htUntrans.get(sKey);
        //outU.println(sKey + "\t" + oin.intValue());
        String sOut= sKey + "\t" + oin.intValue();
        //byte[] aOut= sOut.getBytes(/*"ISO-8859-1"*/"ISO-8859-1");
        byte[] aOut= sOut.getBytes("ISO-8859-1");
        if (aOut.length > sOut.length()){
          System.err.println(sOut + ": +" + (aOut.length - sOut.length()));
        }
        rafU.write(aOut);
        rafU.write("\n".getBytes());
      }
      //outU.close();
      rafU.close();
    } catch(Exception e){
      e.printStackTrace();
    }
    if (htTrans!=null){
      try{
        PrintStream outU= new PrintStream(
            new FileOutputStream("Vertaler.log"), false, "ISO-8859-1");
        Enumeration enmKeys= htTrans.keys();
        int nWords= 0;
        String[] asWord= new String[htTrans.size()];
        while (enmKeys.hasMoreElements()){
          asWord[nWords++]= (String) enmKeys.nextElement();
        }
        Arrays.sort(asWord);
        for (int i= 0; i < nWords; i++){
          String sKey= asWord[i];
          Integer oin= htTrans.get(sKey);
          outU.println(sKey + "\t" + oin.intValue());
        }
        outU.close();
      } catch(Exception e){
        e.printStackTrace();
      }
    }
  }
  //----------------------------------------------------------------------------
  
  /**
   * Vertaalt een regel
   */
  Stats vertaalRegel(
      String sBoek,
      String sLine, 
      PrintStream out,
      PrintStream out2,
      PrintStream2 out3){
    Stats st= new Stats();
    char c0= sLine.charAt(0);
    if (c0 < '0' || c0 > '9'){ // hoofdstuk-aanduiding?
      if (sLine.startsWith("Hoofdstuk")){
        if (iHoofdstuk > 0){
          out.println("</table>");
          out2.println("</dl>");
          out3.println("</chapter>");
        }
        out.println("\n<h2 align=\"center\">" + sLine + "</h2>");
        iHoofdstuk= Integer.parseInt(sLine.substring(sLine.indexOf(" ")).trim());
        out.println("<table>");
        out2.println("\n<h2 align=\"center\">" + sLine + "</h2>");
        out2.println("<dl>");
        out3.println("<chapter i=\"" + iHoofdstuk + "\">");
        return st;
      } else{
        if (!bPlainText){
          System.out.print("(uit export van ivr2txt) ");
        }
        bPlainText= true; // er volgt een regel, die we in een tabel stoppen
        out.println("</table>");
        out.println("<table>");
      }
    }
    String sHWoorden;
    String sVerseId= ":";
    int iVerseOffset= 0;
    if (bPlainText){
      out.print("<tr>");
      out.print("<td class=\"a\"></td>");
      /* behandel nu de inhoud van het vers */
      sHWoorden= sLine.trim();
      out.print("<td class=\"b\">" + sHWoorden + "</td>");
    } else{
      String sNr= sLine.substring(0, sLine.indexOf(" "));
      int iVers= (int) Double.parseDouble(sNr.trim());
      out.print("<tr><a name=\"" + lpad("" + iHoofdstuk, 3) 
          + ":" + lpad("" + iVers, 3) + "\"></a>");
      out.print("<td class=\"a\"><nobr>" + sNr + "</nobr></td>");
      out2.print("<dt>" + sNr + "<dd>");
      sVerseId= iHoofdstuk + ":" + iVers;

      iVerseOffset= out3.getFilePointer();      
      out3.println("<verse i=\"" + iVers + "\" id=\"" + sVerseId + "\">");
      /* behandel nu de inhoud van het vers */
      sHWoorden= sLine.substring(sNr.length()).trim();
      out.print("<td class=\"b\">" + sHWoorden + "</td>");
    }
    String[] asW= sHWoorden.split("[\\r\\n\\t\\ ?\\-\\.\\,\\:\\;\\\"]");
    /** vector met optielijsten en evt. onvertaalbare originelen */
    Vector<Object> vWs= new Vector<Object>();
    /** voor out3, parallel met vWs */
    Vector<String> vOrigs= new Vector<String>();
    for (int iInLine= 0; iInLine < asW.length; iInLine++){
      if (asW[iInLine].length() == 0){
        continue;
      }
      String sW= asW[iInLine];
      if (sW.startsWith("(") || sW.endsWith(")")){
        // deze 'verduidelijkingen' vertalen we niet, alleen het eraan voorafgaande woord
        continue;
      }
      /* loop om leveranciers bij verschillende pre- en postfixes te verzamelen */
      Vector<Woord> vHs= htHs.get(sW);
      if (vHs==null){
        vHs= new Vector<Woord>(); // geleverde woorden die precies kloppen
        htHs.put(sW, vHs);
        for (int iR= 0; iR < asPostfix.length; iR++){
          String sPostfix= asPostfix[iR];
          if (!sW.endsWith(sPostfix)){ // ook voor een lege postfix
            continue;
          }
          for (int iL= 0; iL < asPrefix.length; iL++){
            String sPrefix= asPrefix[iL];
            if (!sW.startsWith(sPrefix)){ // ook voor een lege prefix
              continue;
            }
            if (sW.length() <= sPostfix.length() + sPrefix.length()){
              continue; // er blijft niks meer over tussen voor- en achtervoegsel
            }
            String sTryCore= sW.substring(sPrefix.length(),
                sW.length() - sPostfix.length());
            Vector vLs= (Vector) htCores.get(sTryCore);
            if (vLs==null){
              continue;
            }
            /* vraag aan elke leverancier de mogelijke vertalingen (als Woord) */
            for (int i= 0; i < vLs.size(); i++){
              Woord w= (Woord) vLs.get(i);
              Vector<Woord> vNs= w.translate(sW, sPrefix, sPostfix, bAramText);
              vHs.addAll(vNs); // hier worden voor- en achtervoegsels aangevuld
            }
          }
        }
      }
      if (vHs.size() == 0){
        vWs.add(sW); // origineel invoegen
      } else{
        vWs.add(vHs);
      }
      vOrigs.add(sW);
    }
    /* zin ontleed in losse woorden, voor een deel voorlopig vertaald (optielijsten) */
    /* Nu de Nederlandse vertaling uitschrijven */
    out.print("<td class=\"c\">");
    for (int i= 0; i < vWs.size(); i++){
      Object o= vWs.get(i);
      out3.println("  <word>");
      out3.println("    <he>" + vOrigs.get(i) + "</he>");
      if (o instanceof String){ // onvertaalbaar
        st.nNotFound++;
        String sUn= (String) o;
        Integer oin= htUntrans.get(sUn);
        if (oin==null){
          oin= new Integer(0);
        }
        htUntrans.put(sUn, oin.intValue() + 1);
        out.print("<br><b>" + rpad(sUn, "_________") + "_</b>"); // geen vertaling
        out2.print(" <b>"+sUn+"</b>");
      } else{ // aantal opties, nog onbepaald welke de beste is
        st.nFound++;
        Vector vHs= (Vector) o;
        String sUn= ((Woord) vHs.get(0)).sH;
        out.print("<br><b>" + rpad(sUn, "_________") + "_</b>"); // vertaling volgt...
        /* probeer een keus te maken */
        Woord[] a2w= null;
        if (i < vWs.size() - 1
            && vWs.get(i+1) instanceof Vector){
          a2w= match(vHs, (Vector) vWs.get(i+1));
        }
        if (a2w != null){
          /* we 'weten' de juiste vertaling van dit woord en het volgende */
          out.print(a2w[0].sN);
          out.print(a2w[1].sN);
          out2.print(" <i>" + a2w[0].sN + "</i>");
          out2.print(" <i>" + a2w[1].sN + "</i>");
          out3.println("    <nl>" + a2w[0].sN + "</nl>");
          addNlHe(a2w[0].sN, vOrigs.get(i).toString());
          out3.println("    <nl>" + a2w[1].sN + "</nl>");
          addNlHe(a2w[1].sN, vOrigs.get(i).toString());
          addHWoordPlaats(sBoek, vOrigs.get(i).toString(), sVerseId, iVerseOffset);
          i++; // volgende woord al gekozen, nu overslaan
        } else{
          /* geen keus kunnen maken - alle opties uitschrijven */
          /* maar geen dubbelen */
          Vector<String> vNlOut= new Vector<String>();
          Vector<Woord> vOut= new Vector<Woord>();
          int iMaxComplex= 0;
          for (int j= 0; j < vHs.size(); j++){
            Woord w= (Woord) vHs.get(j);
            if (vNlOut.contains(w.sN)){
              continue;
            }
            iMaxComplex= Math.max(iMaxComplex, w.iComplex);
            vOut.add(w);
            vNlOut.add(w.sN);
          }
          /* schrijf de oplossingen in oplopende volgorde van complexiteit */
          int nOut= 0;
          String sOut2Value= null; // onthouden, omdat we eerst de title moeten schrijven
          out2.print(" <i title=\"");
          for (int iComplex= 0; iComplex <= iMaxComplex; iComplex++){
            for (int j= 0; j < vOut.size(); j++){
              Woord w= vOut.get(j);
              if (w.iComplex!=iComplex){
                continue;
              }
              if (nOut > 0){
                out.print(" <b>|</b> ");
              }
              if (bWriteWHtml){
                out.print("<a href=\"w.html#" 
                    + Woord.htRoots.get(w.sRoot) + "\">"
                    + w.sN
                    + "</a>");
              } else{
                out.print(w.sN);
                out3.println("    <nl>" + w.sN + "</nl>");
                addHWoordPlaats(sBoek, vOrigs.get(i).toString(), sVerseId, iVerseOffset);
                addNlHe(w.sN, vOrigs.get(i).toString());
              }
              if (sOut2Value==null){
                sOut2Value= w.sN;
              } else{ // schrijf de alternatieven (meteen)
                if (nOut > 1){
                  out2.print(", ");
                }
                out2.print(w.sN);
              }
              nOut++;
              /* registreer dit woord in de lijst gevonden oplossingen */
              String sKey= w.sRoot + "\t" + w.sH;
              if (htTrans!=null){
                Integer oin= htTrans.get(sKey);
                 if (oin==null){
                  oin= new Integer(0);
                }
                htTrans.put(sKey, oin.intValue() + 1);
              }
            }
          }
          out2.print("\">" + sOut2Value + "</i>");
          /* Nederlandse oplossingen uitgeschreven */
        }
        /* einde vertaling van 1 woord */
      }
      out3.println("  </word>");
    } // alle woorden in de zin behandeld
    out.println("</td></tr>"); // einde vers
    out3.println("</verse>");
    return st;
  }
  //----------------------------------------------------------------------------

  String lpad(
      String sIn, 
      int nLen){
    return SPACES.substring(0, nLen - sIn.length())
        + sIn;
  }
  //----------------------------------------------------------------------------

  /**
   * Vermeldt een vers waar het vertaalde Hebreeuwse woord in voorkomt
   */
  void addHWoordPlaats(String sBook, String sH, String sVerseId, int iVerseOffset){
    StringBuffer sbPlaatsen= (StringBuffer) htHConcord.get(sH);
    if (sbPlaatsen==null){
      sbPlaatsen= new StringBuffer();
      htHConcord.put(sH, sbPlaatsen);
    }
    String sPlaatsen= sbPlaatsen.toString();
    if (sPlaatsen.indexOf(sBook) < 0){ // (geen boeken die een deel v/d naam van een andere zijn)
      if (sPlaatsen.length() > 0){
        sbPlaatsen.append("; ");
      }
      sbPlaatsen.append(sBook);
      sbPlaatsen.append(" " + sVerseId + "@" + iVerseOffset);
    } else if (sPlaatsen.endsWith("," + sVerseId + "@" + iVerseOffset) 
          || sPlaatsen.endsWith(" " + sVerseId + "@" + iVerseOffset)){
      return;
    } else{
      sbPlaatsen.append("," + sVerseId + "@" + iVerseOffset);
    }
  }
  //----------------------------------------------------------------------------

  /**
   * Slaat een Hebreeuws woord (extra) op bij een nederlandse vertaling.
   */
  void addNlHe(String sNl, String sHe){
    Vector<String> vHe= htNlHe.get(sNl);
    if (vHe==null){
      vHe= new Vector<String>();
      htNlHe.put(sNl, vHe);
    }
    if (vHe.contains(sHe)){
      return; // OK
    }
    vHe.add(sHe); // nu OK
  }
  //----------------------------------------------------------------------------
  
  /**
   * Probeer uit beide lijsten een koppel van bij elkaar passende woorden
   * te kiezen.
   */
  Woord[] match(
      Vector<Woord> v1, 
      Vector<Woord> v2){
    Woord[] aw1= v1.toArray(new Woord[0]);
    Woord[] aw2= v2.toArray(new Woord[0]);
    Woord[] aw= new Woord[2];
    boolean bTest1= false; // "gezegde gevolgd door onderwerp"
    for (int i= 0; i < aw1.length; i++){
      for (int j= 0; j < aw2.length; j++){
        if (bTest1
            && aw1[i].cType == '#' // gezegde gevolgd door onderwerp?
            && (aw2[j].cType == '.' || aw2[j].cType == '*')
            && aw1[i].cPerson == '3'
            && aw2[j].cCase == '1'
            && (aw1[i].cGender == '.' 
                || aw1[i].cGender == '\0' 
                || aw1[i].cGender == aw2[j].cGender)
            && aw1[i].cNumber == aw2[j].cNumber){
          // TODO: dit past wel, maar er zijn misschien meer mogelijkheden...
          System.err.println("'" + aw1[i].sN + "' past bij '" + aw2[j].sN + "'");
          /* vervang het deel tussen haakjes door ons onderwerp (of niet) */
          String sOnderwerp= "<font color=\"blue\">" + aw2[j].sN + "</font>";
          if (aw1[i].sN.indexOf("(") < 0){
            /* inpassen niet mogelijk... */
            aw[0]= aw1[i];
            aw[1]= aw2[j];
          } else{
            Woord w1= (Woord) aw1[i].clone();
            int i0= w1.sN.indexOf("(");
            int i1= w1.sN.indexOf(")");
            w1.sN= w1.sN.substring(0, i0)
                + sOnderwerp
                + w1.sN.substring(i1 + 1);
            aw[0]= w1;
            aw[1]= NULL_WOORD;
          }
          return aw;
          // einde gezegde gevolgd door onderwerp
        } else{
          /* andere combinaties? zoals "zn van" + "zn" */
        }
      } // alle woorden van Vector 2
    } // alle woorden van Vector 1
    return null; // geen koppel kunnen maken
  }

  //----------------------------------------------------------------------------
  /**
   *  Vult de gegeven string aan met karakters uit de tweede string.
   */
  String rpad(String sSrc, String sPad){
    if (sPad.length() < sSrc.length()){
      return sSrc;
    }
    return sSrc + sPad.substring(sSrc.length());
  }
  //----------------------------------------------------------------------------
} // class Vertaler
//------------------------------------------------------------------------------

/**
 *  Behalve de hebreeuwse string bevat deze klasse de Nederlandse vertaling
 *  en kenmerken zoals 'zelfstandig naamwoord' en dergelijke.
 */
class Woord{
  static String sTypes= ".+-*@#%";
  char cType; // code voor het soort woord, bijv. '#' = werkwoordsvorm, 
              // '@' = voorzetsel, '%' voor telwoord enz.
  String sH;  // Hebreeuws woordbeeld
  String sRoot; // stam van het woord
  String sN;  // Nederlandse vertaling
  String sNp;  // meervoudsvorm vh Nederlandse woordenboekwoord
  char cPerson; // '1', '2' of '3'
  char cGender; // 'f' (vrouwelijk) of 'm' (mannelijk)
  char cPluralForm; // 'f' (vrouwelijk) of 'm' (mannelijk): -WT of -IM
  char cNGender; // 'o' als het Nederlandse woord onzijdig is (voor keuze tussen de en het)
  char cNumber; // 's' (enkelvoud) of 'p' (meervoud); 'S' als meervoud niet bestaat of vanwege afwijkende vorm apart wordt vermeld
  char cCase;   // '1' (onderwerp), '2' (genitief), '3' (meewerkend), '4' (lijdend)
  boolean bRaw; // true als het woord alleen met achtervoegsel(s) zo geschreven wordt
  boolean bConstr; // woord staat in status constructus
  boolean bAram; // aramees woord: alleen in Daniel en Ezra (heel soms in Jeremia?)
  int iComplex; // getal dat aantal voor- en achtervoegsels weergeeft
  static String[][] aasPrefix=
    {{".+-*@#%", ""  , ""}
    ,{".%"     , "E" , "de "} // %: bijv ESLSE
    ,{"+"      , "E" , "(de) "} // bepaald lidwoord wordt bij b. nw. in Hebreeuws herhaald; bij zelfst. gebruikt bijv. nw. zou 'de' misschien wel passend zijn
    ,{"#*-"    , "E" , "(is het zo) dat "}
    ,{".+-*@#%", "W" , "en "}
    ,{".+"     , "B" , "bij (de) "} // officieel 'in, met'
    ,{"*#%"    , "B" , "bij "} // #: bijv. BNPL
    ,{".+*"    , "K" , "zoals "} // soms 'bij', 'tijdens'
    ,{"#"      , "K" , "als "} // in vt: toen
    ,{"."      , "KE", "zoals de "} // soms 'bij', 'tijdens'
    ,{".+*%"   , "L" , "aan "}
    ,{".+*"    , "M" , "van "}
    ,{"."      , "ME", "van de "}
    ,{".*"     , "LM", "tot van "} // bijv. LMRHWQ
    ,{".*"     , "ML", "weg van "}
    ,{"+#"     , "S" , "dat "} // "#": bijv. SEIE
    ,{".*"     , "WE", "en de "}
    ,{"+"      , "WE", "en (de) "} // zie regel "+"     , "E"
    ,{"."      , "WB", "en bij (de) "}
    ,{"*%"     , "WB", "en bij "} // bijv. BSBOT
    ,{".*"     , "WL", "en aan "}
    ,{".*"     , "LE", "aan de "}
    ,{".*"     , "WM", "en van "}};
  static String[][] aasPostfix=    // TODO: kloppen onderstaande f-vormen allemaal?
    {{".+-*@#%", "",  ""   , ""     , "mf", ""}
    ,{"."  , "A",   "(de) ", ""     , "mf", "a"} // aramees (*** twijfelachtig
    ,{"+"  , "A",   "(zo) ", ""     , "mf", "a"} // aramees (*** twijfelachtig
    ,{".*" , "E",   "naar ", ""     , "mf", ""}
    ,{"+"  , "E",   ""     , ""     , "f", "", ""} // f. verbuiging van een bijv. nw.
    ,{"#"  , "E",   ""     , " (er)naar", "mf", ""}
    ,{".#" , "E",   ""     , " (...) haar", "mf", ""}
    ,{"."  , "EM",  ""     , "-en (...) hen", "mf", ""}
    ,{"."  , "EN",  ""     , "-en (...) hen", "mf", ""}
    ,{"."  , "I",   ""     , "-en van", "m", ""}
    ,{".#" , "I",   ""     , " (...) mij", "mf", ""}
    ,{"."  , "I",   ""     , "-en (...) mij", "m", ""}  // nieuw; zie AIBI (mijn vijanden)
    ,{"."  , "IE",  "naar ", "-en"           , "m", ""}
    ,{"."  , "IE",  ""     , "-en (...) haar", "m", ""}
    ,{"."  , "IEM", ""     , "-en (...) hen", "m", ""} // vb: MOLLIEM
    ,{"."  , "IEN", ""     , "-en (...) hen", "m", ""} // vb: HßRIEN
    ,{"."  , "IA",  "(de) ", "-en"  , "m", "a"} // aramees (*** twijfelachtig
    ,{"."  , "IK",  ""     , "-en (...) jou", "m", ""}
    ,{"."  , "IKM", ""     , "-en (...) jullie", "m", ""}
    ,{"."  , "IKN", ""     , "-en (...) jullie", "m", ""}
    ,{"."  , "IM",  ""     , "-en"  , "m", ""} // m
    ,{"+"  , "IM",  ""     , " (mv)", "m", ""} // m 
    ,{"."  , "IN",  ""     , "-en"  , "m", "a"} // m, aramees
    ,{"+"  , "IN",  ""     , " (mv)", "m", "a"} // m, aramees
    ,{"."  , "INW", ""     , "-en (...) ons", "m", ""}
    ,{"."  , "IW",  ""     , "-en (...) hem", "m", ""}
    ,{".#" , "K",   ""     , " (...) jou", "mf", ""}
    ,{".#" , "KM",  ""     , " (...) jullie", "mf", ""}
    ,{".#" , "KN",  ""     , " (...) jullie", "mf", ""}
    ,{".#%", "M",   ""     , " (...) hen", "mf", ""} // soms ook afko van IM ($DIQM) %: voor LARBOTM
    ,{".#%", "N",   ""     , " (...) hen", "mf", ""} // %: bijv. LARBOTN
  //,{".#" , "NI",  ""     , " (...) mij", "mf", ""} // (bv. HINI?, maar met wegvallen eind-E
    ,{"#"  , "NE",  ""     , " (...) haar", "mf", ""} // (bijv. IXIRNE)
    ,{"#"  , "NI",  ""     , " (...) mij", "mf", ""} // (bijv. XBBWNI) samentrekking van ANI?
    ,{".#" , "NW",  ""     , " (...) ons", "mf", ""}
    ,{"."  , "T",   ""     , " van" , "f", ""} // f (enkelvoud status constructus)
    ,{"."  , "T",   ""     , "-en (van)", "f", ""} // f (mv, soms daarbij status constructus)
    ,{"."  , "TA",  "(de) ", ""     , "f", "a"} // aramees (*** twijfelachtig
    ,{"."  , "TA",  "(de) ", "-en"  , "f", "a"} // aramees (*** twijfelachtig
    ,{"."  , "TE",  ""     , " (...) haar", "f", ""} // bijv. NDTE
    ,{"."  , "TE",  "naar ", "-en"  , "f", ""}
    ,{"."  , "TE",  ""     , "-en (...) haar", "f", ""}
    ,{"."  , "TEM", ""     , "-en (...) hen", "f", ""} // f (*** komt dit voor?
    ,{"."  , "TEN", ""     , "-en (...) hen", "f", ""} // f
    ,{"."  , "TI",  ""     , " (...) mij", "f", ""} // f
    ,{"."  , "TI",  ""     , "-en (...) mij", "f", ""} // f
    ,{"."  , "TIE", "naar ", "-en"          , "f", ""} // f 
    ,{"."  , "TIE", ""     , "-en (...) haar", "f", ""} // f 
    ,{"."  , "TIEN",""     , "-en (...) hen", "f", ""} // f
    ,{"."  , "TIK", ""     , "-en (...) jou", "f", ""} // f
    ,{"."  , "TIKM",""     , "-en (...) jullie", "f", ""} // f
    ,{"."  , "TIKN",""     , "-en (...) jullie", "f", ""} // f
    ,{"."  , "TINW",""     , "-en (...) ons", "f", ""} // f
    ,{"."  , "TIW", ""     , "-en (...) hem", "f", ""} // f
    ,{"."  , "TK",  ""     , " (...) jou", "f", ""} // f
    ,{"."  , "TM",  ""     , "-en (...) hen", "f", ""} // f bijv. MSPHTM
    ,{"."  , "TM",  ""     , " (...) hen", "f", ""} // bijv. LDRTM
    ,{"."  , "TW",  ""     , " (...) hem", "f", ""} // f
    ,{".#" , "W",   ""     , " (...) hem", "mf", ""}
    ,{"."  , "WT",  ""     , "-en"  , "f", ""} // f
    ,{"+"  , "WT",  ""     , " (mv)", "f", ""} // f
    ,{"."  , "WTA",  "(de) ", "-en" , "f", "a"} // f, aramees 
    ,{"."  , "WTI", ""     , "-en (...) mij", "f", ""} // f
    ,{"."  , "WTIK",""     , "-en (...) jou", "f", ""} // f
    ,{"."  , "WTIKM",""    , "-en (...) jullie", "f", ""} // f
    ,{"."  , "WTIW",""     , "-en (...) hem", "f", ""} // f
    ,{"."  , "WTM", ""     , "-en (...) hen", "f", ""} // f bijv. MSPHWTM
    };
  static Hashtable<String,Integer> htRoots= new Hashtable<String,Integer>();
  private static String sOutDir= "./heb-nl/";
  static PrintStream outW; // voor de woorden-index w.html
  static boolean bWriteW;
  //----------------------------------------------------------------------------

  static void setOutDir(String sDir){
    sOutDir= sDir;
  }
  //----------------------------------------------------------------------------

  static void setWriteW(boolean bWrite){
    bWriteW= bWrite;
  }
  //----------------------------------------------------------------------------

  /**
   *  Geeft een lijst van alle mogelijke prefixes.
   */
  public static String[] getPrefixes(){
    Vector<String> vRet= new Vector<String>();
    for (int i= 0; i < aasPrefix.length; i++){
      String sPre= aasPrefix[i][1];
      if (vRet.contains(sPre)){
        continue;
      }
      vRet.add(sPre);
    }
    return (String[]) vRet.toArray(new String[0]);
  }
  //----------------------------------------------------------------------------

  /**
   *  Geeft een lijst van alle mogelijke postfixes.
   */
  public static String[] getPostfixes(){
    Vector<String> vRet= new Vector<String>();
    for (int i= 0; i < aasPostfix.length; i++){
      String sPost= aasPostfix[i][1];
      if (vRet.contains(sPost)){
        continue;
      }
      vRet.add(sPost);
    }
    return (String[]) vRet.toArray(new String[0]);
  }
  //----------------------------------------------------------------------------

  Woord(
      char cType, 
      String sH, 
      String sN, 
      String sInfo){
    this.cType= cType;
    this.sH= sH;
    /* leg de stam van dit woord vast */
    if (sInfo!=null && sInfo.indexOf("[") >= 0){
      this.sRoot= sInfo.substring(sInfo.indexOf("[") + 1, sInfo.indexOf("]"));
    } else{
      this.sRoot= "" + cType + sH;
    }
    Integer oiRoot= htRoots.get(sRoot);
    if (oiRoot==null){
      oiRoot= new Integer(htRoots.size());
      htRoots.put(sRoot, oiRoot);
      if (bWriteW){
        log(this, sN, sInfo);
      }
    }
    /* bepaal meervoudsvorm, m/v enz. */
    if (this.cType=='.' && sN.endsWith(")") && sN.indexOf(" (") >= 0){
      /* meervoudsvorm (of -uitgang) gegeven */
      int iMv= sN.lastIndexOf(" (");
      String sMv= sN.substring(iMv + 2, sN.length() - 1);
      this.sN= sN.substring(0, iMv);
      /* nu de meervoudsvorm construeren: */
      if (sMv.startsWith("-")){ // alleen het achtervoegsel voor de meervoudsvorm gegeven
        this.sNp= this.sN + sMv.substring(1);
      } else if (sMv.startsWith("...")){ // mv is een gedeeltelijke vervanging
        int iBr= this.sN.lastIndexOf(sMv.charAt(3));
        this.sNp= this.sN.substring(0, iBr) + sMv.substring(3);
      } else{ // meervoudsvorm in zijn geheel gegeven
        this.sNp= sMv;
      }
      //System.out.println("1 " + this.sN + ", 2 " + this.sNp);
    } else{
      this.sN= sN;
    }
    if (this.cType=='.'){
      cGender= sH.endsWith("E") ? 'f' : 'm'; // meestal zo
    } else{
      cGender= '.';
    }
    cPluralForm= cGender; // ook meestal zo
    if (sInfo != null && sInfo.indexOf("{") >= 0){
      cPerson= '.';
      cNGender= '.';
      cNumber= '.';
      String sPGN= sInfo.substring(sInfo.indexOf("{") + 1, sInfo.indexOf("}"));
      for (int i= 0; i < sPGN.length(); i++){
        if ("fm".indexOf(sPGN.charAt(i)) >= 0){
          cGender= sPGN.charAt(i);
          cPluralForm= cGender;
        } else if ("o".indexOf(sPGN.charAt(i)) >= 0){
          cNGender= sPGN.charAt(i);
        } else if ("123".indexOf(sPGN.charAt(i)) >= 0){
          cPerson= sPGN.charAt(i);
        } else if ("sSp".indexOf(sPGN.charAt(i)) >= 0){
          cNumber= sPGN.charAt(i);
        } else if ("-".indexOf(sPGN.charAt(i)) >= 0){
          bRaw= true;
        } else if ("c".indexOf(sPGN.charAt(i)) >= 0){
          bConstr= true;
        } else if ("a".indexOf(sPGN.charAt(i)) >= 0){
          bAram= true;
        } else if ("x".indexOf(sPGN.charAt(i)) >= 0){
          if (cGender=='m'){
            cPluralForm= 'f';
          } else if (cGender=='f'){
            cPluralForm= 'm';
          } else{
            System.err.println(sH+"\t{"+sPGN+"}: x zonder f of m");
          }
        } else{
          System.err.println(sH+"\t{"+sPGN+"}: '" + sPGN.charAt(i) + "' is vreemd");
        }
      }
    } else if (sInfo==null){
      // wat defaults instellen:
      cNumber= 's';
    }
    if (cType=='.' || cType=='*'){
      cCase= '1'; // TODO: '2' instellen bij ... van-vorm ('-I, -T)
    }
  }
  //----------------------------------------------------------------------------

  /**
   * Geeft het deel van dit woord dat invariant is onder verbuigingen,
   * eventueel twee (of meer?) bij bijv. verdwijnen van bepaald letters.
   */
  public String[] getCores(){
    Vector<String> vRet= new Vector<String>();
    vRet.add(this.sH);
    try{
      if (sH.endsWith("WI")){ // BNWI kan worden verbogen tot BNWTIE
         vRet.add(sH.substring(0, sH.length() - 1) + "T");
      } else if (sH.endsWith("E") || sH.endsWith("I")){
        vRet.add(sH.substring(0, sH.length() - 1));
      } else if (sH.endsWith("IM")){
        vRet.add(sH.substring(0, sH.length() - 1)); // ~IM -> ~IK
      } else if (sH.endsWith("W")){
        vRet.add(sH.substring(0, sH.length() - 1) + "E");
      } else if (sH.charAt(sH.length() - 2) == 'W'){
        vRet.add(sH.substring(0, sH.length() - 2) + sH.charAt(sH.length() - 1));
      } else if (sH.startsWith("II")){
        vRet.add(sH.substring(1));
      }
    } catch(Exception e){
      System.err.println("getCores() sH='" + sH + "': " + e);
    }
    return (String[]) vRet.toArray(new String[0]);
  }
  //----------------------------------------------------------------------------

  public Object clone(){
    Woord w2= new Woord(cType, sH, sN, null);
    w2.sRoot= sRoot;
    w2.cGender= cGender;
    w2.cPluralForm= cPluralForm;
    w2.cNGender= cNGender;
    w2.cNumber= cNumber;
    w2.cPerson= cPerson;
    w2.cCase=   cCase;
    w2.bRaw=    false; // na cloneren wordt eea aangeplakt, daarna dus niet meer ruw
    w2.bConstr= bConstr;
    w2.iComplex= iComplex;
    return w2;
  }
  //----------------------------------------------------------------------------
  static boolean isValidType(
      char cType){
    return sTypes.indexOf(cType) >= 0;
  }
  //----------------------------------------------------------------------------

  String plural(){
    if (this.sNp != null){
      return sNp;
    }
    return this.sN + "-en";
  }
  //----------------------------------------------------------------------------

  /**
   *  Geeft een lijst met alle Woorden die met voor- en achtervoegsels
   *  van dit woord kunnen worden gemaakt.
   *  @param sH het complete Hebreeuwse geval;
   *  @param sPrefix het vermoedelijke voorvoegsel;
   *  @param sPostfix het vermoedelijke achtervoegsel.
   */
  Vector<Woord> translate(
      String sH, String sPrefix, String sPostfix, boolean bAramText){
    Vector<Woord> vRet= new Vector<Woord>();
    if (sH.equals(this.sH) && !this.bRaw){
      vRet.add(this);
      return vRet;
    }

    /* Bepaal de basis voor voor- en achtervoegels (triviaal geworden) */
    Woord w0;
    w0= this;

    /* Allerlei (andere) voor- en achtervoegsels */
    for (int i= 0; i < aasPrefix.length; i++){
      if (!aasPrefix[i][1].equals(sPrefix)){
        continue;
      }
      for (int j= 0; j < aasPostfix.length; j++){
        if (!aasPostfix[j][1].equals(sPostfix)){
          continue;
        }
        if (aasPostfix[j][1].length() == 0 && bRaw){ 
          /* 'ruwe' woorden worden alleen met achtervoegsel geldig */
          continue;
        }
        if (sPrefix.equals(aasPrefix[i][1])
            && sPostfix.equals(aasPostfix[j][1])
            && aasPrefix[i][0].indexOf(w0.cType) >= 0 // prefix geldig voor deze woordsoort
            && aasPostfix[j][0].indexOf(w0.cType) >= 0){
          String sPreH= aasPrefix[i][1];
          String sTempPrefix= aasPrefix[i][2];
          if (cNGender=='o' 
              && (aasPostfix[j][3].indexOf("-en") < 0) // meervouden krijgen nooit 'het'
              && w0.cNumber != 'p'
              && sTempPrefix.indexOf("de") >= 0){
            int iEn= sTempPrefix.indexOf("de");
            sTempPrefix= sTempPrefix.substring(0, iEn) + "het" 
                + sTempPrefix.substring(iEn + 2);
          }
          String sPreN= sTempPrefix.startsWith("en ")
              ? "en " + aasPostfix[j][2] + sTempPrefix.substring(3)
              : aasPostfix[j][2] + sTempPrefix;
          /* (zoiets als 'naar en de man' willen we niet, liever 'en naar de man') */
          String sPostH= aasPostfix[j][1];
          String sPostN= aasPostfix[j][3];
          String sValidGenders= aasPostfix[j][4];
          String sPostLang= aasPostfix[j][5];

          /* Uitvluchten: */
          if (sPostN.indexOf("-en") >= 0
              && (w0.cNumber=='p' || w0.cNumber=='S')){ // van een meervoud (of problematisch te vormen meervoud) gaan we geen automatisch meervoud maken.
            continue;
          }
          //if (sPostH.equals("IM") && cPluralForm!='m' && cType!='+'){
          if (sValidGenders.equals("m") && cPluralForm!='m' && cType!='+'){
            continue;
          }
          if (sValidGenders.equals("f") && cPluralForm!='f' && cType!='+'){
            continue;
          }
          if (sPostN.length() > 0 && w0.sN.endsWith(" van")){ 
            // bij een status constructus geen extra achterv.
            continue;
          }
          if (sPostLang.equals("a") && !bAramText){
            // geen aramese achtervoegsels bij woorden in niet-aramese teksten
            continue;
          }

          int iExtraComplex= 0;
          if (sPreH.length() > 0){
            iExtraComplex++;
          }
          if (sPostH.length() > 0){
            iExtraComplex++;
          }
          if (sPreN.indexOf("de ") >= 0){ // soms moet het 'het' zijn
            if (w0.sN.endsWith("je")){
              int iDe= sPreN.indexOf("de ");
              sPreN= sPreN.substring(0, iDe) + "het " + sPreN.substring(iDe + 3);
            }
          }
          Woord w2= (Woord) w0.clone();
          String sHAlt= null;
          /* N.B.: Hieronder staan een paar constructies met w0.sH.length() - 1,
           * maar dat kan alleen werken als dat eerste deel wordt opgegeven
           * in de getCores()-methode
           */
          if (w0.sH.endsWith("O") && (sPostH.equals("IM"))){
            w2.sH= sPreH + w0.sH + sPostH; // gewoon erachteraan, i.t.t. tot volgende
          } else if (w0.sH.endsWith("WO") && (sPostH.startsWith("I"))){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 2) + "O" + sPostH; // ~OI* ipv ~WOI*
          } else if (w0.sH.endsWith("IOI") && (sPostH.length() > 0)){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1)
                + sPostH; // ESMIONI ipv ESMIOINI
          } else if (w0.sH.endsWith("O") && (sPostH.startsWith("I"))){
            w2.sH= sPreH + w0.sH + "T" + sPostH; // OTIW i.p.v. OIW
            sHAlt= sPreH + w0.sH + sPostH; // wel bijv. LMXOIW
          } else if (w0.sH.endsWith("AE") && (sPostH.length() > 0)){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1)
                + sPostH; // ARAK i.p.v. ARAEK
            sHAlt= sPreH + w0.sH + sPostH; // wel bijv. MRAEW
          } else if (w0.sH.endsWith("WI") && (sPostH.startsWith("I")) 
              && sPostN.indexOf("-en") >= 0){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1) 
                + "T" + sPostH; // WBNWTIE ipv WBNWIIE
          } else if (w0.sH.endsWith("I") && (sPostH.startsWith("I"))){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1) 
                + sPostH; // PIEM i.p.v. PIIEM
            sHAlt= sPreH + w0.sH + sPostH; // ONII naast ONI
          } else if (w0.sH.endsWith("IM") && (sPostH.equals("K"))){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1) 
                + sPostH; // NOWRIM + K = NOWRIK
          } else if (w0.sH.endsWith("W") && (sPostH.startsWith("W"))){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1) 
                + "E" + sPostH; // WIQBREW ipv WIQBRWW
            sHAlt= sPreH + w0.sH + sPostH;
          } else if (w0.sH.substring(0, w0.sH.length() - 1).endsWith("W") 
              && (sPostH.startsWith("W"))
              && (!w0.sH.endsWith("WE"))/* EWE -> EWWT */){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 2) 
                + w0.sH.substring(w0.sH.length() - 1)  + sPostH; // MQMW ipv MQWMW
            sHAlt= sPreH + w0.sH + sPostH;
          } else if (w0.sH.endsWith("E") && cPluralForm=='m' 
              && (sPostH.startsWith("I") && (w0.cNumber=='s'))){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1) 
                + sPostH; // ~E wordt ~IM...
          //} else if (w0.sH.endsWith("E") && (sPostH.startsWith("I") && (w0.cNumber=='s'))){
          //  w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1) 
          //      + "T" + sPostH; // ~E wordt ~TI... // (*** uitgezet 20060129
          } else if (w0.sH.endsWith("E") 
                && (sPostH.startsWith("WT") || (sPostH.startsWith("T")))){
            w2.sH= sPreH + w0.sH.substring(0, w0.sH.length() - 1) 
                + sPostH; // ~E wordt ~T of ~WT
          //} else if (w0.sH.startsWith("II") 
          //      && (sPreH.equals("W"))){
          //  w2.sH= sPreH + w0.sH.substring(1) 
          //      + sPostH; // WII~ wordt WI~
          } else{
            w2.sH= sPreH + w0.sH + sPostH; // gewoon voor- en achtervoegsel aanplakken
          } // TODO: ...WT vervangen door ...T als er iets achter komt. Moet dat hier?
          // TODO: gemengde voor-achtervoegsel-uitzonderingen?
          w2.iComplex+= iExtraComplex;
          if (sPostN.indexOf("-en") >= 0){
            if (w0.cNumber=='p'){
              /* is al een meervoud, dus -en schrappen */
              w2.sN= sPreN + w0.sN + sPostN.substring(3);
            } else{
              w2.cNumber= 'p';
              /* vorm een meervoud en verwijder de signaalstring -en */
              w2.sN= sPreN + w0.plural() + sPostN.substring(3);
            }
          } else{
            if (sPreN.equals("(deze) ") && w2.cNumber=='s' && w2.cNGender=='o'){
              sPreN= "(dit) ";
            } else if (sPreN.equals("(de) ") && w2.cNumber=='s' && w2.cNGender=='o'){
              sPreN= "(het) ";
            }
            w2.sN= sPreN + w0.sN + sPostN;
          }
          if (w2.bAram && !bAramText){
            // geen aramese woordvormen in niet-aramese teksten
          } else if (sH.equals(w2.sH)){
            vRet.add(w2);
          } else if (sH.equals(sHAlt)){
            w2.sH= sHAlt;
            vRet.add(w2);
          } else{
            // de geprobeerde constructie komt niet overeen met het gezochte woordbeeld
          }
        }
      }
    }
    return vRet;
  } // Woord::translate(String, String, String, boolean)
  //----------------------------------------------------------------------------

  static void log(Object o, String sN, String sInfo){
    if (outW==null){
      try{
        outW= new PrintStream(
            new FileOutputStream(sOutDir + "w.html"), false, "ISO-8859-1");
      } catch(Exception e){
      }
      outW.print("<html>"
          + "\n<head>"
          + "\n<link rel=\"stylesheet\" href=\"../index.css\" type=\"text/css\" />"
          + "\n</head>"
          + "\n<body>"
          + "\n<dl>");
    }
    if (o==null){
      try{
        outW.print("\n</dl></body></html>");
        outW.close();
      } catch(Exception e){
      }
      outW= null;
      return;
    }
    String sComment= sInfo==null ? "" : sInfo;
    if (sComment.indexOf("]") > 0){
      sComment= sComment.substring(sComment.indexOf("]") + 1);
    }
    if (sComment.indexOf("}") > 0){
      sComment= sComment.substring(sComment.indexOf("}") + 1);
    }
    String sRoot= "";
    String sDisplayRoot= "";
    String sWoordType= "";
    if (o instanceof Woord){
      sRoot= ((Woord) o).sRoot;
      sDisplayRoot= sRoot.substring(1);
      sWoordType= woordtype(((Woord) o).sRoot.charAt(0));
    } else{
      sRoot= ((String) o);
      sDisplayRoot= sRoot.substring(2);
      sWoordType= "(ww.)";
    }
    outW.print("\n<dt><a name=\"" 
        + htRoots.get(sRoot) + "\">"
        + sDisplayRoot + " <b style=\"font-weight: normal\">" 
        + sWoordType + "</b>"
        + "<dd>" + sN
        + "<dd><i>" + (sComment.equals("")? "&nbsp;" : sComment) + "</i>"
        + "<p />");
  } // Woord::log(Woord)
  //----------------------------------------------------------------------------

  /**
   *  Geeft een naam aan het woordtype, tussen haakjes
   *  @param cPre een van {.+-*@#%}
   */
  static String woordtype(char cPre){
    switch(cPre){
      case '.': return "(znw.)";
      case '+': return "(bnw.)";
      case '*': return "(naam)";
      case '@': return "(voorz.)";
      case '#': return "(ww.)";
      case '%': return "(telw.)";
      default: return "";
    }
  } // Woord::woordtype(char)
  //----------------------------------------------------------------------------
} // class Woord
//------------------------------------------------------------------------------

class Stats{
  int nFound;
  int nNotFound;
}
//------------------------------------------------------------------------------

/**
 *  PrintStream met teller
 */
class PrintStream2{
  PrintStream ps;
  private int iOff;
  //----------------------------------------------------------------------------
  PrintStream2(String sFile)
      throws FileNotFoundException, java.io.UnsupportedEncodingException{
    ps= new PrintStream(new FileOutputStream(sFile), false, "ISO-8859-1");
  }
  //----------------------------------------------------------------------------
  void close()
      throws IOException{
    ps.close();
  }
  //----------------------------------------------------------------------------
  void println(String s){
    ps.println(s);
    iOff+= s.length() + 1;
  }
  //----------------------------------------------------------------------------
  int getFilePointer(){
    return iOff;
  }
  //----------------------------------------------------------------------------
} // class PrintStream2
//------------------------------------------------------------------------------
