/*
 * @(#)Entities.java
 *
 * Summary: Generates various text files describing entities.
 *
 * Copyright: (c) 2004-2010 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.5+
 *
 * Created with: IntelliJ IDEA IDE.
 *
 * Version History:
 *  1.0 2004-01-01 - initial version
 *  1.1 2004-01-01 - optimise using
 *                   text.indexOf('&amp;') and sb.append(string) rather
 *                   than processing character by character.
 *  1.2 2004-07-21 - add stripHTMLTags -
 *                   stripFile also strips tags - add stripNbsp
 *  1.3 2005-06-20 - fix bug in possEntityToChar
 *                   exposed possEntityToChar as public
 *  1.4 2005-07-02 - check for null input
 *  1.5 2005-07-29 - no longer needs entitiestochar.ser
 *                   file. Converted to JDK 1.5 back to 1,2
 *  1.6 2005-09-05 - faster code for stripHTMLTags that
 *                   returns original string if nothing changed.
 *  1.7 2005-09-05
 *  1.8 2007-02-26 - fix bug. hex entity it &#xffff; not &x#ffff;
 *  1.9 2007-03-26
 *  2.0 2007-04-26
 *  2.1 2007-05-10
 *  2.2 2007-05-14 - StripHTMLTags now strips applet, style, script pairs.
 *                   generate hex entities as comments in entitycase.javafrag
 *  2.3 2008-07-29 - refactor code for Entities, add notes to chars, add a few new missing Entities.
 *  2.4 2008-08-05 - add translateNbspTo parameter to several methods and deprecate the versions without it.
 *                   This allows you to directly control how &nbsp; is translated, usually ' ' or (char)160.
 *                   Renamed methods to make it clearer just what sort of input is expect.
 *  2.5 2008-08-06 - add ability to insert XML entities. Convert to JDK 1.5+, with generics, and for:each, StringBuilder
 *  2.6 2009-04-05 - StripEntities now leaves a space behind when it removes a <br><p><td> etc tag.
 *  2.7 2009-11-14 - generate a table for the HTML cheat sheet of quote-like entities.
 *  2.8 2009-12-22 - export table on HTML 5 entities. Now import csv file rather than embed entity facts.
 */
package com.mindprod.entities;

import com.mindprod.common15.StringTools;
import com.mindprod.csv.CSVReader;

import java.io.*;
import java.util.HashMap;
import java.util.HashSet;

import static java.lang.System.err;

/**
 * Generates various text files describing entities.
 * <p/>
 * see main for details.
 * <p/>
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 2.8 2009-12-22 - export table on HTML 5 entities. Now import csv file rather than embed entity facts.
 * @noinspection WeakerAccess
 * @see InsertEntities
 * @see InsertFileEntities
 * @see StripEntities
 * @see StripFileEntities
 * @since 2004-01-01
 */
public final class Entities
    {
    // ------------------------------ CONSTANTS ------------------------------

    /**
     * Longest an HTML 4 entity can be {@value #LONGEST_HTML4_ENTITIY}, at least in our tables, including the lead & and trail ;.
     */
    static final int LONGEST_HTML4_ENTITIY = "&thetasysm".length();

    /**
     * Longest an HTML 5 entity can be {@value #LONGEST_HTML5_ENTITIY}, at least in our tables, including the lead & and trail ;
     */
    static final int LONGEST_HTML5_ENTITIY = "&CounterClockwiseContourIntegral;".length();

    /**
     * undisplayed copyright notice
     *
     * @noinspection UnusedDeclaration
     */
    public static final String EMBEDDED_COPYRIGHT =
            "copyright (c) 2005-2010 Roedy Green, Canadian Mind Products, http://mindprod.com";

    /**
     * @noinspection UnusedDeclaration
     */
    private static final String RELEASE_DATE = "2009-12-22";

    /**
     * title of this package.
     *
     * @noinspection UnusedDeclaration
     */
    private static final String TITLE_STRING = "Entities";

    /**
     * version of this code.
     *
     * @noinspection UnusedDeclaration
     */
    private static final String VERSION_STRING = "2.8";

    /**
     * used to print 8 items per line.
     */
    private static int counter = 0;

    /**
     * entities for the HTML Cheat sheet.
     */
    private static PrintWriter entityArrow;

    /**
     * entities as case statements to convert number to entity.
     */
    private static PrintWriter entityCase;

    /**
     * entities as case statements to use in StripQuotes to get rid of accented chars
     */
    private static PrintWriter entityCaseHex;

    /**
     * Russian numeric entities
     */
    private static PrintWriter entityCyrillic;

    /**
     * entities for the HTML Cheat sheet.
     */
    private static PrintWriter entityGreek;

    /**
     * entities for the HTML-5 entity list
     */
    private static PrintWriter entityHTML5;

    /**
     * entity names without &;.
     */
    private static PrintWriter entityJustKeys;

    /**
     * character codes.
     */
    private static PrintWriter entityJustValues;

    /**
     * entities for the HTML Cheat sheet.
     */
    private static PrintWriter entityLatin;

    /**
     * Polish numeric entities
     */
    private static PrintWriter entityPolish;

    /**
     * entities for the HTML Cheat sheet.
     */
    private static PrintWriter entityQuoter;

    /**
     * entities for the HTML Cheat sheet.
     */
    private static PrintWriter entitySpecial;

    /**
     * entities for the HTML Cheat sheet.
     */
    private static PrintWriter entitySymbol;

    /**
     * For Vslick editor vslick\builtins\html.tagdoc.
     */
    private static PrintWriter entityVslickHtmlTagdoc;

    /**
     * For Vslick editor user.vlx.
     */
    private static PrintWriter entityVslickVlx;

    // -------------------------- STATIC METHODS --------------------------

    /**
     * Used to build the entityToChar conversion table. This is not a constructor. It builds nothing and leaves nothing
     * behind.  It just emits various bits of source code for use elsewhere.
     *
     * @param theCharNumber Character equivalent
     * @param entity        entity with &..;
     * @param description   of this character,  no html allowed. Code special chars as Java, not as entities.
     * @param notes         html giving extra info about this entity.. Code special chars as Java, not as entities.
     * @param categories    one or more category letters:  l=lower case Latin L=upper case Latin (alpha accents) g=lower case Greek G=upper case Greek
     *                      a=arrow s=symbol 4=html4 5=html5 n=not an entity.
     */
    private static void associate(
            String categories,
            int theCharNumber,
            String entity,
            String description,
            String notes
    )
        {
        // Print out in number order, one line in each file each
        // time associate is called.
        // include 4 and 5 arrows
        if ( categories.indexOf( 'a' ) >= 0 )
            {
            singleEntityLine( entity, theCharNumber, description, notes, entityArrow );
            }

        if ( StringTools.haveCommonChar( "4", categories ) )
            {
            associateEntityCase( entity, theCharNumber, description );
            associateEntityCaseHex( entity, theCharNumber );
            associateEntityJustKeys( entity, theCharNumber, description );
            associateEntityJustValues( entity, theCharNumber, description );
            associateEntityVslickVlx( entity );
            associateEntityVslickHtmlTagdoc( entity, theCharNumber, description );
            }

        // new HTML 5 entities.
        if ( StringTools.haveCommonChar( "5", categories ) )
            {
            singleEntityLine( entity, theCharNumber, description, notes, entityHTML5 );
            }

        // latin, greek handled specially in pairs.
        if ( StringTools.haveCommonChar( "agGns", categories ) && categories.indexOf( '4' ) >= 0 )
            {
            singleEntityLine( entity, theCharNumber, description, notes, entitySpecial );
            }

        if ( categories.indexOf( 's' ) >= 0 && categories.indexOf( '4' ) >= 0 )
            {
            singleEntityLine( entity, theCharNumber, description, notes, entitySymbol );
            }
        if ( categories.indexOf( 'q' ) >= 0 && categories.indexOf( '4' ) >= 0 )
            {
            singleEntityLine( entity, theCharNumber, description, notes, entityQuoter );
            }
        }

    /**
     * Display Java source code for the association of number to entity
     * For InsertEntities  as a case clause.
     *
     * @param entity        entity with &..;
     * @param theCharNumber unicode number of the character
     * @param description   short description of the char
     */
    private static void associateEntityCase( String entity, int theCharNumber, String description )
        {
        StringBuilder sb = new StringBuilder( 400 );

        sb.append( "case " );
        sb.append( StringTools.leftPad( Integer.toString( theCharNumber ),
                4,
                false ) );
        sb.append( " : return " );
        sb.append( StringTools.rightPad( "\"" + entity + "\"",
                LONGEST_HTML4_ENTITIY + 2,
                false ) );
        sb.append( " /* " );
        sb.append( "&#x" );
        sb.append( Integer.toHexString( theCharNumber ) );
        sb.append( "; " );
        sb.append( description );// no not entify
        sb.append( " */;" );
        // Java case   34 : return "&quot;"     /* &#x22; quotation mark */;
        entityCase.println( sb.toString() );
        }

    private static void associateEntityCaseHex( String entity, int theCharNumber )
        {
        StringBuilder sb = new StringBuilder( 400 );

        // Display Java source code Tools.stripQuotes to get rid of accented letters
        sb.append( "case " );
        sb.append( StringTools.rightPad( "/* " + entity + " */",
                14,
                false ) );
        sb.append( " \'\\u" );
        sb.append( StringTools.toLzHexString( theCharNumber, 4 ) );
        sb.append( "\' :" );
        // Java case /* AElig */    '\u00c6' :
        entityCaseHex.println( sb.toString() );
        }

    private static void associateEntityJustKeys( String entity, int theCharNumber, String description )
        {
        // list of entities for array init with C-style comment what they are
        // for StripEntities
        StringBuilder sb = new StringBuilder( 400 );
        final String choppedEntity = entity.substring( 1, entity.length() - 1 );
        sb.append( StringTools.rightPad(
                "\"" + choppedEntity + "\"",
                LONGEST_HTML4_ENTITIY,
                false ) );
        sb.append( " /* " );
        sb.append( StringTools.leftPad( Integer.toString( theCharNumber ),
                4,
                false ) );
        sb.append( " : " );
        sb.append( "&#x" );
        sb.append( Integer.toHexString( theCharNumber ) );
        sb.append( "; " );
        sb.append( description ); // do not entify
        sb.append( " */," );
        // java: "quot"     /*   34 : &#x22; quotation mark */,
        entityJustKeys.println( sb.toString() );
        }

    /**
     * output one row if information about an entity creating a list of character codes with comments.
     *
     * @param entity        char entity with lead & and trail ;
     * @param theCharNumber Unicode char ordinal
     * @param description   description of what symbol looks like.
     */
    private static void associateEntityJustValues( String entity, int theCharNumber, String description )
        {
        StringBuilder sb = new StringBuilder( 400 );

        // list of entity numbers for array init with C-style comment what they are
        // for StripEntities
        sb.append( StringTools.leftPad(
                Integer.toString( theCharNumber ),
                4,
                false ) );
        sb.append( " /* " );
        sb.append( StringTools.rightPad( entity,
                LONGEST_HTML4_ENTITIY, false ) );
        sb.append( " : " );
        sb.append( "&#x" );
        sb.append( Integer.toHexString( theCharNumber ) );
        sb.append( "; " );
        sb.append( description );  // do not entify
        sb.append( " */," );
        // java 34 /* &quot;     : &#x22; quotation mark */
        entityJustValues.println( sb.toString() );
        }

    private static void associateEntityVslickHtmlTagdoc( String entity, int theCharNumber, String description )
        {
        StringBuilder sb = new StringBuilder( 400 );

        // For SlickEdit list of entities.
        sb.append( "const " );
        final String choppedEntity = entity.substring( 1, entity.length() - 1 );
        sb.append( StringTools.rightPad( choppedEntity + ";",
                LONGEST_HTML4_ENTITIY - 1, false ) );
        sb.append( " // " );
        sb.append( StringTools.leftPad( Integer.toString( theCharNumber ),
                4,
                false ) );
        sb.append( " : " );
        sb.append( "&#x" );
        sb.append( Integer.toHexString( theCharNumber ) );
        sb.append( "; " );
        sb.append( description ); // do not entify
        // const quot;     //   34 : &#x22; quotation mark
        entityVslickHtmlTagdoc.println( sb.toString() );
        }

    private static void associateEntityVslickVlx( String entity )
        {
        StringBuilder sb = new StringBuilder( 400 );
        //  8 entries per line
        if ( counter++ % 8 == 0 )
            {
            sb.append( "\ncskeywords=" );
            }

        // cskeywords= &AElig; &Aacute; &Acirc; &Agrave; &Aring; &Atilde; &Auml;
        sb.append( ' ' );
        sb.append( entity );
        entityVslickVlx.print( sb.toString() );
        }

    private static void closeFiles()
        {
        // C L O S E
        entityArrow.close();
        entityCase.close();
        entityCaseHex.close();
        entityCyrillic.close();
        entityGreek.close();
        entityPolish.close();
        entityJustKeys.close();
        entityJustValues.close();
        entityHTML5.close();
        entityLatin.close();
        entitySpecial.close();
        entitySymbol.close();
        entityQuoter.close();
        entityVslickHtmlTagdoc.close();
        entityVslickVlx.close();
        }

    /**
     * Emit row of HTML to describe one entity upper/lower case pain
     *
     * @param p                   Where to imit the html output
     * @param upperCaseCharNumber Unicode ordinal for upper case version
     * @param lowerCaseCharNumber Uincode ordinaly for lower case version
     * @param description         What the entity looks like
     */
    private static void entityPair( PrintWriter p, final int upperCaseCharNumber, final int lowerCaseCharNumber, final String description )
        {
        StringBuilder sb = new StringBuilder( 260 );
        // for HTML cheat sheet, after massaging.
        final String upperEntity = InsertEntities.insertHTMLEntities( String.valueOf( ( char ) upperCaseCharNumber ) );
        final String lowerEntity = InsertEntities.insertHTMLEntities( String.valueOf( ( char ) lowerCaseCharNumber ) );
        sb.append( "<tr>" );
        sb.append( "<td class=\"behold\">" );
        sb.append( upperEntity );
        sb.append( "</td>" );
        sb.append( "<td class=\"behold\">" );
        sb.append( lowerEntity );
        sb.append( "</td>" );
        sb.append( "<td class=\"entity\">" );
        sb.append( "&amp;" );
        sb.append( upperEntity.substring( 1, upperEntity.length() - 1 ) );
        sb.append( ";" );
        sb.append( "</td>" );
        sb.append( "<td class=\"entity\">" );
        sb.append( "&amp;" );
        sb.append( lowerEntity.substring( 1, lowerEntity.length() - 1 ) );
        sb.append( ";" );
        sb.append( "</td>" );
        sb.append( "<td class=\"hexentity\">&amp;#x" );
        sb.append( Integer.toHexString( upperCaseCharNumber ) );
        sb.append( ";</td>" );
        sb.append( "<td class=\"hexentity\">&amp;#x" );
        sb.append( Integer.toHexString( lowerCaseCharNumber ) );
        sb.append( ";</td>" );
        sb.append( "<td class=\"decimalentity\">&amp;#" );
        sb.append( Integer.toString( upperCaseCharNumber ) );
        sb.append( ";</td>" );
        sb.append( "<td class=\"decimalentity\">&amp;#" );
        sb.append( Integer.toString( lowerCaseCharNumber ) );
        sb.append( ";</td>" );
        sb.append( "<td  class=\"charliteral\">" );
        sb.append( "\'\\u" );
        sb.append( StringTools.toLzHexString( upperCaseCharNumber, 4 ) );
        sb.append( '\'' );
        sb.append( "</td>" );
        sb.append( "<td  class=\"charliteral\">" );
        sb.append( "\'\\u" );
        sb.append( StringTools.toLzHexString( upperCaseCharNumber, 4 ) );
        sb.append( '\'' );
        sb.append( "</td>" );
        sb.append( "<td>" );
        sb.append( InsertEntities.insertHTMLEntities( description ) );
        sb.append( "</td></tr>\n" );
        // html rendered, char entity, hex entity, java literal, desc
        // html: char itself, entity , hex entity, decimal enity, Java, notes  in upper/lower pairs.
        p.write( sb.toString() );
        }

    /**
     * read in facts about each entity and let associations method generate various files.
     */
    private static void makeAssociations() throws IOException
        {
        // Track hex -> entity for html4 entities.
        // pruseme sorted with 4 before 5
        final HashMap<String, String> html4descs = new HashMap<String, String>( 3000 );
        final HashMap<String, String> html4notes = new HashMap<String, String>( 3000 );
        final HashSet<String> entities = new HashSet<String>( 3000 );
        // presume current dir is E:\com\mindprod\entities
        CSVReader r = new CSVReader( new BufferedReader( new FileReader( "entityfacts.csv" ) ) );
        try
            {
            while ( true )
                {
                final String categories = r.get();
                String hexString = r.get();
                // chop off lead 0x
                int theCharNumber = Integer.parseInt( hexString.substring( 2 ), 16 );
                final String entity = r.get();
                String description = r.get();
                String notes = r.get();
                r.skipToNextLine();
                if ( categories.indexOf( 'n' ) <= 0 && !( entity.startsWith( "&" ) && entity.endsWith( ";" ) ) )
                    {
                    err.println( "non entity " + entity );
                    }
                if ( !entities.add( entity ) )
                    {
                    // &lang; and &rang; are dedefined in HTML 5 so are dups
                    err.println( "duplicate entity " + entity );
                    }
                if ( categories.indexOf( '5' ) >= 0 )
                    {
                    // see if this duplicates html4
                    final String html4desc = html4descs.get( hexString );
                    // don't overwrite existing description.
                    if ( html4desc != null && description.length() == 0 )
                        {
                        // was a duplicate
                        description = html4desc;
                        }
                    final String html4note = html4notes.get( hexString );
                    // don't overwrite existing note.
                    if ( html4note != null && notes.length() == 0 )
                        {
                        // was a duplicate
                        notes = html4note;
                        }
                    }
                else
                    {
                    // was html4
                    html4descs.put( hexString, description );
                    html4notes.put( hexString, notes );
                    }

                associate( categories, theCharNumber, entity, description, notes );
                }
            }
        catch ( EOFException e )
            {
            r.close();
            }
        }

    private static void makeCyrillicEntityPairs()
        {
        entityPair( entityCyrillic, 0x0410, 0x0430, "A" );
        entityPair( entityCyrillic, 0x0411, 0x0431, "BE" );
        entityPair( entityCyrillic, 0x0412, 0x0432, "VE" );
        entityPair( entityCyrillic, 0x0413, 0x0433, "GHE" );
        entityPair( entityCyrillic, 0x0414, 0x0434, "DE" );
        entityPair( entityCyrillic, 0x0415, 0x0435, "IE" );
        entityPair( entityCyrillic, 0x0416, 0x0436, "ZHE" );
        entityPair( entityCyrillic, 0x0417, 0x0437, "ZE" );
        entityPair( entityCyrillic, 0x0418, 0x0438, "I" );
        entityPair( entityCyrillic, 0x0419, 0x0439, "SHORT I" );
        entityPair( entityCyrillic, 0x041A, 0x043A, "KA" );
        entityPair( entityCyrillic, 0x041B, 0x043B, "EL" );
        entityPair( entityCyrillic, 0x041C, 0x043C, "EM" );
        entityPair( entityCyrillic, 0x041D, 0x043D, "EN" );
        entityPair( entityCyrillic, 0x041E, 0x043E, "O" );
        entityPair( entityCyrillic, 0x041F, 0x043F, "PE" );
        entityPair( entityCyrillic, 0x0420, 0x0440, "ER" );
        entityPair( entityCyrillic, 0x0421, 0x0441, "ES" );
        entityPair( entityCyrillic, 0x0422, 0x0442, "TE" );
        entityPair( entityCyrillic, 0x0423, 0x0443, "U" );
        entityPair( entityCyrillic, 0x0424, 0x0444, "EF" );
        entityPair( entityCyrillic, 0x0425, 0x0445, "HA" );
        entityPair( entityCyrillic, 0x0426, 0x0446, "TSE" );
        entityPair( entityCyrillic, 0x0427, 0x0447, "CHE" );
        entityPair( entityCyrillic, 0x0428, 0x0448, "SHA" );
        entityPair( entityCyrillic, 0x0429, 0x0449, "SHCHA" );
        entityPair( entityCyrillic, 0x042A, 0x044A, "HARD SIGN" );
        entityPair( entityCyrillic, 0x042B, 0x044B, "YERU" );
        entityPair( entityCyrillic, 0x042C, 0x044C, "SOFT SIGN" );
        entityPair( entityCyrillic, 0x042D, 0x044D, "E" );
        entityPair( entityCyrillic, 0x042E, 0x044E, "YU" );
        entityPair( entityCyrillic, 0x042F, 0x044F, "YA" );
        }

    private static void makeGreekEntityPairs()
        {
        entityPair( entityGreek, 0x0391/*  913 */, 0x03b1/*  945 */, "alpha" );
        entityPair( entityGreek, 0x0392/*  914 */, 0x03b2/*  946 */, "beta" );
        entityPair( entityGreek, 0x03a7/*  935 */, 0x03c7/*  967 */, "chi" );
        entityPair( entityGreek, 0x0394/*  916 */, 0x03b4/*  948 */, "delta" );
        entityPair( entityGreek, 0x0395/*  917 */, 0x03b5/*  949 */, "epsilon" );
        entityPair( entityGreek, 0x0397/*  919 */, 0x03b7/*  951 */, "eta" );
        entityPair( entityGreek, 0x0393/*  915 */, 0x03b3/*  947 */, "gamma" );
        entityPair( entityGreek, 0x0399/*  921 */, 0x03b9/*  953 */, "iota" );
        entityPair( entityGreek, 0x039a/*  922 */, 0x03ba/*  954 */, "kappa" );
        entityPair( entityGreek, 0x039b/*  923 */, 0x03bb/*  955 */, "lambda" );
        entityPair( entityGreek, 0x039c/*  924 */, 0x03bc/*  956 */, "mu like &amp;micro; &micro;" );
        entityPair( entityGreek, 0x039d/*  925 */, 0x03bd/*  957 */, "nu" );
        entityPair( entityGreek, 0x03a9/*  937 */, 0x03c9/*  969 */, "omega" );
        entityPair( entityGreek, 0x039f/*  927 */, 0x03bf/*  959 */, "omicron" );
        entityPair( entityGreek, 0x03a6/*  934 */, 0x03c6/*  966 */, "phi" );
        entityPair( entityGreek, 0x03a0/*  928 */, 0x03c0/*  960 */, "pi" );
        entityPair( entityGreek, 0x03d6/*  982 */, 0x03d6/*  982 */, "Greek pi symbol, not ordinary pi, looks like omega bar," ); // manual fix
        entityPair( entityGreek, 0x03a8/*  936 */, 0x03c8/*  968 */, "psi" );
        entityPair( entityGreek, 0x03a1/*  929 */, 0x03c1/*  961 */, "rho" );
        entityPair( entityGreek, 0x03a3/*  931 */, 0x03c3/*  963 */, "sigma" );
        entityPair( entityGreek, 0x03c2/*  962 */, 0x03c2/*  962 */, "final sigma" );  // manual fix
        entityPair( entityGreek, 0x03a4/*  932 */, 0x03c4/*  964 */, "tau" );
        entityPair( entityGreek, 0x0398/*  920 */, 0x03b8/*  952 */, "theta" );
        entityPair( entityGreek, 0x03d1/*  977 */, 0x03d2/*  978 */, "Greek upsilon with hook symbol" );
        entityPair( entityGreek, 0x03a5/*  933 */, 0x03c5/*  965 */, "upsilon" );
        entityPair( entityGreek, 0x039e/*  926 */, 0x03be/*  958 */, "xi" );
        entityPair( entityGreek, 0x0396/*  918 */, 0x03b6/*  950 */, "zeta" );
        }

    private static void makeLatinEntityPairs()
        {
        entityPair( entityLatin, 0x00c1, 0x00e1, "a acute" );
        entityPair( entityLatin, 0x00c2, 0x00e2, "a circumflex" );
        entityPair( entityLatin, 0x00c6, 0x00e6, "ligature ae" );
        entityPair( entityLatin, 0x00c0, 0x00e0, "a grave" );
        entityPair( entityLatin, 0x00c5, 0x00e5, "a ring above" );
        entityPair( entityLatin, 0x00c3, 0x00e3, "a tilde" );
        entityPair( entityLatin, 0x00c4, 0x00e4, "a diaeresis" );
        entityPair( entityLatin, 0x00c7, 0x00e7, "c cedilla" );
        entityPair( entityLatin, 0x00c9, 0x00e9, "e acute" );
        entityPair( entityLatin, 0x00ca, 0x00ea, "e circumflex" );
        entityPair( entityLatin, 0x00c8, 0x00e8, "e grave" );
        entityPair( entityLatin, 0x00d0, 0x00f0, "eth" );
        entityPair( entityLatin, 0x00cb, 0x00eb, "e diaeresis" );
        entityPair( entityLatin, 0x0192, 0x0192, "f hook" ); // needs manual fixup. LC only.
        entityPair( entityLatin, 0x00cd, 0x00ed, "i acute" );
        entityPair( entityLatin, 0x00ce, 0x00ee, "i circumflex" );
        entityPair( entityLatin, 0x00cc, 0x00ec, "i grave" );
        entityPair( entityLatin, 0x00cf, 0x00ef, "i diaeresis" );
        entityPair( entityLatin, 0x00d1, 0x00f1, "n tilde" );
        entityPair( entityLatin, 0x00d3, 0x00f3, "o acute" );
        entityPair( entityLatin, 0x00d4, 0x00f4, "o circumflex" );
        entityPair( entityLatin, 0x0152, 0x0153, "ligature oe" );
        entityPair( entityLatin, 0x00d2, 0x00f2, "o grave" );
        entityPair( entityLatin, 0x00d8, 0x00f8, "o stroke" );
        entityPair( entityLatin, 0x00d5, 0x00f5, "o tilde" );
        entityPair( entityLatin, 0x00d6, 0x00f6, "o diaeresis" );
        entityPair( entityLatin, 0x0160, 0x0161, "s caron" );
        entityPair( entityLatin, 0x00df, 0x00df, "sharp s, German SS" ); // needs manual fixup UC only
        entityPair( entityLatin, 0x00de, 0x00fe, "thorn" );
        entityPair( entityLatin, 0x00da, 0x00fa, "u acute" );
        entityPair( entityLatin, 0x00db, 0x00fb, "u circumflex" );
        entityPair( entityLatin, 0x00d9, 0x00f9, "u grave" );
        entityPair( entityLatin, 0x00dc, 0x00fc, "u diaeresis" );
        entityPair( entityLatin, 0x00dd, 0x00fd, "y acute" );
        entityPair( entityLatin, 0x00ff, 0x0178, "Y diaeresis" );
        }

    private static void makePolishEntityPairs()
        {
        entityPair( entityPolish, 0x0104, 0x0104, "A ogonek" );
        entityPair( entityPolish, 0x0118, 0x0118, "E ogonek" );
        entityPair( entityPolish, 0x00d3, 0x00d3, "O acute" );
        entityPair( entityPolish, 0x0106, 0x0106, "C acute" );
        entityPair( entityPolish, 0x0141, 0x0141, "L stroke" );
        entityPair( entityPolish, 0x0143, 0x0143, "N acute" );
        entityPair( entityPolish, 0x015a, 0x015a, "S acute" );
        entityPair( entityPolish, 0x0179, 0x0179, "Z acute" );
        entityPair( entityPolish, 0x017b, 0x017b, "Z dot above" );
        }

    // Polish, Russian, obsure arrows were done manually.

    private static void openFiles() throws IOException
        {
        // O P E N
        final boolean autoflush = true;
        entityArrow = new PrintWriter( new BufferedWriter( new FileWriter( "entityarrow.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entityCase = new PrintWriter( new BufferedWriter( new FileWriter( "entitycase.javafrag" ), 4096/* buffsize */ ), autoflush );
        entityCaseHex = new PrintWriter( new BufferedWriter( new FileWriter( "entitycasehex.javafrag" ), 4096/* buffsize */ ), autoflush );
        entityCyrillic = new PrintWriter( new BufferedWriter( new FileWriter( "entitycyrillic.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entityGreek = new PrintWriter( new BufferedWriter( new FileWriter( "entitygreek.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entityPolish = new PrintWriter( new BufferedWriter( new FileWriter( "entitypolish.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entityJustKeys = new PrintWriter( new BufferedWriter( new FileWriter( "entityjustkeys.javafrag" ), 4096/* buffsize */ ), autoflush );
        entityJustValues = new PrintWriter( new BufferedWriter( new FileWriter( "entityjustvalues.javafrag" ), 4096/* buffsize */ ), autoflush );
        entityHTML5 = new PrintWriter( new BufferedWriter( new FileWriter( "entityhtml5.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entityLatin = new PrintWriter( new BufferedWriter( new FileWriter( "entitylatin.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entitySpecial = new PrintWriter( new BufferedWriter( new FileWriter( "entityspecial.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entitySymbol = new PrintWriter( new BufferedWriter( new FileWriter( "entitysymbol.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entityQuoter = new PrintWriter( new BufferedWriter( new FileWriter( "entityquoter.htmlfrag" ), 4096/* buffsize */ ), autoflush );
        entityVslickHtmlTagdoc = new PrintWriter( new FileWriter( "entityvslickhtml.tagdoc" ), autoflush );
        entityVslickVlx = new PrintWriter( new FileWriter( "entityvslick.vlx" ), autoflush );
        }

    /**
     * Generete HTML for a table row to display one entity, behold twice, as char entity and hex entity.
     *
     * @param entity        String for entity with lead & and trail ;
     * @param theCharNumber unicode character ordinal
     * @param description   description of what char looks like
     * @param notes         notes about char
     * @param p             where to emit HTML
     */

    private static void singleEntityLine( final String entity, final int theCharNumber, final String description, final String notes, PrintWriter p )
        {
        StringBuilder sb = new StringBuilder( 400 );

        // for HTML cheat sheet, after massaging.
        sb.append( "<tr>" );
        sb.append( "<td class=\"behold\">" );
        sb.append( entity );
        sb.append( "</td>" );
        sb.append( "<td class=\"entity\">" );
        if ( entity.startsWith( "&" ) )
            {
            final String choppedEntity = entity.substring( 1, entity.length() - 1 );
            sb.append( "&amp;" );
            sb.append( choppedEntity );
            sb.append( ";" );
            }
        else
            {
            sb.append( entity );// handle n non-entity
            }
        sb.append( "</td>" );
        sb.append( "<td class=\"behold\">" );
        sb.append( "&#x" );
        sb.append( Integer.toHexString( theCharNumber ) );
        sb.append( ";</td>" );
        sb.append( "<td class=\"hexentity\">&amp;#x" );
        sb.append( Integer.toHexString( theCharNumber ) );
        sb.append( ";</td>" );
        sb.append( "<td class=\"decimalentity\">&amp;#" );
        sb.append( Integer.toString( theCharNumber ) );
        sb.append( ";</td>" );
        sb.append( "<td class=\"charliteral\">" );
        if ( theCharNumber <= 0xffff )
            {
            // simple 1-char encoding
            sb.append( "\'\\u" );
            sb.append( StringTools.toLzHexString( theCharNumber, 4 ) );
            sb.append( '\'' );
            }
        else
            {
            // we need to encode it as a surrogate pair.
            final int extract = theCharNumber - 0x10000;
            final int high = ( extract >>> 10 ) + 0xd800;
            final int low = ( extract & 0x3ff ) + 0xdc00;
            sb.append( "&quot;\\u" );
            sb.append( StringTools.toLzHexString( high, 4 ) );
            sb.append( "\\u" );
            sb.append( StringTools.toLzHexString( low, 4 ) );
            sb.append( "&quot;" );
            }
        sb.append( "</td>" );
        sb.append( "<td>" );
        sb.append( InsertEntities.insertHTMLEntities( description ) );
        sb.append( "</td>" );
        sb.append( "<td>" );
        sb.append( InsertEntities.insertHTMLEntities( notes ) );
        sb.append( "</td>" );
        sb.append( "</tr>\n" );
        p.write( sb.toString() );
        }

    // --------------------------- main() method ---------------------------

    /**
     * Run once to generate [various text files that are inserted to handle entity coding.
     * generates the following files in current directory.
     * <p/>
     * entitycase.javafrag : case statements. Insert in InsertEntities.java, then recompile.
     * entitycasehex.javafrag : alternate case statements to Insert in InsertEntities.java, then recompile.
     * entityfacts.javafrag : insert into Entities.java and recompile. Tidies code.
     * entityjustkeys.javafrag  :  legal entity keys. Insert in StripEntities.java, then recompile.
     * entityjustvalues.javafrag  : legal entity values. Insert in StripEntities.java, then recompile.
     * entityspecial.htmlfrag : copy to E:\mindprod\jgloss\include\entityspecial.html (note extension change)
     * entityvslickhtml.tagdoc : insert into F:\program files\vslick\builtins\html.tagdoc, just ones not there yet.
     * entityvslick.vlx  : Insert into F:\program files\vslick\vslick.vlx,
     * patch keywords= &amp; &lt; &gt; &quot;  instead of cskeywords.
     * <p/>
     *
     * @param args not used.
     *
     * @throws java.io.IOException to get maximal info about the problem.
     */
    public static void main( String[] args ) throws IOException
        {
        openFiles();
        // W R I T E , associate does the actual writing
        makeAssociations();
        makeLatinEntityPairs();
        makeCyrillicEntityPairs();
        makeGreekEntityPairs();
        makePolishEntityPairs();
        closeFiles();
        }// end main
    }

// end Entities
