// $Id: gendaml.java,v 1.53 2003/03/08 23:13:19 mdean Exp $


/**
 * generate a DAML+OIL version of the 2001 CIA World Factbook 
 * (http://www.cia.gov/cia/publications/factbook/)
 */
class gendaml
{
    static String fipsBase = "http://www.daml.org/2001/09/countries/fips#";
    static String factbookDirectory = "http://www.daml.org/2001/12/factbook/";
    static String factbookBase = "http://www.cia.gov/cia/publications/factbook/";
    static String isoBase = "http://www.daml.org/2001/09/countries/iso#";

    static java.text.DecimalFormat df = new java.text.DecimalFormat("#,##0");

    static final char nbsp = 160;

    /**
     * trim whitespace, including nbsp's
     */
    static String trim(String string)
    {
	return string.replace(nbsp, ' ').trim();
    }

    /**
     * trim specific suffix
     */
    static String ignoreSuffix(String string,
			       String suffix)
    {
	if (string.endsWith(suffix))
	    return string.substring(0, string.length() - suffix.length());
	else return string;
    }

    /**
     * trim specific suffix
     */
    static String ignorePrefix(String string,
			       String prefix)
    {
	if (string.startsWith(prefix))
	    return string.substring(prefix.length());
	else return string;
    }

    static class Handler
    {
	void process(Country country,
		     String value)
	    throws Exception
	{
	}

	void unexpectedValue(Country country,
			     com.hp.hpl.mesa.rdf.jena.model.Property property,
			     String value)
	{
	    System.err.println("unexpected value " + value + " for " + property + " in " + country);
	}
    }

    static class StringHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;

	StringHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    this.property = property;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    country.model.add(country.resource,
			      property,
			      value);
	}
    }

    static class NumberHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;

	NumberHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    this.property = property;
	}

	static class ParseException
	    extends Exception
	{
	}

	Object parse(String value)
	    throws Exception
	{
	    boolean real = false;

	    if (value.equals("NA"))
		return null;
	    if (value.equals("NEGL"))
		return null;

	    // get rid of any numeric formatting
	    for (int i = 0; i < value.length(); i++)
		{
		    if ((value.charAt(i) == ',')
			&& ((i+1) < value.length())
			&& (value.charAt(i+1) >= '0')
			&& (value.charAt(i+1) <= '9'))
			value = value.substring(0, i) + value.substring(i + 1);
		    if (value.charAt(i) == '.')
			real = true;
		}

	    // convert quantities
	    int index;
	    long multiple = 1;
	    if ((index = value.indexOf("thousand")) != (-1))
		{
		    multiple *= 1000;
		    value = value.substring(0, index - 1) + value.substring(index + 8);
		}
	    if ((index = value.indexOf("million")) != (-1))
		{
		    multiple *= 1000000;
		    value = value.substring(0, index - 1) + value.substring(index + 7);
		}
	    if ((index = value.indexOf("billion")) != (-1))
		{
		    multiple *= 1000000000;
		    value = value.substring(0, index - 1) + value.substring(index + 7);
		}
	    if ((index = value.indexOf("trillion")) != (-1))
		{
		    multiple *= 1000000000000L;
		    value = value.substring(0, index - 1) + value.substring(index + 8);
		}

	    // .parse may throw exception
	    if (real)
		{
		    double retval = Double.parseDouble(value);
		    retval *= multiple;

		    if ((retval >= 1e7) // avoid scientific notation - see Double.toString javadoc
			|| (multiple > 1)) // avoid extra .0
			return new Long((long) retval);
		    else
			return new Double(retval);
		}
	    else
		{
		    long retval = Long.parseLong(value);
		    retval *= multiple;
		    return new Long(retval);
		}
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    if (value.startsWith("not specified"))
		return;
	    if (value.startsWith("NA"))
		return;
	    ReifiedComments rc = new ReifiedComments();
	    value = rc.parse(value);
	    Object objectValue;
	    try {
		objectValue = parse(value);
	    } catch (Exception e) {
		unexpectedValue(country, property, value);
		return;
	    }
	    if (objectValue == null)
		return;
	    com.hp.hpl.mesa.rdf.jena.model.Statement statement = country.model.createStatement(country.resource,
											       property,
											       objectValue);
	    country.model.add(statement);
	    rc.add(statement);
	}
    }

    /**
     * numeric value with specified units
     */
    static class UnitsHandler
	extends NumberHandler
    {
	String units;

	UnitsHandler(com.hp.hpl.mesa.rdf.jena.model.Property property,
		     String units)
	{
	    super(property);
	    this.units = units;
	}

	Object parse(String value)
	    throws Exception
	{
	    if (! value.endsWith(units))
		{
		    throw new ParseException();
		}
	    return super.parse(value.substring(0, value.length() - units.length() - 1));
	}
    }

    static class DollarHandler
	extends NumberHandler
    {
	DollarHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    super(property);
	}

	Object parse(String value)
	    throws Exception
	{
	    if (value.charAt(0) != '$')
		{
		    throw new ParseException();
		}
	    return super.parse(value.substring(1));
	}
    }

    /**
     * used for GDP
     */
    static class PPPHandler
	extends DollarHandler
    {
	PPPHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    super(property);
	}

	Object parse(String value)
	    throws Exception
	{
	    String ppp = "purchasing power parity - ";
	    if (! value.startsWith(ppp))
		{
		    throw new ParseException();
		}
	    return super.parse(value.substring(ppp.length()));
	}
    }

    /**
     * numeric value as percentage
     */
    static class PercentHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;

	PercentHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    this.property = property;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    ReifiedComments rc = new ReifiedComments();
	    value = rc.parse(value);
	    if (! value.endsWith("%"))
		{
		    unexpectedValue(country, property, value);
		    return;
		}
	    if (value.equals("NA%"))
		return;
	    com.hp.hpl.mesa.rdf.jena.model.Statement statement = country.model.createStatement(country.resource,
											       property,
											       value.substring(0, value.length() - 1));
	    country.model.add(statement);
	    rc.add(statement);
	}
    }

    /**
     * list of references to external objects
     */
    static class ListHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;
	List list;

	ListHandler(com.hp.hpl.mesa.rdf.jena.model.Property property,
		    List list)
	{
	    this.property = property;
	    this.list = list;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    Tokenizer tokenizer = new Tokenizer(value, ", ");
	    while (tokenizer.hasMoreTokens())
		{
		    String token = tokenizer.nextToken();
		    // XXX - reified comments?
		    country.model.add(country.resource,
				      property,
				      list.get(token));
		}
	}
    }

    /**
     * list of strings
     */
    static class StringListHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;

	StringListHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    this.property = property;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    Tokenizer tokenizer = new Tokenizer(value, "; ");
	    while (tokenizer.hasMoreTokens())
		{
		    String token = tokenizer.nextToken();
		    // XXX - reified comments?
		    country.model.add(country.resource,
				      property,
				      token);
		}
	}
    }

    /**
     * list of name/optional percentage pairs
     */
    static class ListPercentHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;
	List list;
	com.hp.hpl.mesa.rdf.jena.model.Resource percentClass;

	ListPercentHandler(com.hp.hpl.mesa.rdf.jena.model.Property property,
			   List list,
			   com.hp.hpl.mesa.rdf.jena.model.Resource percentClass)
	{
	    this.property = property;
	    this.list = list;
	    this.percentClass = percentClass;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    Tokenizer tokenizer = new Tokenizer(value, ", ");
	    while (tokenizer.hasMoreTokens())
		{
		    String token = tokenizer.nextToken();
		    ReifiedComments rc = new ReifiedComments();
		    String comment = tokenizer.getComment();
		    if (comment != null)
			rc.processComment(comment);
		    String name;
		    String percent = null;
		    if (token.endsWith("%"))
			{
			    int space = token.lastIndexOf(' ');
			    name = token.substring(0, space);
			    percent = token.substring(space + 1, token.length() - 1);
			}
		    else
			name = token;
		    name = ignorePrefix(name, "and ");
		    
		    com.hp.hpl.mesa.rdf.jena.model.Resource percentResource = country.model.createResource();
		    country.model.add(percentResource,
				      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
				      percentClass);
		    country.model.add(percentResource,
				      property,
				      list.get(name));
		    if (percent != null)
			country.model.add(percentResource,
					  factbook_ont.percent,
					  percent);
		    com.hp.hpl.mesa.rdf.jena.model.Statement statement = country.model.createStatement(country.resource,
												       property,
												       percentResource);
		    country.model.add(statement);
		    rc.add(statement);
		}
	}
    }

    /**
     * list of name/count pairs
     */
    static class ListCountHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;
	List list;
	com.hp.hpl.mesa.rdf.jena.model.Resource percentClass;

	ListCountHandler(com.hp.hpl.mesa.rdf.jena.model.Property property,
			 List list,
			 com.hp.hpl.mesa.rdf.jena.model.Resource percentClass)
	{
	    this.property = property;
	    this.list = list;
	    this.percentClass = percentClass;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    Tokenizer tokenizer = new Tokenizer(value, ", ");
	    while (tokenizer.hasMoreTokens())
		{
		    String token = tokenizer.nextToken();
		    ReifiedComments rc = new ReifiedComments();
		    String comment = tokenizer.getComment();
		    if (comment != null)
			rc.processComment(comment);
		    String name;
		    String percent = null;
		    if (token.endsWith("%"))
			{
			    int space = token.lastIndexOf(' ');
			    name = token.substring(0, space);
			    percent = token.substring(space + 1, token.length() - 1);
			}
		    else
			name = token;
		    name = ignorePrefix(name, "and ");
		    
		    com.hp.hpl.mesa.rdf.jena.model.Resource percentResource = country.model.createResource();
		    country.model.add(percentResource,
				      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
				      percentClass);
		    country.model.add(percentResource,
				      property,
				      list.get(name));
		    if (percent != null)
			country.model.add(percentResource,
					  factbook_ont.percent,
					  percent);
		    com.hp.hpl.mesa.rdf.jena.model.Statement statement = country.model.createStatement(country.resource,
												       property,
												       percentResource);
		    country.model.add(statement);
		    rc.add(statement);
		}
	}
    }

    /**
     * list of country/optional percentage pairs;
     * this is similar to the "border countries" handler
     */
    static class CountryPercentHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;

	CountryPercentHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    this.property = property;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    java.util.Vector vector = null;
	    if (property.equals(factbook_ont.exportPartner))
		vector = country.exports;
	    else if (property.equals(factbook_ont.importPartner))
		vector = country.imports;
	    else
		{
		    System.err.println("unexpected property " + property + " in CountryPercent");
		    System.exit(1);
		}

 	    Tokenizer tokenizer = new Tokenizer(value, ", ");
	    while (tokenizer.hasMoreTokens())
		{
		    String pair = tokenizer.nextToken().trim();
		    String otherCountry;
		    String percent;
		    if (pair.endsWith("%"))
			{
			    int split = pair.lastIndexOf(' ');
			    otherCountry = pair.substring(0, split);
			    percent = pair.substring(split + 1, pair.length() - 1);
			}
		    else
			{
			    otherCountry = pair;
			    percent = null;
			}
		    vector.add(new CountryNumber(otherCountry,
						 percent,
						 tokenizer.getComment()));
		}
	}
    }

    /**
     * lists separate using both commas and semicolons
     */
    static class SublistsHandler
	extends ListHandler
    {
	SublistsHandler(com.hp.hpl.mesa.rdf.jena.model.Property property,
			List list)
	{
	    super(property, list);
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    super.process(country, value.replace(';', ','));
	}
    }

    /**
     * place/elevation pair
     */
    static class ElevationExtremeHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;

	ElevationExtremeHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    this.property = property;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    if (! value.endsWith(" m"))
		{
		    unexpectedValue(country, property, value);
		    return;
		}
	    int space2 = value.lastIndexOf(' ');
	    int space1 = value.lastIndexOf(' ', space2 - 1);
	    String name = value.substring(0, space1);
	    String elevation = value.substring(space1 + 1, space2);
	    com.hp.hpl.mesa.rdf.jena.model.Resource extremeResource = country.model.createResource();
	    country.model.add(extremeResource,
			      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
			      factbook_ont.ElevationExtreme);
	    country.model.add(extremeResource,
			      factbook_ont.name,
			      name);
	    country.model.add(extremeResource,
			      factbook_ont.elevation,
			      df.parse(elevation));
	    country.model.add(country.resource,
			      property,
			      extremeResource);
	}
    }


    /**
     * count
     */
    static class AirportBreakdownHandler
	extends Handler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;
	boolean paved;
	String minRunway;
	String maxRunway;

	AirportBreakdownHandler(com.hp.hpl.mesa.rdf.jena.model.Property property,
				boolean paved,
				String minRunway,
				String maxRunway)
	{
	    this.property = property;
	    this.paved = paved;
	    this.minRunway = minRunway;
	    this.maxRunway = maxRunway;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    com.hp.hpl.mesa.rdf.jena.model.Resource resource = country.model.createResource();
	    country.model.add(resource,
			      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
			      factbook_ont.AirportBreakdown);
	    country.model.add(resource,
			      factbook_ont.paved,
			      paved);
	    if (minRunway != null)
		country.model.add(resource,
				  factbook_ont.minRunway,
				  minRunway);
	    if (maxRunway != null)
		country.model.add(resource,
				  factbook_ont.maxRunway,
				  maxRunway);
	    country.model.add(resource,
			      factbook_ont.count,
			      value);
	    country.model.add(country.resource,
			      property,
			      resource);
	}
    }

    /**
     * ratio
     */
    static class SexRatioHandler
	extends UnitsHandler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;
	String minAge;
	String maxAge;

	SexRatioHandler(com.hp.hpl.mesa.rdf.jena.model.Property property,
			String minAge,
			String maxAge)
	{
	    super(property, "male(s)/female");
	    this.property = property;
	    this.minAge = minAge;
	    this.maxAge = maxAge;
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    ReifiedComments rc = new ReifiedComments();
	    value = rc.parse(value);
	    Object ratio = parse(value);

	    com.hp.hpl.mesa.rdf.jena.model.Resource resource = country.model.createResource();
	    country.model.add(resource,
			      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
			      factbook_ont.SexRatioBreakdown);
	    if (minAge != null)
		country.model.add(resource,
				  factbook_ont.minAge,
				  minAge);
	    if (maxAge != null)
		country.model.add(resource,
				  factbook_ont.maxAge,
				  maxAge);
	    country.model.add(resource,
			      factbook_ont.ratio,
			      ratio);
	    com.hp.hpl.mesa.rdf.jena.model.Statement statement = 
	    country.model.createStatement(country.resource,
					  property,
					  resource);
	    country.model.add(statement);
	    rc.add(statement);
	}
    }

    /**
     * Internet country code uses ISO 3166
     */
    static class InternetCountryCodeHandler
	extends StringHandler
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;

	InternetCountryCodeHandler(com.hp.hpl.mesa.rdf.jena.model.Property property)
	{
	    super(property);
	}

	void process(Country country,
		     String value)
	    throws Exception
	{
	    super.process(country, value);
	    country.model.add(country.resource,
			      DAML.sameIndividualAs,
			      country.model.createResource(isoBase + value.trim().substring(1).toUpperCase()));
	}
    }

    /**
     * partial statements associated with a future subject
     */
    static class PredicateObject
    {
	com.hp.hpl.mesa.rdf.jena.model.Property property;
	com.hp.hpl.mesa.rdf.jena.model.RDFNode object;

	PredicateObject(com.hp.hpl.mesa.rdf.jena.model.Property property,
			com.hp.hpl.mesa.rdf.jena.model.RDFNode object)
	{
	    this.property = property;
	    this.object = object;
	}

	PredicateObject(com.hp.hpl.mesa.rdf.jena.model.Property property,
			String literal)
	{
	    this.property = property;
	    this.object = new com.hp.hpl.mesa.rdf.jena.common.LiteralImpl(literal);
	}

	void add(com.hp.hpl.mesa.rdf.jena.model.Model model,
		 com.hp.hpl.mesa.rdf.jena.model.Resource subject)
	    throws com.hp.hpl.mesa.rdf.jena.model.RDFException
	{
	    model.add(subject,
		      property,
		      object);
	}
    }

    /**
     * associate parenthesized comments with 1 or more statements
     */
    static class ReifiedComments
    {
	java.util.Vector comments = new java.util.Vector();
	
	ReifiedComments()
	{
	}

	/**
	 * parse a trailing comment
	 */
	String parse(String value)
	{
	    if (! value.endsWith(")"))
		return value;
	    int open = value.lastIndexOf('(');
	    String comment = trim(value.substring(open + 1, value.length() - 1));
	    processComment(comment);

	    return value.substring(0, open - 1).trim();
	}

	void processComment(String comment)
	{
	    // landlocked
	    String landlocked = "landlocked";
	    if (comment.equals(landlocked))
		{
		    comments.add(new PredicateObject(factbook_ont.note,
						     comment));
		    comment = ignoreSuffix(comment, landlocked).trim();
		}

	    // estimate
	    String est = "est.";
	    if (comment.endsWith(est))
		{
		    comments.add(new PredicateObject(com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
						     factbook_ont.Estimate));
		    comment = ignoreSuffix(comment, est).trim();
		}

	    // year
	    if ((comment.length() == 4)
		)
		{
		    try {
			int year = Integer.parseInt(comment);
			comments.add(new PredicateObject(factbook_ont.year,
							 comment));
			comment = "";
		    } catch (Exception e) {
			// nothing
		    }
		}

	    // anything left?
	    if (! comment.equals(""))
		{
		    comments.add(new PredicateObject(factbook_ont.note,
						     comment));
		}
	}

	/**
	 * add any comments to the specified statement
	 */
	void add(com.hp.hpl.mesa.rdf.jena.model.Statement statement)
	    throws com.hp.hpl.mesa.rdf.jena.model.RDFException
	{
	    com.hp.hpl.mesa.rdf.jena.model.Model model = statement.getModel();
	    java.util.Iterator iterator = comments.iterator();
	    while (iterator.hasNext())
		{
		    PredicateObject po = (PredicateObject) iterator.next();
		    model.add(statement,
			      po.property,
			      po.object);
		}
	}
    }

    /**
     * similar to java.util.StringTokenizer, but look for a multi-character separator (to avoid commas in numbers) and handle parenthesized comments
     */
    static class Tokenizer
    {
	String string;
	String separator;
	int start = 0;

	Tokenizer(String string,
		  String separator)
	{
	    this.string = string;
	    this.separator = separator;
	}

	String nextComment = null;
	String comment = null;

	/**
	 * find the next separator
	 */
	int nextInternal()
	{
	    boolean inComment = false;
	    if (start >= string.length())
		return -1;

	    for (int i = start; i < string.length(); i++)
		{
		    char c = string.charAt(i);
		    if (inComment)
			{
			    if (c == ')')
				inComment = false;
			    continue;
			}
		    if (c == '(')
			{
			    inComment = true;
			    continue;
			}
		    if (string.startsWith(separator, i))
			return i;
		}

	    // end of string
	    return string.length();
	}

	String nextToken()
	{
	    int next = nextInternal();
	    String retval = string.substring(start, next).trim();
	    int open = retval.indexOf('(');
	    if (open == (-1))
		comment = null;
	    else
		{
		    int close = retval.indexOf(')');
		    comment = retval.substring(open + 1, close);
		    String before = retval.substring(0, open);
		    String after = retval.substring(close + 1);
		    if ((before.length() > 0)
			&& (after.length() > 0))
			retval = before.trim() + " " + after.trim();
		    else 
			retval = (before + after).trim();
		}
	    
	    start = next + separator.length();
	    return retval;
	}

	boolean hasMoreTokens()
	{
	    return nextInternal() != (-1);
	}

	/**
	 * return any parenthesized comment associated with the token most recently returned by nextToken
	 */
	String getComment()
	{
	    return comment;
	}
    }

    static class CountryNumber
    {
	/**
	 * country name resolved in second pass since page might not have been loaded yet
	 */
	String country;
	Object number;
	String comment;

	CountryNumber(String country,
		      Object number,
		      String comment)
	{
	    this.country = country;
	    this.number = number;
	    this.comment = comment;
	}
    }

    static class Section
    {
	String section;

	Section(String section)
	{
	    this.section = section;
	}

	Bold add(String bold)
	{
	    return new Bold(bold);
	}
    }

    /**
     * entities mentioned by many countries 
     */
    static class List
	implements Comparable
    {
	String name;
	com.hp.hpl.mesa.rdf.jena.model.Resource type;

	static java.util.TreeSet lists = new java.util.TreeSet();

	List(String name,
	     com.hp.hpl.mesa.rdf.jena.model.Resource type)
	{
	    this.name = name;
	    this.type = type;
	    lists.add(this);
	}

	public int compareTo(Object object)
	{
	    List list = (List) object;
	    return name.compareTo(list.name);
	}

	java.util.Hashtable table = new java.util.Hashtable();

	com.hp.hpl.mesa.rdf.jena.model.Resource get(String name)
	{
	    com.hp.hpl.mesa.rdf.jena.model.Resource retval = (com.hp.hpl.mesa.rdf.jena.model.Resource) table.get(name);
	    if (retval == null)
		{
		    retval = new com.hp.hpl.mesa.rdf.jena.common.ResourceImpl(factbookDirectory + this.name + "#" + name.replace(' ', '_').replace('\'', '_').replace('/', '_'));
		    table.put(name, retval);
		}
	    return retval;
	}

	/**
	 * create the DAML file containing all of these resources
	 */
	void generate()
	    throws Exception
	{
	    com.hp.hpl.mesa.rdf.jena.model.Model model = new com.hp.hpl.mesa.rdf.jena.mem.ModelMem();
	    java.util.Iterator iterator = table.keySet().iterator();
	    while (iterator.hasNext())
		{
		    String key = (String) iterator.next();
		    com.hp.hpl.mesa.rdf.jena.model.Resource resource = (com.hp.hpl.mesa.rdf.jena.model.Resource) table.get(key);
		    model.add(resource,
			      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
			      type);
		    model.add(resource,
			      factbook_ont.name, // XXX - rdfs:label?
			      key);
		}

	    java.io.PrintWriter stream = new java.io.PrintWriter(new java.io.FileOutputStream(name + ".daml"));
	    stream.println("<?xml version='1.0' encoding='ISO-8859-1'?>");
	    model.write(stream);
	}
    }

    static List commodities = new List("commodities", factbook_ont.Commodity);
    static List naturalResources = new List("naturalResources", factbook_ont.NaturalResource);
    static List industries = new List("industries", factbook_ont.Industry);
    static List environmentalAgreements = new List("environmentalAgreements", factbook_ont.EnvironmentalAgreement);
    static List internationalOrganizations = new List("internationalOrganizations", factbook_ont.InternationalOrganization);
    static List governmentTypes = new List("governmentTypes", factbook_ont.GovernmentType);
    static List ethnicGroups = new List("ethnicGroups", factbook_ont.EthnicGroup);
    static List religions = new List("religions", factbook_ont.Religion);
    static List languages = new List("languages", factbook_ont.Language);
    static List occupations = new List("occupations", factbook_ont.Occupation);
    static List agricultureProducts = new List("agricultureProducts", factbook_ont.AgricultureProduct);
    static List radioBand = new List("radioBands", factbook_ont.RadioBand);

    static class Bold
    {
	String bold;

	/**
	 * String italic tag -> Handler
	 */
	java.util.Hashtable italics = new java.util.Hashtable();

	/**
	 * String bold -> Bold
	 */
	static java.util.Hashtable bolds = new java.util.Hashtable();

	Bold(String bold)
	{
	    this.bold = bold;
	    if (! bold.equals(""))
		bold += ":";
	    bolds.put(bold, this);
	}

	void add(String italic,
		 Handler handler)
	{
	    if (! italic.equals(""))
		italic += ":"; 
	    italics.put(italic,
			handler);
	}
    }

    static class Country
	implements Comparable
    {
	String path;
	String code;
	String name = null;
	String bold = "";
	String italic = "";
	com.hp.hpl.mesa.rdf.jena.model.Model model = new com.hp.hpl.mesa.rdf.jena.mem.ModelMem();
	com.hp.hpl.mesa.rdf.jena.model.Resource resource;
	/**
	 * of CountryNumber
	 */
	java.util.Vector borders = new java.util.Vector();
	/**
	 * of CountryNumber
	 */
	java.util.Vector exports = new java.util.Vector();
	/**
	 * of CountryNumber
	 */
	java.util.Vector imports = new java.util.Vector();
	/**
	 * of String name
	 */
	java.util.Vector dependentAreas = new java.util.Vector();

	/**
	 * String name -> Country
	 */
	static java.util.TreeMap countries = new java.util.TreeMap();

	Country(String path)
	    throws Exception
	{
	    this.path = path;

	    // code to use to refer to this country
	    int dot = path.indexOf('.');
	    code = path.substring(0, dot);

	    // resource to use to refer to this country 
	    resource = model.createResource(fipsBase + code.toUpperCase());
	}

	public String toString()
	{
	    return name + " (" + code + ")";
	}

	public int compareTo(Object object)
	{
	    Country country = (Country) object;
	    return name.compareTo(country.name);
	}

	void parseInternal(org.w3c.dom.Node node)
	    throws Exception
	{
	    if (node instanceof org.w3c.dom.Text)
		{
		    String text = trim(node.getNodeValue());
		    if (name == null)
			{
			    name = text;
			    return;
			}
		    if (bold.equals(""))
			{
			}
		    else
			{
			    Bold boldO = (Bold) Bold.bolds.get(bold);
			    if (boldO == null)
				System.err.println("unexpected bold " + bold + " in " + path);
			    else
				{
				    Handler handler = (Handler) boldO.italics.get(italic);
				    if (handler == null)
					{
					    System.err.println("unexpected italic " + italic + " for " + bold + " in " + path); 
					}
				    else
					{
					    handler.process(this,
							    text);
					}
				}
			}
		    return;
		}

	    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
	    String tag = element.getNodeName();
	    if (tag.equals("head"))
		return;
	    else if (tag.equals("a"))
		return;
	    else if (tag.equals("b"))
		{
		    bold = element.getFirstChild().getNodeValue();
		    italic = "";
		}
	    else if (tag.equals("i"))
		{
		    italic = trim(element.getFirstChild().getNodeValue());
		}
	    else if (tag.equals("br")
		     || tag.equals("hr")
		     || tag.equals("p"))
		{
		    // nothing
		}
	    else if (tag.equals("table")
		     || tag.equals("tr")
		     || tag.equals("td")
		     || tag.equals("font")
		     || tag.equals("html")
		     || tag.equals("body"))
		{
		    for (org.w3c.dom.Node child = element.getFirstChild();
			 child != null;
			 child = child.getNextSibling())
			{
			    parseInternal(child);
			}
		}
	    else
		{
		    System.err.println("unexpected tag " + tag);
		}
				       
	}

	void parse()
	    throws Exception
	{
	    // generic entries
	    if (code.equals("xx"))
		return;

	    org.daml.html.Tree tree = new org.daml.html.Tree("file:" + path);

	    parseInternal(tree.document.getDocumentElement());

	    // images
	    model.add(resource,
		      factbook_ont.smallFlag,
		      factbookBase + "flags/" + code + "-flag.jpg");
	    model.add(resource,
		      factbook_ont.largeFlag,
		      factbookBase + "flags/" + code + "-lgflag.jpg");
	    model.add(resource,
		      factbook_ont.map,
		      factbookBase + "maps/" + code + "-map.jpg");

	    countries.put(name, this);
	}

	void processDeferredProperty(java.util.Vector vector,
				     com.hp.hpl.mesa.rdf.jena.model.Property property,
				     com.hp.hpl.mesa.rdf.jena.model.Resource pairClass,
				     com.hp.hpl.mesa.rdf.jena.model.Property countryProperty,
				     com.hp.hpl.mesa.rdf.jena.model.Property numberProperty,
				     String name)
	    throws Exception
	{
	    java.util.Iterator iterator = vector.iterator();
	    while (iterator.hasNext())
		{
		    CountryNumber pair = (CountryNumber) iterator.next();
		    Country pairCountry = (Country) Country.countries.get(pair.country);
		    if (pairCountry == null)
			{
			    System.err.println("can't find " + name + " country " + pair.country + " for " + this);
			}
		    else
			{
			    com.hp.hpl.mesa.rdf.jena.model.Resource pairResource = model.createResource();
			    model.add(pairResource,
				      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
				      pairClass);
			    model.add(pairResource,
				      countryProperty,
				      pairCountry.resource);
			    if (pair.number != null)
				model.add(pairResource,
					  numberProperty,
					  pair.number);
			    com.hp.hpl.mesa.rdf.jena.model.Statement statement = model.createStatement(this.resource,
												       property,
												       pairResource);
			    model.add(statement);

			    // maybe reify note
			    if (pair.comment != null)
				model.add(statement,
					  factbook_ont.note,
					  pair.comment);
			}
		}
	}

	void processDeferredProperties()
	    throws Exception
	{
	    processDeferredProperty(borders,
				    factbook_ont.border,
				    factbook_ont.Border,
				    factbook_ont.country,
				    factbook_ont.distance,
				    "border");
	    processDeferredProperty(exports,
				    factbook_ont.exportPartner,
				    factbook_ont.CountryPercent,
				    factbook_ont.country,
				    factbook_ont.percent,
				    "export partner");
	    processDeferredProperty(imports,
				    factbook_ont.importPartner,
				    factbook_ont.CountryPercent,
				    factbook_ont.country,
				    factbook_ont.percent,
				    "import partner");
	}

	static void addSynonym(String synonym,
			       String name)
	{
	    Object country = countries.get(name);
	    if (country == null)
		{
		    System.err.println("couldn't find country " + name + " for synonym " + synonym);
		    System.exit(1);
		}
	    else
		countries.put(synonym, country);
	}
    }

    static void setupHandlers()
    {
	Section section;
	Bold bold;

	bold = new Bold("");
	bold.add("", new Handler());

	section = new Section("Introduction");

	bold = section.add("Background");
	bold.add("", new StringHandler(factbook_ont.background));

	section = new Section("Geography");

	bold = section.add("Location");
	bold.add("", new StringHandler(factbook_ont.location));

	bold = section.add("Geographic coordinates");
	bold.add("", new Handler()
	    {
		void process(Country country,
			     String value)
		    throws Exception
		{
		    java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(value);
		    double latitude;
		    latitude = Integer.parseInt(tokenizer.nextToken());
		    latitude += ((double) Integer.parseInt(tokenizer.nextToken()) / 60.0);
		    if (tokenizer.nextToken().equals("S,"))
			latitude = (- latitude);

		    double longitude;
		    longitude = Integer.parseInt(tokenizer.nextToken());
		    longitude += ((double) Integer.parseInt(tokenizer.nextToken()) / 60.0);
		    if (tokenizer.nextToken().equals("W"))
			longitude = (- longitude);

		    com.hp.hpl.mesa.rdf.jena.model.Resource latlon = country.model.createResource();
		    country.model.add(latlon,
				      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
				      factbook_ont.LatLon);
		    country.model.add(latlon,
				      factbook_ont.latitude,
				      latitude);
		    country.model.add(latlon,
				      factbook_ont.longitude,
				      longitude);
		    country.model.add(country.resource,
				      factbook_ont.geographicCoordinates,
				      latlon);
		}
	    });

	bold = section.add("Map references");
	bold.add("", new StringHandler(factbook_ont.mapReferences));

	bold = section.add("Area");
	bold.add("total", new UnitsHandler(factbook_ont.totalArea, "sq km"));
	bold.add("land", new UnitsHandler(factbook_ont.landArea, "sq km"));
	bold.add("water", new UnitsHandler(factbook_ont.waterArea, "sq km"));
	bold.add("note", new Handler());

	bold = section.add("Area - comparative");
	bold.add("", new StringHandler(factbook_ont.comparativeArea));
	bold.add("note", new Handler());

	bold = section.add("Land boundaries");
	bold.add("", new Handler()); // e.g. 0 km in aa.html
	bold.add("total", new UnitsHandler(factbook_ont.landBoundaries, "km"));
	bold.add("border countries", new Handler()
	    {
		void process(Country country,
			     String value)
		    throws Exception
		{
		    Tokenizer tokenizer = new Tokenizer(value, ", ");
		    while (tokenizer.hasMoreTokens())
			{
			    String pair = tokenizer.nextToken().trim();
			    int split = pair.lastIndexOf(' ',
							 pair.lastIndexOf(' ') - 1);
			    String pairCountry = pair.substring(0, split);
			    String pairDistance = pair.substring(split + 1);
			    String units = "km";
			    if (! pairDistance.endsWith(units))
				{
				    unexpectedValue(country,
						    factbook_ont.distance,
						    pairDistance);
				    return;
				}
			    country.borders.add(new CountryNumber(pairCountry,
								  df.parse(pairDistance.substring(0, pairDistance.length() - units.length() - 1)),
								  tokenizer.getComment()));
			}
		}
	    });
	bold.add("note", new Handler());

	bold = section.add("Coastline");
	bold.add("", new UnitsHandler(factbook_ont.coastline, "km"));

	bold = section.add("Maritime claims");
	bold.add("", new Handler()); // e.g. none (landlocked) in af.html
	bold.add("exclusive economic zone", new UnitsHandler(factbook_ont.exclusiveEconomicZone, "NM"));
	bold.add("territorial sea", new UnitsHandler(factbook_ont.territorialSea, "NM"));
	bold.add("contiguous zone", new UnitsHandler(factbook_ont.contiguousZone, "NM"));
	bold.add("continental shelf", new UnitsHandler(factbook_ont.continentalShelf, "NM"));
	bold.add("exclusive fishing zone", new UnitsHandler(factbook_ont.exclusiveFishingZone, "NM"));
	bold.add("extended fishing zone", new UnitsHandler(factbook_ont.extendedFishingZone, "NM"));
	bold.add("note", new Handler());

	bold = section.add("Climate");
	bold.add("", new StringHandler(factbook_ont.climate));

	bold = section.add("Terrain");
	bold.add("", new StringHandler(factbook_ont.terrain));
	bold.add("note", new Handler());

	bold = section.add("Elevation extremes");
	bold.add("lowest point", new ElevationExtremeHandler(factbook_ont.lowestPoint));
	bold.add("highest point", new ElevationExtremeHandler(factbook_ont.highestPoint));
	bold.add("note", new Handler());

	bold = section.add("Natural resources");
	bold.add("", new ListHandler(factbook_ont.naturalResource,
				     naturalResources));
	bold.add("note", new Handler());

	bold = section.add("Land use");
	bold.add("arable land", new PercentHandler(factbook_ont.arableLand));
	bold.add("permanent crops", new PercentHandler(factbook_ont.permanentCrops));
	bold.add("permanent pastures", new PercentHandler(factbook_ont.permanentPastures));
	bold.add("forests and woodland", new PercentHandler(factbook_ont.forestsAndWoodland));
	bold.add("other", new PercentHandler(factbook_ont.otherLandUse));
	bold.add("note", new Handler());

	bold = section.add("Irrigated land");
	bold.add("", new UnitsHandler(factbook_ont.irrigatedLand, "sq km"));
	bold.add("note", new Handler());

	bold = section.add("Natural hazards");
	bold.add("", new StringListHandler(factbook_ont.naturalHazard));

	bold = section.add("Environment - current issues");
	bold.add("", new StringListHandler(factbook_ont.environmentalIssue));
	bold.add("note", new Handler());

	bold = section.add("Environment - international agreements");
	bold.add("", new Handler());
	bold.add("party to", new ListHandler(factbook_ont.environmentalAgreementPartyTo,
					     environmentalAgreements));
	bold.add("signed, but not ratified", new ListHandler(factbook_ont.environmentalAgreementSigned,
							     environmentalAgreements));
	bold.add("note", new Handler());

	bold = section.add("Geography - note");
	bold.add("", new Handler());

	section = new Section("People");

	bold = section.add("Population");
	bold.add("", new NumberHandler(factbook_ont.population));
	bold.add("note", new Handler());

	bold = section.add("Age structure"); // XXX
	bold.add("0-14 years", new Handler());
	bold.add("15-64 years", new Handler());
	bold.add("65 years and over", new Handler());

	bold = section.add("Population growth rate");
	bold.add("", new PercentHandler(factbook_ont.populationGrowthRate));
	bold.add("note", new Handler());

	bold = section.add("Birth rate");
	bold.add("", new UnitsHandler(factbook_ont.birthRate,
				      "births/1,000 population"));

	bold = section.add("Death rate");
	bold.add("", new UnitsHandler(factbook_ont.deathRate,
				      "deaths/1,000 population"));

	bold = section.add("Net migration rate");
	bold.add("", new UnitsHandler(factbook_ont.netMigrationRate,
				      "migrant(s)/1,000 population"));
	bold.add("note", new Handler());

	bold = section.add("Sex ratio");
	bold.add("at birth", new SexRatioHandler(factbook_ont.sexRatio, "0", "0"));
	bold.add("under 15 years", new SexRatioHandler(factbook_ont.sexRatio, "0", "14"));
	bold.add("15-64 years", new SexRatioHandler(factbook_ont.sexRatio, "15", "64"));
	bold.add("65 years and over", new SexRatioHandler(factbook_ont.sexRatio, "65", null));
	bold.add("total population", new SexRatioHandler(factbook_ont.sexRatio, null, null));

	bold = section.add("Infant mortality rate");
	bold.add("", new UnitsHandler(factbook_ont.infantMortalityRate,
				      "deaths/1,000 live births"));

	bold = section.add("Life expectancy at birth");	// XXX
	bold.add("total population", new Handler());
	bold.add("male", new Handler());
	bold.add("female", new Handler());

	bold = section.add("Total fertility rate");
	bold.add("", new UnitsHandler(factbook_ont.totalFertilityRate,
				      "children born/woman"));

	bold = section.add("HIV/AIDS - adult prevalence rate");	// XXX
	bold.add("", new Handler());

	bold = section.add("HIV/AIDS - people living with HIV/AIDS"); // XXX
	bold.add("", new Handler());

	bold = section.add("HIV/AIDS - deaths"); // XXX
	bold.add("", new Handler());

	bold = section.add("Nationality");
	bold.add("noun", new StringHandler(factbook_ont.nationalityNoun));
	bold.add("adjective", new StringHandler(factbook_ont.nationalityAdjective));

	bold = section.add("Ethnic groups");
	bold.add("", new ListPercentHandler(factbook_ont.ethnicGroup,
					    ethnicGroups,
					    factbook_ont.EthnicGroupPercent));
	bold.add("note", new Handler());

	bold = section.add("Religions");
	bold.add("", new ListPercentHandler(factbook_ont.religion,
					    religions,
					    factbook_ont.ReligionPercent));
	bold.add("note", new Handler());

	bold = section.add("Languages");
	bold.add("", new ListPercentHandler(factbook_ont.language,
					    languages,
					    factbook_ont.LanguagePercent));
	bold.add("note", new Handler());

	bold = section.add("Literacy");
	bold.add("definition", new StringHandler(factbook_ont.literacyDefinition));
	bold.add("total population", new PercentHandler(factbook_ont.literacyTotal));
	bold.add("male", new PercentHandler(factbook_ont.literacyMale));
	bold.add("female", new PercentHandler(factbook_ont.literacyFemale));
	bold.add("note", new Handler());

	bold = section.add("People - note");
	bold.add("", new Handler());

	section = new Section("Government");

	bold = section.add("Country name");
	bold.add("conventional long form", new StringHandler(factbook_ont.conventionalLongCountryName));
	bold.add("conventional short form", new StringHandler(factbook_ont.conventionalShortCountryName));
	bold.add("local long form", new StringHandler(factbook_ont.localLongCountryName));
	bold.add("local short form", new StringHandler(factbook_ont.localShortCountryName));
	bold.add("former", new StringHandler(factbook_ont.formerCountryName));
	bold.add("abbreviation", new StringHandler(factbook_ont.countryAbbreviation));
	bold.add("note", new Handler());

	bold = section.add("Dependency status");
	bold.add("", new StringHandler(factbook_ont.dependencyStatus));
	bold.add("note", new Handler());
	
	bold = section.add("Government type");
	bold.add("", new ListHandler(factbook_ont.governmentType,
				     governmentTypes));
	bold.add("note", new Handler());

	bold = section.add("Capital");
	bold.add("", new Handler()
	    {
		void process(Country country,
			     String value)
		    throws Exception
		{
		    int semi;
		    String name;
		    ReifiedComments rc = new ReifiedComments();
		    if ((semi = value.indexOf(';')) == (-1))
			{
			    name = value;
			}
		    else
			{
			    name = value.substring(0, semi);
			    rc.processComment(value.substring(semi + 2));
			}
		    com.hp.hpl.mesa.rdf.jena.model.Resource capitalResource = country.model.createResource();
		    country.model.add(capitalResource,
				      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
				      factbook_ont.CapitalCity);
		    country.model.add(capitalResource,
				      factbook_ont.name,
				      name);
		    com.hp.hpl.mesa.rdf.jena.model.Statement statement = country.model.createStatement(country.resource,
												       factbook_ont.capital,
												       capitalResource);
		    country.model.add(statement);
		    rc.add(statement);
		}
	    });

	bold = section.add("Administrative divisions");
	bold.add("", new Handler()
	    {
		boolean isNumber(String string)
		{
		    for (int i = 0; i < string.length(); i++)
			{
			    char ch = string.charAt(i);
			    if ((ch < '0')
				|| (ch > '9'))
				return false;
			}
		    return (string.length() > 0);
		}

		String camelCase(String string)
		{
		    String retval = "";
		    boolean up = true;

		    for (int i = 0; i < string.length(); i++)
			{
			    char ch = string.charAt(i);
			    if (ch == ' ')
				up = true;
			    else
				if (up)
				    {
					retval += Character.toUpperCase(ch);
					up = false;
				    }
				else
				    retval += ch;
			}
		    
		    return retval;
		}

		java.util.Vector parseDescription(String string)
		{
		    java.util.Vector retval = new java.util.Vector();

		    // always end with a name
		    string = string + " 1 ";

		    boolean start = false;
		    boolean inComment = false;
		    int count = 0;
		    String word = "";
		    String name = "";
		    for (int i = 0; i < string.length(); i++)
			{
			    char ch = string.charAt(i);

			    if (ch == ')')
				{
				    inComment = false;
				    continue;
				}
			    if (inComment)
				continue;
			    if (ch == '(')
				{
				    inComment = true;
				    continue;
				}
			    if (ch == ' ')
				{
				    if (word.equals(""))
					continue;

				    if (isNumber(word))
					{
					    if (start)
						{
						    // last name
						    if (! name.equals(""))
							{
							    name = ignorePrefix(name, "and ");
							    name = ignoreSuffix(name, " and");
							    // hacks
							    if (name.endsWith("cities")
								|| name.endsWith("communities")
								|| name.endsWith("counties")
								|| name.endsWith("dependencies")
								|| name.endsWith("municipalities")
								|| name.endsWith("territories"))
								name = name.substring(0, name.length() - 3) + "y";
							    else if (name.endsWith("parishes"))
								name = name.substring(0, name.length() - 2);
							    else if (name.endsWith("oblasti"))
								name = name.substring(0, name.length() - 1);
							    else if (name.endsWith("status"))
								{
								    // nothing
								}
							    else if (count > 1)
								name = ignoreSuffix(name, "s");	// for now, anyway
							    retval.add(camelCase(name));
							}
						}

					    // next name
					    start = true;
					    name = "";
					    count = Integer.parseInt(word);
					}
				    else
					{
					    // add to name
					    if (! word.equals(""))
						{
						    if (name.equals(""))
							name = word;
						    else name += (" " + word);
						}
					}
				    // next word
				    word = "";
				    continue;
				}
			    if ((ch == '*')
				|| (ch == ','))
				continue;
			    // word character
			    word = word + ch;
			}

		    return retval;
		}

		void process(Country country,
			     String value)
		    throws Exception
		{
		    Tokenizer tokenizer = new Tokenizer(value, "; ");
		    while (tokenizer.hasMoreTokens())
			{
			    String description = tokenizer.nextToken();
			    if (description.startsWith("none"))
				return;
			    String divisions;
			    if (tokenizer.hasMoreTokens())
				divisions = tokenizer.nextToken();
			    else
				{
				    System.out.println("mismatched administrative divisions clause " + description + " for " + country); // XXX
				    return;
				}
			    // process description
			    java.util.Vector classNames = parseDescription(description);
			    java.util.Vector classResources = new java.util.Vector();
			    java.util.Iterator iterator = classNames.iterator();
			    while (iterator.hasNext())
				{
				    String className = (String) iterator.next();
				    com.hp.hpl.mesa.rdf.jena.model.Resource classResource = country.model.createResource("#" + className);
				    classResources.add(classResource);
				    country.model.add(classResource,
						      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
						      com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.Class);
				    country.model.add(classResource,
						      com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.subClassOf,
						      factbook_ont.AdministrativeDivision);
				    country.model.add(classResource,
						      com.hp.hpl.mesa.rdf.jena.vocabulary.RDFS.label,
						      className);
				}
			    Tokenizer divisionTokenizer = new Tokenizer(divisions, ", ");
			    while (divisionTokenizer.hasMoreTokens())
				{
				    String division = divisionTokenizer.nextToken();
				    int stars = 0;
				    for (int i = 0; i < division.length(); i++)
					{
					    if (division.charAt(i) == '*')
						stars++;
					}
				    if (stars > 0)
					division = division.substring(0, division.indexOf('*'));
				    com.hp.hpl.mesa.rdf.jena.model.Resource type;
				    if (stars < classResources.size())
					{
					    type = (com.hp.hpl.mesa.rdf.jena.model.Resource) classResources.elementAt(stars);
					}
				    else
					{
					    System.err.println("no administrative division class " + stars + " for " + country);
					    type = factbook_ont.AdministrativeDivision;	// default
					}
				    com.hp.hpl.mesa.rdf.jena.model.Resource adResource = country.model.createResource();
				    country.model.add(adResource,
						      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
						      type);
				    country.model.add(adResource,
						      factbook_ont.name,
						      division);
				    country.model.add(country.resource,
						      factbook_ont.administrativeDivision,
						      adResource);
				}
			}
		}
	    });
	bold.add("note", new Handler());

	bold = section.add("Dependent areas");
	bold.add("", new Handler()
	    {
		void process(Country country,
			     String value)
		    throws Exception
		{
		    Tokenizer tokenizer = new Tokenizer(value, ", ");
		    while (tokenizer.hasMoreTokens())
			{
			    String token = tokenizer.nextToken().trim();
			    country.dependentAreas.add(token);
			}
		}
	    });
	bold.add("note", new Handler());

	bold = section.add("Independence");
	bold.add("", new StringHandler(factbook_ont.independence));
	bold.add("note", new Handler());

	bold = section.add("National holiday");	// XXX
	bold.add("", new Handler());

	bold = section.add("Constitution");
	bold.add("", new StringHandler(factbook_ont.constitution));
	bold.add("note", new Handler());

	bold = section.add("Legal system");
	bold.add("", new StringHandler(factbook_ont.legalSystem));

	bold = section.add("Suffrage");
	bold.add("", new StringHandler(factbook_ont.suffrage));
	bold.add("note", new Handler());

	bold = section.add("Executive branch");	// XXX
	bold.add("", new Handler());
	bold.add("chief of state", new Handler());
	bold.add("head of government", new Handler());
	bold.add("cabinet", new Handler());
	bold.add("elections", new Handler());
	bold.add("election results", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Legislative branch"); // XXX
	bold.add("", new Handler());
	bold.add("elections", new Handler());
	bold.add("election results", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Judicial branch"); // XXX
	bold.add("", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Political parties and leaders"); // XXX
	bold.add("", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Political pressure groups and leaders"); // XXX
	bold.add("", new Handler());
	bold.add("note", new Handler());

	bold = section.add("International organization participation");
	bold.add("", new ListHandler(factbook_ont.participatesIn,
				     internationalOrganizations));

	bold = section.add("Diplomatic representation in the US"); // XXX
	bold.add("", new Handler());
	bold.add("chancery", new Handler());
	bold.add("embassy", new Handler());
	bold.add("consulate(s)", new Handler());
	bold.add("consulate(s) general", new Handler());
	bold.add("honorary consulate(s) general", new Handler());
	bold.add("honorary consulate(s)", new Handler());
	bold.add("chief of mission", new Handler());
	bold.add("mailing address", new Handler());
	bold.add("telephone", new Handler());
	bold.add("telephone", new Handler());
	bold.add("FAX", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Diplomatic representation from the US"); // XXX
	bold.add("", new Handler());
	bold.add("chief of mission", new Handler());
	bold.add("embassy", new Handler());
	bold.add("consulate(s)", new Handler());
	bold.add("consulate(s) general", new Handler());
	bold.add("branch office(s)", new Handler());
	bold.add("mailing address", new Handler());
	bold.add("telephone", new Handler());
	bold.add("FAX", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Flag description");
	bold.add("", new StringHandler(factbook_ont.flagDescription));
	bold.add("note", new Handler());

	bold = section.add("Government - note");
	bold.add("", new Handler());

	section = new Section("Economy");

	bold = section.add("Economy - overview");
	bold.add("", new StringHandler(factbook_ont.economyOverview));

	bold = section.add("GDP");
	bold.add("", new PPPHandler(factbook_ont.grossDomesticProduct));
	bold.add("note", new Handler());

	bold = section.add("GDP - real growth rate");
	bold.add("", new PercentHandler(factbook_ont.grossDomesticProductRealGrowth));

	bold = section.add("GDP - per capita");
	bold.add("", new PPPHandler(factbook_ont.grossDomesticProductPerCapita));

	bold = section.add("GDP - composition by sector");
	bold.add("", new Handler());
	bold.add("agriculture", new PercentHandler(factbook_ont.grossDomesticProductAgriculture));
	bold.add("industry", new PercentHandler(factbook_ont.grossDomesticProductIndustry));
	bold.add("services", new PercentHandler(factbook_ont.grossDomesticProductServices));
	bold.add("note", new Handler());

	bold = section.add("Population below poverty line");
	bold.add("", new PercentHandler(factbook_ont.populationBelowPovertyLine));

	bold = section.add("Household income or consumption by percentage share");
	bold.add("lowest 10%", new PercentHandler(factbook_ont.householdIncomeLowest10Percent));
	bold.add("highest 10%", new PercentHandler(factbook_ont.householdIncomeHighest10Percent));

	bold = section.add("Inflation rate (consumer prices)");
	bold.add("", new PercentHandler(factbook_ont.inflationRate));
	bold.add("note", new Handler());

	bold = section.add("Labor force");
	bold.add("", new NumberHandler(factbook_ont.laborForce));
	bold.add("note", new Handler());

	bold = section.add("Labor force - by occupation");
	bold.add("", new ListPercentHandler(factbook_ont.occupation,
					    occupations,
					    factbook_ont.OccupationPercent));
	bold.add("note", new Handler());

	bold = section.add("Unemployment rate");
	bold.add("", new PercentHandler(factbook_ont.unemploymentRate));
	bold.add("note", new Handler());

	bold = section.add("Budget");
	bold.add("revenues", new DollarHandler(factbook_ont.budgetRevenues));
	bold.add("expenditures", new Handler()
	    {
		Handler handler1 = new DollarHandler(factbook_ont.budgetExpenditures);
		Handler handler2 = new DollarHandler(factbook_ont.budgetCapitalExpenditures);
		void process(Country country,
			     String value)
		    throws Exception
		{
		    if (value.equals("NA"))
			return;
		    int comma = value.indexOf(", ");
		    handler1.process(country,
				     value.substring(0, comma));
		    handler2.process(country,
				     ignorePrefix(value.substring(comma + 2), // best way?
						  "including capital expenditures of "));
		}
	    });
	bold.add("note", new Handler());

	bold = section.add("Industries");
	bold.add("", new SublistsHandler(factbook_ont.industry,
					 industries));

	bold = section.add("Industrial production growth rate");
	bold.add("", new PercentHandler(factbook_ont.industrialProductionGrowthRate));

	bold = section.add("Electricity - production");
	bold.add("", new UnitsHandler(factbook_ont.electricityProduction,
				      "kWh"));
	bold.add("note", new Handler());

	bold = section.add("Electricity - production by source");
	bold.add("fossil fuel", new PercentHandler(factbook_ont.electricityProductionFossilFuel));
	bold.add("hydro", new PercentHandler(factbook_ont.electricityProductionHydro));
	bold.add("nuclear", new PercentHandler(factbook_ont.electricityProductionNuclear));
	bold.add("other", new PercentHandler(factbook_ont.electricityProductionOther));
	bold.add("note", new Handler());

	bold = section.add("Electricity - consumption");
	bold.add("", new UnitsHandler(factbook_ont.electricityConsumption, 
				      "kWh"));

	bold = section.add("Electricity - exports");
	bold.add("", new UnitsHandler(factbook_ont.electricityExports,
				      "kWh"));
	bold.add("note", new Handler());

	bold = section.add("Electricity - imports");
	bold.add("", new UnitsHandler(factbook_ont.electricityImports,
				      "kWh"));
	bold.add("note", new Handler());

	bold = section.add("Agriculture - products");
	bold.add("", new SublistsHandler(factbook_ont.agricultureProduct,
					 agricultureProducts));

	bold = section.add("Exports");
	bold.add("", new DollarHandler(factbook_ont.exports));

	bold = section.add("Exports - commodities");
	bold.add("", new ListPercentHandler(factbook_ont.exportsCommodity, 
					    commodities,
					    factbook_ont.CommodityPercent));

	bold = section.add("Exports - partners");
	bold.add("", new CountryPercentHandler(factbook_ont.exportPartner));
	bold.add("note", new Handler());

	bold = section.add("Imports");
	bold.add("", new DollarHandler(factbook_ont.imports));

	bold = section.add("Imports - commodities");
	bold.add("", new ListPercentHandler(factbook_ont.importsCommodity,
					    commodities,
					    factbook_ont.CommodityPercent));

	bold = section.add("Imports - partners");
	bold.add("", new CountryPercentHandler(factbook_ont.importPartner));

	bold = section.add("Debt - external");
	bold.add("", new DollarHandler(factbook_ont.externalDebt));

	bold = section.add("Economic aid - donor");
	bold.add("", new DollarHandler(factbook_ont.economicAidDonor));

	bold = section.add("Economic aid - recipient");
	bold.add("", new DollarHandler(factbook_ont.economicAidRecipient));
	bold.add("note", new Handler());

	bold = section.add("Currency");
	bold.add("", new StringHandler(factbook_ont.currency));
	bold.add("note", new Handler());

	bold = section.add("Currency code");
	bold.add("", new StringHandler(factbook_ont.currencyCode));

	bold = section.add("Exchange rates");
	bold.add("", new Handler()); // XXX
	bold.add("note", new Handler());

	bold = section.add("Fiscal year");
	bold.add("", new StringHandler(factbook_ont.fiscalYear));

	section = new Section("Communications");

	bold = section.add("Telephones - main lines in use");
	bold.add("", new NumberHandler(factbook_ont.mainTelephoneLines));
	bold.add("note", new Handler());

	bold = section.add("Telephones - mobile cellular");
	bold.add("", new NumberHandler(factbook_ont.mobileTelephoneLines));
	bold.add("note", new Handler());

	bold = section.add("Telephone system");
	bold.add("general assessment", new StringHandler(factbook_ont.telephoneSystemGeneralAssessment));
	bold.add("domestic", new StringHandler(factbook_ont.telephoneSystemDomestic));
	bold.add("international", new StringHandler(factbook_ont.telephoneSystemInternational));
	bold.add("note", new Handler());

	bold = section.add("Radio broadcast stations");	// XXX - need to check for ", "
	bold.add("", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Radios");
	bold.add("", new NumberHandler(factbook_ont.radios));

	bold = section.add("Television broadcast stations");
	bold.add("", new NumberHandler(factbook_ont.televisionBroadcastStations));
	bold.add("note", new Handler());

	bold = section.add("Televisions");
	bold.add("", new NumberHandler(factbook_ont.televisions));
	bold.add("note", new Handler());

	bold = section.add("Internet country code");
	bold.add("", new InternetCountryCodeHandler(factbook_ont.internetCountryCode));

	bold = section.add("Internet Service Providers (ISPs)");
	bold.add("", new NumberHandler(factbook_ont.internetServiceProviders));
	bold.add("note", new Handler());

	bold = section.add("Internet users");
	bold.add("", new NumberHandler(factbook_ont.internetUsers));
	bold.add("note", new Handler());

	bold = section.add("Communications - note");
	bold.add("", new Handler());

	section = new Section("Transportation");

	bold = section.add("Railways");	// XXX
	bold.add("", new Handler()); // e.g. 0 km in aa.html
	bold.add("total", new UnitsHandler(factbook_ont.railwaysTotal,
					   "km"));
	bold.add("standard gauge", new Handler());
	bold.add("narrow gauge", new Handler());
	bold.add("broad gauge", new Handler());
	bold.add("dual gauge", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Highways");
	bold.add("", new Handler());
	bold.add("total", new UnitsHandler(factbook_ont.highwaysTotal,
					   "km"));
	bold.add("paved", new UnitsHandler(factbook_ont.highwaysPaved,
					   "km"));
	bold.add("unpaved", new UnitsHandler(factbook_ont.highwaysUnpaved,
					     "km"));
	bold.add("note", new Handler());

	bold = section.add("Waterways");
	bold.add("", new UnitsHandler(factbook_ont.waterways,
				      "km"));
	bold.add("note", new Handler());

	bold = section.add("Pipelines"); // XXX
	bold.add("", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Ports and harbors");
	bold.add("", new Handler()
	    {
		void process(Country country,
			     String value)
		    throws Exception
		{
		    Tokenizer tokenizer = new Tokenizer(value, ", ");
		    while (tokenizer.hasMoreTokens())
			{
			    String token = tokenizer.nextToken().trim();
			    com.hp.hpl.mesa.rdf.jena.model.Resource portResource = country.model.createResource();
			    country.model.add(portResource,
					      com.hp.hpl.mesa.rdf.jena.vocabulary.RDF.type,
					      factbook_ont.Port);
			    country.model.add(portResource,
					      factbook_ont.name,
					      token);
			    country.model.add(country.resource,
					      factbook_ont.port,
					      portResource);
			}
		}
	    });
	bold.add("note", new Handler());

	bold = section.add("Merchant marine"); // XXX
	bold.add("", new Handler());
	bold.add("total", new Handler());
	bold.add("ships by type", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Airports");
	bold.add("", new NumberHandler(factbook_ont.airports));
	bold.add("note", new Handler());

	bold = section.add("Airports - with paved runways");
	bold.add("total", new Handler()); // redundant
	bold.add("over 3,047 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, true, "3048", null));
	bold.add("2,438 to 3,047 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, true, "2438", "3047"));
	bold.add("1,524 to 2,437 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, true, "1524", "2437"));
	bold.add("914 to 1,523 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, true, "914", "1523"));
	bold.add("under 914 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, true, null, "913"));

	bold = section.add("Airports - with unpaved runways");
	bold.add("total", new Handler()); // redundant
	bold.add("over 3,047 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, false, "3048", null));
	bold.add("2,438 to 3,047 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, false, "2438", "3047"));
	bold.add("1,524 to 2,437 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, false, "1524", "2437"));
	bold.add("914 to 1,523 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, false, "914", "1523"));
	bold.add("under 914 m", new AirportBreakdownHandler(factbook_ont.airportBreakdown, false, null, "913"));

	bold = section.add("Heliports");
	bold.add("", new NumberHandler(factbook_ont.heliports));

	bold = section.add("Transportation - note");
	bold.add("", new Handler());

	section = new Section("Military");

	bold = section.add("Military branches"); // XXX
	bold.add("", new Handler());
	bold.add("note", new Handler());

	bold = section.add("Military manpower - military age");
	bold.add("", new UnitsHandler(factbook_ont.militaryAge,
				      "years of age"));
	bold.add("note", new Handler());

	bold = section.add("Military manpower - availability");
	bold.add("males age 15-49", new NumberHandler(factbook_ont.malesOfMilitaryAge));
	bold.add("females age 15-49", new NumberHandler(factbook_ont.femalesOfMilitaryAge));
	bold.add("note", new Handler());

	bold = section.add("Military manpower - fit for military service");
	bold.add("", new Handler());
	bold.add("males age 15-49", new NumberHandler(factbook_ont.malesFitForMilitaryService));
	bold.add("females age 15-49", new NumberHandler(factbook_ont.femalesFitForMilitaryService));

	bold = section.add("Military manpower - reaching military age annually");
	bold.add("males", new NumberHandler(factbook_ont.malesReachingMilitaryAgeAnnually));
	bold.add("females", new NumberHandler(factbook_ont.femalesReachingMilitaryAgeAnnually));

	bold = section.add("Military expenditures - dollar figure");
	bold.add("", new DollarHandler(factbook_ont.militaryExpenditures));

	bold = section.add("Military expenditures - percent of GDP");
	bold.add("", new PercentHandler(factbook_ont.militaryExpendituresPercentGDP));

	bold = section.add("Military - note");
	bold.add("", new Handler());

	section = new Section("Transnational Issues");

	bold = section.add("Disputes - international");
	bold.add("", new StringListHandler(factbook_ont.internationalDispute));

	bold = section.add("Illicit drugs");
	bold.add("", new StringListHandler(factbook_ont.illicitDrugs));
    }

    public static void main(String args[])
	throws Exception
    {
	setupHandlers();

	for (int i = 0; i < args.length; i++)
	    {
		String arg = args[i];
		Country country = new Country(arg);
		country.parse();
	    }

	// deferred resolution of country names
	Country.addSynonym("UAE", "United Arab Emirates");
	Country.addSynonym("UK", "United Kingdom");
	Country.addSynonym("US", "United States");
	Country.addSynonym("North Korea", "Korea, North");
	Country.addSynonym("NZ", "New Zealand");
	Country.addSynonym("South Korea", "Korea, South");
	java.util.Iterator iterator = Country.countries.values().iterator();
	while (iterator.hasNext())
	    {
		Country country = (Country) iterator.next();
		country.processDeferredProperties();
		java.util.Iterator dependentAreaIterator = country.dependentAreas.iterator();
		while (dependentAreaIterator.hasNext())
		    {
			String token = (String) dependentAreaIterator.next();
			Country otherCountry = (Country) Country.countries.get(token);
			if (otherCountry == null)
			    {
				System.err.println("can't find dependent area " + token + " for " + country);
			    }
			else
			    {
				country.model.add(country.resource,
						  factbook_ont.dependentArea,
						  otherCountry.resource);
			    }
		    }
	    }

	// serialize
	iterator = Country.countries.values().iterator();
	while (iterator.hasNext())
	    {
		Country country = (Country) iterator.next();
		java.io.PrintWriter stream = new java.io.PrintWriter(new java.io.FileOutputStream(country.code + ".daml"));
		stream.println("<?xml version='1.0' encoding='ISO-8859-1'?>");
		country.model.write(stream);
	    }
	iterator = List.lists.iterator();
	while (iterator.hasNext())
	    {
		List list = (List) iterator.next();
		list.generate();
	    }

	// index page
	java.io.PrintWriter stream = new java.io.PrintWriter(new java.io.FileOutputStream("countries.html"));
	String title = "DAML CIA World Factbook Pages";
	stream.println("<title>" + title + "</title>");
	stream.println("<h1>" + title + "</h1>");
	stream.println("<h2>Countries</h2>");
	stream.println("<ul>");
	iterator = Country.countries.values().iterator();
	while (iterator.hasNext())
	    {
		Country country = (Country) iterator.next();
		stream.println("  <li><a href=\"" + country.code + "\">" + country.name + "</a></li>");
	    }
	stream.println("</ul>");
	stream.println("<h2>Shared Objects</h2>");
	stream.println("<ul>");
	iterator = List.lists.iterator();
	while (iterator.hasNext())
	    {
		List list = (List) iterator.next();
		stream.println("  <li><a href=\"" + list.name + "\">" + list.name + "</a></li>");  
	    }
	stream.println("</ul>");
	stream.println("<hr>");
	stream.println(new java.util.Date());
	stream.close();
    }
}
