/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Methods to read collect.cfg files into internal XML form, and write 
 * them back out again.
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.cdm;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;

import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataTools;
import org.greenstone.gatherer.util.Codec;
import org.greenstone.gatherer.util.XMLTools;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;

import org.w3c.dom.*;

public class CollectCfgReadWrite {

  
  static public String toString (Element command_element) {
    String command_element_name = command_element.getNodeName ();
    if(command_element_name.equals (StaticStrings.CLASSIFY_ELEMENT)) {
      return classifyToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.FORMAT_ELEMENT)) {
      return formatToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.INDEXES_ELEMENT)) {
      return indexesToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.INDEX_DEFAULT_ELEMENT)) {
      return indexDefaultToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.SORTS_ELEMENT)) {
      return sortsToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.SORT_DEFAULT_ELEMENT)) {
      return sortDefaultToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.LANGUAGES_ELEMENT)) {
      return languagesToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.LANGUAGE_DEFAULT_ELEMENT)) {
      return languageDefaultToString (command_element);
    }
    else if (command_element_name.equals (StaticStrings.LANGUAGE_METADATA_ELEMENT)) {
      return languageMetadataToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.INDEXOPTIONS_ELEMENT)) {
      return indexOptionsToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.INDEXOPTION_DEFAULT_ELEMENT)) {
      return indexOptionDefaultToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.COLLECTIONMETADATA_ELEMENT)) {
      return metadataToString (command_element); 
    }
    else if(command_element_name.equals (StaticStrings.COLLECTIONMETADATA_CREATOR_ELEMENT)) {
      return metadataToString (command_element); 
    }
    else if(command_element_name.equals (StaticStrings.COLLECTIONMETADATA_MAINTAINER_ELEMENT)) {
      return metadataToString (command_element); 
    }
    else if(command_element_name.equals (StaticStrings.COLLECTIONMETADATA_PUBLIC_ELEMENT)) {
      return metadataToString (command_element); 
    }
    else if (command_element_name.equals (StaticStrings.SEARCHMETADATA_ELEMENT)) {
	return metadataToString( command_element, true);
    }
    else if (command_element_name.equals (StaticStrings.BUILDTYPE_ELEMENT)) {
      return metadataToString (command_element); 
    }
    else if (command_element_name.equals (StaticStrings.DATABASETYPE_ELEMENT)) {
      return metadataToString (command_element); 
    } 
    else if(command_element_name.equals (StaticStrings.PLUGIN_ELEMENT)) {
      return pluginToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.SUBCOLLECTION_ELEMENT)) {
      return subcollectionToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.SUBCOLLECTION_DEFAULT_INDEX_ELEMENT)) {
      return subcollectionDefaultIndexToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.SUBCOLLECTION_INDEXES_ELEMENT)) {
      return subcollectionIndexesToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.SUPERCOLLECTION_ELEMENT)) {
      return supercollectionToString (command_element);
    }
    else if(command_element_name.equals (StaticStrings.UNKNOWN_ELEMENT)) {
      return unknownToString (command_element);
    }
    return "";
  }

  static private String classifyToString (Element command_element) {
    StringBuffer text = new StringBuffer (StaticStrings.CLASSIFY_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    text.append (command_element.getAttribute (StaticStrings.TYPE_ATTRIBUTE));
    NodeList option_elements = command_element.getElementsByTagName (StaticStrings.OPTION_ELEMENT);
    int option_elements_length = option_elements.getLength ();
    for(int j = 0; j < option_elements_length; j++) {
      Element option_element = (Element) option_elements.item (j);
      if(option_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) {
	text.append (StaticStrings.SPACE_CHARACTER);
	text.append (StaticStrings.MINUS_CHARACTER);
	text.append (option_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
	String value_str = XMLTools.getValue (option_element);
	
	if (value_str.length () > 0) {
	  text.append (StaticStrings.SPACE_CHARACTER);
	  if(value_str.indexOf (StaticStrings.SPACE_CHARACTER) != -1) {
	    // enclose in quotes
	    text.append(StaticStrings.SPEECH_CHARACTER);
	    text.append(value_str);
	    text.append(StaticStrings.SPEECH_CHARACTER);
	  } else {
		    
	    text.append(value_str);
	  }
	}
              
	value_str = null;
      }
      option_element = null;
    }
    option_elements = null;
    return text.toString ();
  }
    
  static private String formatToString (Element command_element) {
    StringBuffer text = new StringBuffer (StaticStrings.FORMAT_STR);
    text.append (StaticStrings.SPACE_CHARACTER);
    text.append (command_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
    text.append (StaticStrings.SPACE_CHARACTER);
    String value_str = command_element.getAttribute (StaticStrings.VALUE_ATTRIBUTE);
    if(value_str.length () != 0) {
      text.append (value_str);
    }
    else {
      // Remember to encode format string to Greenstone specification
      value_str = Codec.transform (XMLTools.getValue (command_element), Codec.DOM_TO_GREENSTONE);
      text.append (StaticStrings.SPEECH_CHARACTER);
      text.append (value_str);
      text.append (StaticStrings.SPEECH_CHARACTER);
    }
    value_str = null;
    return text.toString ();
  }
  
  static private String indexesToString (Element command_element) {
    boolean comment_only = false;
    StringBuffer text = new StringBuffer ("");
    if(command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.FALSE_STR)) {
      text.append ("#");
      comment_only = true;
    }
    text.append (StaticStrings.INDEX_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    if(!comment_only) {
      text.append (StaticStrings.TAB_CHARACTER);
    }
    NodeList index_elements = command_element.getElementsByTagName (StaticStrings.INDEX_ELEMENT);
    if (index_elements.getLength () == 0) { // no indexes
      return "";
    }
    // For each index, write its level, a colon, then concatenate its child content elements into a single comma separated list
    int index_elements_length = index_elements.getLength ();
    for(int j = 0; j < index_elements_length; j++) {
      Element index_element = (Element) index_elements.item (j);
      String level_str = index_element.getAttribute (StaticStrings.LEVEL_ATTRIBUTE);
      if(level_str.length () > 0) {
	text.append (level_str);
	text.append (StaticStrings.COLON_CHARACTER);
      }
      NodeList content_elements = index_element.getElementsByTagName (StaticStrings.CONTENT_ELEMENT);
      int content_elements_length = content_elements.getLength ();
      // Don't output anything if no indexes are set
      if(content_elements_length == 0) {
	return null;
      }
      for(int k = 0; k < content_elements_length; k++) {
	Element content_element = (Element) content_elements.item (k);
	String name_str = content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE);
	text.append (name_str);
	name_str = null;
	if(k < content_elements_length - 1) {
	  text.append (StaticStrings.COMMA_CHARACTER);
	}
	content_element = null;
      }
      if(j < index_elements_length - 1) {
	text.append (StaticStrings.SPACE_CHARACTER);
      }
      content_elements = null;
      index_element = null;
    }
    index_elements = null;
    return text.toString ();
  }
    
  static private String indexDefaultToString (Element command_element) {
    StringBuffer text = new StringBuffer ("");
    if(command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.FALSE_STR)) {
      text.append ("#");
    }
    text.append (StaticStrings.INDEX_DEFAULT_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    if (!command_element.getAttribute (StaticStrings.LEVEL_ATTRIBUTE).equals ("")) {
      text.append (command_element.getAttribute (StaticStrings.LEVEL_ATTRIBUTE));
      text.append (StaticStrings.COLON_CHARACTER);
    }
    NodeList content_elements = command_element.getElementsByTagName (StaticStrings.CONTENT_ELEMENT);
    int content_elements_length = content_elements.getLength ();
    for(int j = 0; j < content_elements_length; j++) {
      Element content_element = (Element) content_elements.item (j);
      String name_str = content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE);
      text.append (name_str);
      name_str = null;
      if(j < content_elements_length - 1) {
	text.append (StaticStrings.COMMA_CHARACTER);
      }
      content_element = null;
    }
    content_elements = null;
    return text.toString ();
  }
     static private String sortsToString (Element command_element) {
    boolean comment_only = false;
    StringBuffer text = new StringBuffer ("");
    if(command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.FALSE_STR)) {
	System.err.println("sorts not assigned, returning false");
	return "";
	//	text.append ("#");
	//comment_only = true;
    }
    text.append (StaticStrings.SORT_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    //    if(!comment_only) { why???
    //  text.append (StaticStrings.TAB_CHARACTER);
    // }
    NodeList index_elements = command_element.getElementsByTagName (StaticStrings.SORT_ELEMENT);
    if (index_elements.getLength () == 0) { // no indexes
      return "";
    }
    // For each sortfield,  concatenate its child content elements into a single comma separated list
    int index_elements_length = index_elements.getLength ();
    for(int j = 0; j < index_elements_length; j++) {
      Element index_element = (Element) index_elements.item (j);
      NodeList content_elements = index_element.getElementsByTagName (StaticStrings.CONTENT_ELEMENT);
      int content_elements_length = content_elements.getLength ();
      // Don't output anything if no indexes are set
      if(content_elements_length == 0) {
	return null;
      }
      for(int k = 0; k < content_elements_length; k++) {
	Element content_element = (Element) content_elements.item (k);
	String name_str = content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE);
	text.append (name_str);
	name_str = null;
	if(k < content_elements_length - 1) {
	  text.append (StaticStrings.COMMA_CHARACTER);
	}
	content_element = null;
      }
      if(j < index_elements_length - 1) {
	text.append (StaticStrings.SPACE_CHARACTER);
      }
      content_elements = null;
      index_element = null;
    }
    index_elements = null;
    return text.toString ();
  }
    
  static private String sortDefaultToString (Element command_element) {
    StringBuffer text = new StringBuffer ("");
    if(command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.FALSE_STR)) {
	return "";
    }
    text.append (StaticStrings.SORT_DEFAULT_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    NodeList content_elements = command_element.getElementsByTagName (StaticStrings.CONTENT_ELEMENT);
    int content_elements_length = content_elements.getLength ();
    for(int j = 0; j < content_elements_length; j++) {
      Element content_element = (Element) content_elements.item (j);
      String name_str = content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE);
      text.append (name_str);
      name_str = null;
      if(j < content_elements_length - 1) {
	text.append (StaticStrings.COMMA_CHARACTER);
      }
      content_element = null;
    }
    content_elements = null;
    return text.toString ();
  }
 
  static private String languagesToString (Element command_element) {
    StringBuffer text = new StringBuffer (StaticStrings.LANGUAGES_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    // Retrieve all the languages and write them out in a space separated list
    NodeList language_elements = command_element.getElementsByTagName (StaticStrings.LANGUAGE_ELEMENT);
    int language_elements_length = language_elements.getLength ();
    if(language_elements_length == 0) {
      return null;
    }
    for(int j = 0; j < language_elements_length; j++) {
      Element language_element = (Element) language_elements.item (j);
      text.append (language_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
      if(j < language_elements_length - 1) {
	text.append (StaticStrings.SPACE_CHARACTER);
      }
    }
    return text.toString ();
  }
    
  static private String languageDefaultToString (Element command_element) {
    StringBuffer text = new StringBuffer (StaticStrings.LANGUAGE_DEFAULT_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    text.append (command_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
    return text.toString ();
  }
    
  static private String languageMetadataToString (Element command_element) {
    if (!command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) {
      return "";
    }
    StringBuffer text = new StringBuffer (StaticStrings.LANGUAGE_METADATA_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    String name_str = command_element.getAttribute (StaticStrings.NAME_ATTRIBUTE);
    text.append (name_str);
    return text.toString ();
  }
    
  static private String indexOptionsToString (Element command_element) {
    StringBuffer text = new StringBuffer ("");
    if(command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.FALSE_STR)) {
      text.append ("#");
    }
    text.append (command_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
    text.append (StaticStrings.TAB_CHARACTER);
    NodeList content_elements = command_element.getElementsByTagName (StaticStrings.INDEXOPTION_ELEMENT);
    int content_elements_length = content_elements.getLength ();
    // Don't output anything if no options are set.
    if(content_elements_length == 0) {
      return null;
    }
    for(int i = 0; i < content_elements_length; i++) {
      Element content_element = (Element) content_elements.item (i);
      text.append (content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
      text.append (StaticStrings.SPACE_CHARACTER);
    }
    return text.substring (0, text.length () - 1);
  }
    
  static private String indexOptionDefaultToString (Element command_element) {
    // Don't bother if there is no value
    if (command_element.getAttribute (StaticStrings.VALUE_ATTRIBUTE).equals ("")) {
      return "";
    }
    StringBuffer text = new StringBuffer ("");
    if(command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.FALSE_STR)) {
      text.append ("#");
    }
    text.append (command_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
    text.append (StaticStrings.TAB_CHARACTER);
    text.append (command_element.getAttribute (StaticStrings.VALUE_ATTRIBUTE));
    return text.toString ();
  }
    
    static private String metadataToString(Element command_element) {
	return metadataToString(command_element, false);
    }
    static private String metadataToString (Element command_element, boolean use_dot) {
    // lets first check the value - if its empty, don't bother sticking it in the config file
    String value_str = XMLTools.getValue (command_element);
    if (value_str.equals ("")) {
      return "";
    }
    boolean special = false;
        
    StringBuffer text = new StringBuffer ("");
    String name_str = command_element.getAttribute (StaticStrings.NAME_ATTRIBUTE);
    // If the name is one of the special four, we don't write the collectionmeta first. Note maintainer and buildtype are singled out for 'prittying' reasons.
    if(name_str.equals (StaticStrings.COLLECTIONMETADATA_MAINTAINER_STR)|| name_str.equals (StaticStrings.BUILDTYPE_STR) || name_str.equals (StaticStrings.DATABASETYPE_STR)) {
      text.append (name_str);
      text.append (StaticStrings.TAB_CHARACTER);
      special = true;
    }
    else if (name_str.equals (StaticStrings.COLLECTIONMETADATA_CREATOR_STR) || name_str.equals (StaticStrings.COLLECTIONMETADATA_PUBLIC_STR) ) {
      text.append (name_str);
      text.append (StaticStrings.TAB_CHARACTER);
      text.append (StaticStrings.TAB_CHARACTER);
      special = true;
    }
    else {
      text.append (StaticStrings.COLLECTIONMETADATA_STR);
      text.append (StaticStrings.TAB_CHARACTER);
      if (use_dot) {
	  text.append(StaticStrings.STOP_CHARACTER);
      }
      text.append (name_str);
      text.append (StaticStrings.SPACE_CHARACTER);
      String language_str = command_element.getAttribute (StaticStrings.LANGUAGE_ATTRIBUTE);
      text.append (StaticStrings.LBRACKET_CHARACTER);
      text.append (StaticStrings.METADATA_LANGUAGE_SPECIFIER);
      text.append (language_str);
      text.append (StaticStrings.RBRACKET_CHARACTER);
      text.append (StaticStrings.SPACE_CHARACTER);
    }
    name_str = null;
        
    // decode the value from XML to a form for config file
    value_str = Codec.transform (value_str, Codec.DOM_TO_GREENSTONE);
        
    // We don't wrap the email addresses in quotes, nor the other special metadata
    if(special) {
      text.append (value_str);
    }
    else {
      text.append (StaticStrings.SPEECH_CHARACTER);
      text.append (value_str);
      text.append (StaticStrings.SPEECH_CHARACTER);
    }
    value_str = null;
    return text.toString ();
  }
    
  static private String searchtypeToString (Element command_element) {
    if(command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) {
      StringBuffer text = new StringBuffer (StaticStrings.SEARCHTYPE_STR);
      text.append (StaticStrings.TAB_CHARACTER);
      NodeList search_elements = command_element.getElementsByTagName (StaticStrings.CONTENT_ELEMENT);
      int search_elements_length = search_elements.getLength ();
      for(int i = 0; i < search_elements_length; i++) {
	Element search_element = (Element) search_elements.item (i);
	text.append (search_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
	text.append (StaticStrings.SPACE_CHARACTER);
      }
      return text.substring (0, text.length () - 1);
    }
    else {
      return null;
    }
  }
    
  static private String subcollectionToString (Element command_element) {
    StringBuffer text = new StringBuffer (StaticStrings.SUBCOLLECTION_STR);
    text.append (StaticStrings.SPACE_CHARACTER);
    text.append (command_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
    text.append (StaticStrings.SPACE_CHARACTER);
    text.append (StaticStrings.TAB_CHARACTER);
    text.append (StaticStrings.SPEECH_CHARACTER);
    if(command_element.getAttribute (StaticStrings.TYPE_ATTRIBUTE).equals (StaticStrings.EXCLUDE_STR)) {
      text.append (StaticStrings.EXCLAMATION_CHARACTER);
    }
    text.append (command_element.getAttribute (StaticStrings.CONTENT_ATTRIBUTE));
    text.append (StaticStrings.SEPARATOR_CHARACTER);
    text.append (XMLTools.getValue (command_element));
    text.append (StaticStrings.SEPARATOR_CHARACTER);
    String options_str = command_element.getAttribute (StaticStrings.OPTIONS_ATTRIBUTE);
    if(options_str.length () > 0) {
      text.append (options_str);
    }
    options_str = null;
    text.append (StaticStrings.SPEECH_CHARACTER);
    return text.toString ();
  }
    
  static private String subcollectionDefaultIndexToString (Element command_element) {
    StringBuffer text = new StringBuffer (StaticStrings.SUBCOLLECTION_DEFAULT_INDEX_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    NodeList content_elements = command_element.getElementsByTagName (StaticStrings.CONTENT_ELEMENT);
    int content_elements_length = content_elements.getLength ();
    for(int j = 0; j < content_elements_length; j++) {
      Element content_element = (Element) content_elements.item (j);
      text.append (content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
      if(j < content_elements_length - 1) {
	text.append (StaticStrings.COMMA_CHARACTER);
      }
    }
    return text.toString ();
  }
    
  static private String subcollectionIndexesToString (Element command_element) {
    StringBuffer text = new StringBuffer (StaticStrings.SUBCOLLECTION_INDEX_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    // Retrieve all of the subcollection index partitions
    NodeList subcollectionindex_elements = command_element.getElementsByTagName (StaticStrings.INDEX_ELEMENT);
    int subcollectionindex_elements_length = subcollectionindex_elements.getLength ();
    if(subcollectionindex_elements_length == 0) {
      return null;
    }
    for(int j = 0; j < subcollectionindex_elements_length; j++) {
      Element subcollectionindex_element = (Element) subcollectionindex_elements.item (j);
      NodeList content_elements = subcollectionindex_element.getElementsByTagName (StaticStrings.CONTENT_ELEMENT);
      int content_elements_length = content_elements.getLength ();
      for(int k = 0; k < content_elements_length; k++) {
	Element content_element = (Element) content_elements.item (k);
	text.append (content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
	if(k < content_elements_length - 1) {
	  text.append (StaticStrings.COMMA_CHARACTER);
	}
      }
      if(j < subcollectionindex_elements_length - 1) {
	text.append (StaticStrings.SPACE_CHARACTER);
      }
    }
    return text.toString ();
  }
    
  static private String supercollectionToString (Element command_element) {
    NodeList content_elements = command_element.getElementsByTagName (StaticStrings.COLLECTION_ELEMENT);
    int content_elements_length = content_elements.getLength ();
    if(content_elements_length > 1) {
      StringBuffer text = new StringBuffer (StaticStrings.SUPERCOLLECTION_STR);
      text.append (StaticStrings.TAB_CHARACTER);
      for(int j = 0; j < content_elements_length; j++) {
	Element content_element = (Element) content_elements.item (j);
	text.append (content_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
	if(j < content_elements_length - 1) {
	  text.append (StaticStrings.SPACE_CHARACTER);
	}
      }
      return text.toString ();
    }
    return null;
  }
    
  static private String unknownToString (Element command_element) {
    return XMLTools.getValue (command_element);
  }
    

  /** Parse a collect.cfg into a DOM model representation.
   * note we are ignoring 2.39 compatibility now. */
  static public String parse (File collect_cfg_file, Document document) {
    // hack for pre 2.71 compatibility - we need to add in a
    // build type if there is not one there
    boolean search_types_parsed = false;
    boolean build_types_parsed = false;
    try {
      StringBuffer saved_collect_cfg_string_buffer = new StringBuffer ();
            
      Element collect_cfg_element = document.getDocumentElement ();
      // Read in the file one command at a time.
      InputStream istream = new FileInputStream (collect_cfg_file);
      Reader in_reader = new InputStreamReader (istream, CollectionConfiguration.ENCODING);
      BufferedReader in = new BufferedReader (in_reader);
      String command_str = null;
      while((command_str = in.readLine ()) != null) {
	saved_collect_cfg_string_buffer.append (command_str + "\n");
                
	boolean append_element = true;
	Element command_element = null;
	// A command may be broken over several lines.
	command_str = command_str.trim ();
	boolean eof = false;
	while(!eof && command_str.endsWith (StaticStrings.NEWLINE_CHARACTER)) {
	  String next_line = in.readLine ();
	  if(next_line != null) {
	    next_line = next_line.trim ();
	    if(next_line.length () > 0) {
	      // Remove the new line character
	      command_str = command_str.substring (0, command_str.lastIndexOf (StaticStrings.NEWLINE_CHARACTER));
	      // And append the next line, which due to the test above must be non-zero length
	      command_str = command_str + next_line;
	    }
	    next_line = null;
	  }
	  // If we've reached the end of the file theres nothing more we can do
	  else {
	    eof = true;
	  }
	}
	// If there is still a new line character, then we remove it and hope for the best
	if(command_str.endsWith (StaticStrings.NEWLINE_CHARACTER)) {
	  command_str = command_str.substring (0, command_str.lastIndexOf (StaticStrings.NEWLINE_CHARACTER));
	}
	// Now we've either got a command to parse...
	if(command_str.length () != 0) {
	  // Start trying to figure out what it is
	  //StringTokenizer tokenizer = new StringTokenizer(command_str);
	  // Instead of a standard string tokenizer I'm going to use the new version of CommandTokenizer, which is not only smart enough to correctly notice speech marks and correctly parse them out, but now also takes the input stream so it can rebuild tokens that stretch over several lines.
	  CommandTokenizer tokenizer = new CommandTokenizer (command_str, in);
	  String command_type = tokenizer.nextToken ().toLowerCase ();
	  // Why can't you switch on strings eh? We pass it to the various subparsers who each have a bash at parsing the command. If none can parse the command, an unknown element is created
	  if(command_element == null && command_type.equals (StaticStrings.CLASSIFY_STR)) {
	    command_element = parseClassify (command_str, document); 
	  }
	  if(command_element == null && command_type.equals (StaticStrings.FORMAT_STR)) {
	    command_element = parseFormat (tokenizer, document); // Revised to handle multiple lines
	  }
	  if(command_element == null && (command_type.equals (StaticStrings.INDEX_STR)  || command_type.equals (StaticStrings.COMMENTED_INDEXES_STR))) {
	    command_element = parseIndex (command_str, document);
	  }
	  if(command_element == null && (command_type.equals (StaticStrings.INDEX_DEFAULT_STR) || command_type.equals (StaticStrings.COMMENTED_INDEX_DEFAULT_STR))) {
                        
	    command_element = parseIndexDefault (command_str, document);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.SORT_STR)) {
	    command_element = parseSortfields (command_str, document);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.SORT_DEFAULT_STR) ) {
                        
	    command_element = parseSortfieldDefault (command_str, document);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.LANGUAGES_STR)) {
	    command_element = parseLanguage (command_str, document);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.LANGUAGE_DEFAULT_STR)) {
	    command_element = parseLanguageDefault (command_str, document);
	  }
	  if (command_element == null && command_type.equals (StaticStrings.LANGUAGE_METADATA_STR)) {
	    command_element = parseLanguageMetadata (command_str, document);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.LEVELS_STR)) {
	    command_element = parseIndexOptions (command_str, document, StaticStrings.LEVELS_STR, true);
	  }
	  if (command_element == null && command_type.equals (StaticStrings.COMMENTED_LEVELS_STR)) {
	    command_element = parseIndexOptions (command_str, document, StaticStrings.LEVELS_STR, false);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.LEVEL_DEFAULT_STR)) {
	    command_element = parseIndexOptionDefault (command_str, document, StaticStrings.LEVEL_DEFAULT_STR, true);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.COMMENTED_LEVEL_DEFAULT_STR)) {
	    command_element = parseIndexOptionDefault (command_str, document, StaticStrings.LEVEL_DEFAULT_STR, false);
	  }
	  if (command_element == null && command_type.equals (StaticStrings.INDEXOPTIONS_STR)) {
	    command_element = parseIndexOptions (command_str, document, StaticStrings.INDEXOPTIONS_STR, true);
	  }
	  if (command_element == null && command_type.equals (StaticStrings.COMMENTED_INDEXOPTIONS_STR)) {
	    command_element = parseIndexOptions (command_str, document, StaticStrings.INDEXOPTIONS_STR, false);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.COLLECTIONMETADATA_STR)) {
	    command_element = parseMetadata (tokenizer, document); // Revised to handle multiple lines
	  }
	  if(command_element == null && (command_type.equals (StaticStrings.COLLECTIONMETADATA_PUBLIC_STR) || command_type.equals (StaticStrings.COLLECTIONMETADATA_CREATOR_STR) || command_type.equals (StaticStrings.COLLECTIONMETADATA_MAINTAINER_STR) || command_type.equals (StaticStrings.BUILDTYPE_STR) || command_type.equals (StaticStrings.DATABASETYPE_STR))) {
	    command_element = parseMetadataSpecial (command_str, document);
	    // pre 2.71 hack
	    if (command_type.equals (StaticStrings.BUILDTYPE_STR)) {
	      build_types_parsed = true;
	    }
	  }
	  if(command_element == null && command_type.equals (StaticStrings.PLUGIN_STR)) {
	    command_element = parsePlugin (command_str, document);
	  }
	  // leave here for backwards compatibility
	  if(command_element == null && command_type.equals (StaticStrings.SEARCHTYPE_STR)) {
	    command_element = parseSearchType (command_str, document);
	    // pre 2.71 hack
	    search_types_parsed = true;
                        
	  }
	  if(command_element == null && command_type.equals (StaticStrings.SUBCOLLECTION_STR)) {
	    command_element = parseSubCollection (command_str, document);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.SUBCOLLECTION_DEFAULT_INDEX_STR)) {
	    command_element = parseSubCollectionDefaultIndex (command_str, document);
	  }
	  if(command_element == null && command_type.equals (StaticStrings.SUBCOLLECTION_INDEX_STR)) {
	    command_element = parseSubCollectionIndex (command_str, document);
	  }
	  if(command_element == null && (command_type.equals (StaticStrings.SUPERCOLLECTION_STR) || command_type.equals (StaticStrings.CCS_STR))) {
	    command_element = parseSuperCollection (command_str, document);
	  }
	  // Doesn't match any known type
	  command_type = null;
	  if(command_element == null) {
	    // No-one knows what to do with this command, so we create an Unknown command element
	    command_element = document.createElement (StaticStrings.UNKNOWN_ELEMENT);
	    XMLTools.setValue (command_element, command_str);
	  }
	}
	// Or an empty line to remember for later
	else {
	  command_element = document.createElement (CollectionConfiguration.NEWLINE_ELEMENT);
	}
	// Now command element shouldn't be null so we append it to the collection config DOM, but only if we haven't been told not to add it
	//if(append_element) {
	collect_cfg_element.appendChild (command_element);
	//}
      }
      if (!build_types_parsed) {
	String buildtype_type = BuildTypeManager.BUILD_TYPE_MG;
	if (search_types_parsed) {
	  buildtype_type = BuildTypeManager.BUILD_TYPE_MGPP;
	}
	Element command_element = parseMetadataSpecial (StaticStrings.BUILDTYPE_STR+"  "+buildtype_type, document);
	Node target_node = CollectionConfiguration.findInsertionPoint (command_element);
	if(target_node != null) {
	  collect_cfg_element.insertBefore (command_element, target_node);
	}
	else {
	  collect_cfg_element.appendChild (command_element);
	}
                
      }
      return saved_collect_cfg_string_buffer.toString();
    }
    catch(Exception exception) {
      DebugStream.println ("Error in CollectionConfiguration.parse(java.io.File): " + exception);
      DebugStream.printStackTrace (exception);
    }

    return null;
  }
    
    
  /** Parses arguments from a tokenizer and returns a HashMap of mappings. The tricky bit here is that not all entries in the HashMap are name->value pairs, as some arguments are boolean and are turned on by their presence. Arguments are denoted by a '-' prefix.
   * @param tokenizer a CommandTokenizer based on the unconsumed portion of a command string
   * @return a HashMap containing the arguments parsed
   */
  static public HashMap parseArguments (CommandTokenizer tokenizer) {
    HashMap arguments = new HashMap ();
    String name = null;
    String value = null;
    while(tokenizer.hasMoreTokens () || name != null) {
      // First we retrieve a name if we need one.
      if(name == null) {
	name = tokenizer.nextToken ();
      }
      // Now we attempt to retrieve a value
      if(tokenizer.hasMoreTokens ()) {
	value = tokenizer.nextToken ();
	// Test if the value is actually a name, and if so add the name by itself, then put value into name so that it is parsed correctly during the next loop.
	// The value is not a name if it contains a space character: it's a quoted value
	if (value.startsWith(StaticStrings.MINUS_CHARACTER) && value.indexOf(StaticStrings.SPACE_CHARACTER) == -1) {
	  arguments.put (name, null);
	  name = value;
	}
	// Otherwise we have a typical name->value pair ready to go
	else {
	  arguments.put (name, value);
	  name = null;
	}
      }
      // Otherwise its a binary flag
      else {
	arguments.put (name, null);
	name = null;
      }
    }
    return arguments;
  }
    
  static private Element parseClassify (String command_str, Document document) {
    Element command_element = null;
    try {
      CommandTokenizer tokenizer = new CommandTokenizer (command_str);
      // Check the token count. The token count from a command tokenizer isn't guarenteed to be correct, but it does give the maximum number of available tokens according to the underlying StringTokenizer (some of which may actually be append together by the CommandTokenizer as being a single argument).
      if(tokenizer.countTokens () >= 2) {  // Must support "classify Phind" (no args)
	command_element = document.createElement (StaticStrings.CLASSIFY_ELEMENT);
	// First token is classify
	tokenizer.nextToken ();
	// The next token is the classifier type
	command_element.setAttribute (StaticStrings.TYPE_ATTRIBUTE, tokenizer.nextToken ());
	// Now we parse out the remaining arguments into a hashmapping from name to value
	HashMap arguments = parseArguments (tokenizer);
	// Assign the arguments as Option elements
	Iterator names = arguments.keySet ().iterator ();
	while(names.hasNext ()) {
	  String name = (String) names.next ();
	  String value = (String) arguments.get (name); // Can be null
	  Element option_element = document.createElement (StaticStrings.OPTION_ELEMENT);
	  option_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, name.substring (1));
	  if(value != null) {
	    // Remove any speech marks appended in strings containing whitespace
	    if(value.startsWith (StaticStrings.SPEECH_CHARACTER) && value.endsWith (StaticStrings.SPEECH_CHARACTER)) {
	      value = value.substring (1, value.length () - 1);
	    }
	    XMLTools.setValue (option_element, value);
	  }
	  option_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
	  command_element.appendChild (option_element);
	  option_element = null;
	  name = null;
	  value = null;
	}
	names = null;
	arguments = null;
      }
      tokenizer = null;
    }
    catch(Exception error) {
    }
    return command_element;
  }
    
  static private Element parseFormat (CommandTokenizer tokenizer, Document document) {
    Element command_element = null;
    try {
      command_element = document.createElement (StaticStrings.FORMAT_ELEMENT);
      String name_str = tokenizer.nextToken ();
      String value_str = tokenizer.nextToken ();
      if(name_str != null && value_str != null) {
	command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, name_str);
	// If the value is true or false we add it as an attribute
	if(value_str.equalsIgnoreCase (StaticStrings.TRUE_STR) || value_str.equalsIgnoreCase (StaticStrings.FALSE_STR)) {
	  command_element.setAttribute (StaticStrings.VALUE_ATTRIBUTE, value_str.toLowerCase ());
	}
	// Otherwise it gets added as a text node
	else {
	  // Ready the value str (which can contain all sorts of funky characters) for writing as a DOM value
	  value_str = Codec.transform (value_str, Codec.GREENSTONE_TO_DOM);
	  XMLTools.setValue (command_element, value_str);
	}
      }
      else {
	command_element = null;
      }
      name_str = null;
      value_str = null;
    }
    catch (Exception exception) {
      DebugStream.printStackTrace (exception);
      command_element = null;
    }
    return command_element;
  }
    
  static private Element parseIndex (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      String command = tokenizer.nextToken ();
      command_element = document.createElement (StaticStrings.INDEXES_ELEMENT);
      command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, (command.equals (StaticStrings.INDEX_STR) ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
      command = null;
      if(!tokenizer.hasMoreTokens ()) {
                
	// there are no indexes
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.FALSE_STR);
	command_element.setAttribute (StaticStrings.MGPP_ATTRIBUTE, StaticStrings.FALSE_STR); // for now
	tokenizer = null;
	return command_element;
      }
            
      while(tokenizer.hasMoreTokens ()) {
	Element index_element = document.createElement (StaticStrings.INDEX_ELEMENT);
	String index_str = tokenizer.nextToken ();
	// There are two types of index we have to consider. MG versions use "level:source,source" while MGPP versions use "source,source source" 
	if(index_str.indexOf (StaticStrings.COLON_CHARACTER) != -1) {
	  index_element.setAttribute (StaticStrings.LEVEL_ATTRIBUTE, index_str.substring (0, index_str.indexOf (StaticStrings.COLON_CHARACTER)));
	  index_str = index_str.substring (index_str.indexOf (StaticStrings.COLON_CHARACTER) + 1);
	  command_element.setAttribute (StaticStrings.MGPP_ATTRIBUTE, StaticStrings.FALSE_STR);
	}
	else {
	  command_element.setAttribute (StaticStrings.MGPP_ATTRIBUTE, StaticStrings.TRUE_STR);
	}
	StringTokenizer content_tokenizer = new StringTokenizer (index_str, StaticStrings.COMMA_CHARACTER);
	while(content_tokenizer.hasMoreTokens ()) {
	  Element content_element = document.createElement (StaticStrings.CONTENT_ELEMENT);
	  String content_str = content_tokenizer.nextToken ();
	  // Since the contents of indexes have to be certain keywords, or metadata elements, if the content isn't a keyword and doesn't yet have a namespace, append the extracted metadata namespace.
	  if(content_str.indexOf (StaticStrings.NS_SEP) == -1) {
	    if(content_str.equals (StaticStrings.TEXT_STR) || content_str.equals (StaticStrings.ALLFIELDS_STR) || content_str.equals(StaticStrings.METADATA_STR)) {
	      // Our special strings are OK.
	    }
	    else {
	      content_str = StaticStrings.EXTRACTED_NAMESPACE + content_str;
	    }
	  }
	  content_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, content_str);
	  index_element.appendChild (content_element);
	  content_element = null;
	}
	content_tokenizer = null;
	index_str = null;
	command_element.appendChild (index_element);
	index_element = null;
      }
      tokenizer = null;
    }
    catch (Exception exception) {
      exception.printStackTrace ();
    }
    return command_element;
  }
    
  static private Element parseIndexDefault (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      if(tokenizer.countTokens () >= 2) {
	command_element = document.createElement (StaticStrings.INDEX_DEFAULT_ELEMENT);
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, ((tokenizer.nextToken ()).equals (StaticStrings.INDEX_DEFAULT_STR) ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
	String index_str = tokenizer.nextToken ();
	String level="";
	if (index_str.indexOf (StaticStrings.COLON_CHARACTER) !=-1) {
	  level =  index_str.substring (0, index_str.indexOf (StaticStrings.COLON_CHARACTER));
	}
                
	command_element.setAttribute (StaticStrings.LEVEL_ATTRIBUTE,level);
                
	String content_str = index_str;
                
	if (index_str.indexOf (StaticStrings.COLON_CHARACTER) !=-1) {
	  content_str = index_str.substring (index_str.indexOf (StaticStrings.COLON_CHARACTER) + 1);
	}
                
	StringTokenizer content_tokenizer = new StringTokenizer (content_str, StaticStrings.COMMA_CHARACTER);
	while(content_tokenizer.hasMoreTokens ()) {
	  Element content_element = document.createElement (StaticStrings.CONTENT_ELEMENT);
	  content_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, content_tokenizer.nextToken ());
	  command_element.appendChild (content_element);
	  content_element = null;
	}
	content_tokenizer = null;
	content_str = null;
	content_str = null;
	index_str = null;
      }
      tokenizer = null;
    }
    catch (Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseSortfields (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      String command = tokenizer.nextToken ();
      command_element = document.createElement (StaticStrings.SORTS_ELEMENT);
      command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE,  StaticStrings.TRUE_STR);
      command = null;
      if(!tokenizer.hasMoreTokens ()) {
                
	// there are no sortfields
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.FALSE_STR);
	tokenizer = null;
	return command_element;
      }
            
      while(tokenizer.hasMoreTokens ()) {
	Element index_element = document.createElement (StaticStrings.SORT_ELEMENT);
	String index_str = tokenizer.nextToken ();
	StringTokenizer content_tokenizer = new StringTokenizer (index_str, StaticStrings.COMMA_CHARACTER);
	while(content_tokenizer.hasMoreTokens ()) {
	  Element content_element = document.createElement (StaticStrings.CONTENT_ELEMENT);
	  String content_str = content_tokenizer.nextToken ();
	  // Since the contents of indexes have to be certain keywords, or metadata elements, if the content isn't a keyword and doesn't yet have a namespace, append the extracted metadata namespace.
	  if(content_str.indexOf (StaticStrings.NS_SEP) == -1) {
	    if(content_str.equals (StaticStrings.RANK_STR) || content_str.equals (StaticStrings.NONE_STR) ) {
	      // Our special strings are OK.
	    }
	    else {
	      content_str = StaticStrings.EXTRACTED_NAMESPACE + content_str;
	    }
	  }
	  content_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, content_str);
	  index_element.appendChild (content_element);
	  content_element = null;
	}
	content_tokenizer = null;
	index_str = null;
	command_element.appendChild (index_element);
	index_element = null;
      }
      tokenizer = null;
    }
    catch (Exception exception) {
      exception.printStackTrace ();
    }
    return command_element;
  }

  static private Element parseSortfieldDefault (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      if(tokenizer.countTokens () >= 2) {
	command_element = document.createElement (StaticStrings.SORT_DEFAULT_ELEMENT);
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, ((tokenizer.nextToken ()).equals (StaticStrings.SORT_DEFAULT_STR) ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
	String index_str = tokenizer.nextToken ();
                
	String content_str = index_str;
                
                
	StringTokenizer content_tokenizer = new StringTokenizer (content_str, StaticStrings.COMMA_CHARACTER);
	while(content_tokenizer.hasMoreTokens ()) {
	  Element content_element = document.createElement (StaticStrings.CONTENT_ELEMENT);
	  content_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, content_tokenizer.nextToken ());
	  command_element.appendChild (content_element);
	  content_element = null;
	}
	content_tokenizer = null;
	content_str = null;
	content_str = null;
	index_str = null;
      }
      tokenizer = null;
    }
    catch (Exception exception) {
    }
    return command_element;
  }

  static private Element parseLanguage (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      tokenizer.nextToken ();
      if(tokenizer.hasMoreTokens ()) {
	command_element = document.createElement (StaticStrings.LANGUAGES_ELEMENT);
	while(tokenizer.hasMoreTokens ()) {
	  Element language_element = document.createElement (StaticStrings.LANGUAGE_ELEMENT);
	  language_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, tokenizer.nextToken ());
	  command_element.appendChild (language_element);
	  language_element = null;
	}
      }
      tokenizer = null;
    }
    catch (Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseLanguageDefault (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      if(tokenizer.countTokens () >= 2) {
	command_element = document.createElement (StaticStrings.LANGUAGE_DEFAULT_ELEMENT);
	tokenizer.nextToken ();
	String default_language_str = tokenizer.nextToken ();
	command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, default_language_str);
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
	default_language_str = null;
      }
      tokenizer = null;
    }
    catch (Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseLanguageMetadata (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      if(tokenizer.countTokens () >= 2) {
	command_element = document.createElement (StaticStrings.LANGUAGE_METADATA_ELEMENT);
	tokenizer.nextToken ();
	String language_metadata_str = tokenizer.nextToken ();
	if (language_metadata_str.indexOf (StaticStrings.NS_SEP) == -1) {
	  language_metadata_str = StaticStrings.EXTRACTED_NAMESPACE + language_metadata_str;
	}
	command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, language_metadata_str);
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
	language_metadata_str = null;
      }
      tokenizer = null;
            
    }
    catch (Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseIndexOptions (String command_str, Document document, String type, boolean assigned) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      // First token is command type
      String command = tokenizer.nextToken ();
      if(tokenizer.hasMoreTokens ()) {
	command_element = document.createElement (StaticStrings.INDEXOPTIONS_ELEMENT);
	command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE,type);
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, (assigned ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
	while(tokenizer.hasMoreTokens ()) {
	  Element option_element = document.createElement (StaticStrings.INDEXOPTION_ELEMENT);
	  option_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, tokenizer.nextToken ());
	  command_element.appendChild (option_element);
	  option_element = null;
	}
      }
      command = null;
    }
    catch(Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseIndexOptionDefault (String command_str, Document document, String type, boolean assigned) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      // First token is command type
      String command = tokenizer.nextToken ();
      if(tokenizer.hasMoreTokens ()) {
	command_element = document.createElement (StaticStrings.INDEXOPTION_DEFAULT_ELEMENT);
	command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, (assigned ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR)); // is it commented out or not?
	command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, type);
	command_element.setAttribute (StaticStrings.VALUE_ATTRIBUTE, tokenizer.nextToken ());
      }
            
      tokenizer = null;
    }
    catch (Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseMetadata (CommandTokenizer tokenizer, Document document) {
    Element command_element = null;
    boolean is_search_meta = false;
    try {
     
      String name_str = tokenizer.nextToken ();
      if (name_str.startsWith(StaticStrings.DOT_CHARACTER)) {
	  is_search_meta = true;
	  name_str = name_str.substring(1); // remove the dot
      }
      String value_str = tokenizer.nextToken ();
      if(name_str != null && value_str != null) {
	String language_str = Configuration.getLanguage ();
	// Check if the value string is actually a language string
	if(value_str.startsWith (StaticStrings.LBRACKET_CHARACTER) && value_str.endsWith (StaticStrings.RBRACKET_CHARACTER)) {
	  language_str = value_str.substring (value_str.indexOf (StaticStrings.METADATA_LANGUAGE_SPECIFIER) + 2, value_str.length () - 1);
	  value_str = tokenizer.nextToken ();
	}
	if(value_str != null) {
	  // Ready the value str (which can contain all sorts of funky characters) for writing as a DOM value
	    if (is_search_meta) {
		command_element = document.createElement (StaticStrings.SEARCHMETADATA_ELEMENT);
		command_element.setAttribute(StaticStrings.TYPE_ATTRIBUTE, SearchMeta.TYPE_SEARCH);
	    } else {
		command_element = document.createElement (StaticStrings.COLLECTIONMETADATA_ELEMENT);
	    }
	  value_str = Codec.transform (value_str, Codec.GREENSTONE_TO_DOM);
	  command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, name_str);
	  command_element.setAttribute (StaticStrings.LANGUAGE_ATTRIBUTE, language_str);
	  command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
	  XMLTools.setValue (command_element, value_str);
	}
	else {
	  command_element = null;
	}
	language_str = null;
      }
      else {
	command_element = null;
      }
      name_str = null;
      value_str = null;
    }
    catch (Exception exception) {
      DebugStream.printStackTrace (exception);
      command_element = null;
    }
    return command_element;
  }
    
  static private Element parseMetadataSpecial (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      if(tokenizer.countTokens () >= 2) {
	String name_str = tokenizer.nextToken ();
	String value_str = tokenizer.nextToken ();
	if (name_str.equals (StaticStrings.COLLECTIONMETADATA_CREATOR_STR)) {
	  command_element = document.createElement (StaticStrings.COLLECTIONMETADATA_CREATOR_ELEMENT);
	}
	else if(name_str.equals (StaticStrings.COLLECTIONMETADATA_MAINTAINER_STR)) {
	  command_element = document.createElement (StaticStrings.COLLECTIONMETADATA_MAINTAINER_ELEMENT);
	}
	else if(name_str.equals (StaticStrings.COLLECTIONMETADATA_PUBLIC_STR)) {
	  command_element = document.createElement (StaticStrings.COLLECTIONMETADATA_PUBLIC_ELEMENT);
	}
	else if (name_str.equals (StaticStrings.BUILDTYPE_STR)) {
	  command_element = document.createElement (StaticStrings.BUILDTYPE_ELEMENT);
	}
	else if (name_str.equals (StaticStrings.DATABASETYPE_STR)) {
	  command_element = document.createElement (StaticStrings.DATABASETYPE_ELEMENT);
	}
	if(command_element != null) {
	  command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, name_str);
	  command_element.setAttribute (StaticStrings.LANGUAGE_ATTRIBUTE, StaticStrings.ENGLISH_LANGUAGE_STR);
	  command_element.setAttribute (StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
	  command_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
	  if(value_str.startsWith (StaticStrings.SPEECH_CHARACTER) && value_str.endsWith (StaticStrings.SPEECH_CHARACTER)) {
	    value_str = value_str.substring (1, value_str.length () - 1);
	  }
	  XMLTools.setValue (command_element, value_str);
	}
	value_str = null;
	name_str = null;
      }
      tokenizer = null;
    }
    catch (Exception exception) {
    }
    return command_element;
  }
    
  static private Element parsePlugin (String command_str, Document document) {
    Element command_element = null;
    try {
      CommandTokenizer tokenizer = new CommandTokenizer (command_str);
      // Check the token count. The token count from a command tokenizer isn't guarenteed to be correct, but it does give the maximum number of available tokens according to the underlying StringTokenizer (some of which may actually be append together by the CommandTokenizer as being a single argument).
      if(tokenizer.countTokens () >= 2) {
	command_element = document.createElement (StaticStrings.PLUGIN_ELEMENT);
	// First token is plugin
	tokenizer.nextToken ();
	// The next token is the type
	String type = tokenizer.nextToken ();
	type = Utility.ensureNewPluginName(type);
	command_element.setAttribute (StaticStrings.TYPE_ATTRIBUTE, type);
	// Now we parse out the remaining arguments into a hashmapping from name to value
	HashMap arguments = parseArguments (tokenizer);
	// also watch out for the deprecated -use_metadata_files option to RecPlug and remove it
	Iterator names = arguments.keySet ().iterator ();
	while(names.hasNext ()) {
	  String name = (String) names.next ();
	  String value = (String) arguments.get (name); // Can be null
                    
	  if(type.equals (StaticStrings.RECPLUG_STR) && name.substring (1).equals (StaticStrings.USE_METADATA_FILES_ARGUMENT)) {
	    continue; // ignore this option
	  }
	  Element option_element = document.createElement (StaticStrings.OPTION_ELEMENT);
	  option_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, name.substring (1));
	  option_element.setAttribute (StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
	  if(value != null) {
	    // Remove any speech marks appended in strings containing whitespace
	    if(value.startsWith (StaticStrings.SPEECH_CHARACTER) && value.endsWith (StaticStrings.SPEECH_CHARACTER)) {
	      value = value.substring (1, value.length () - 1);
	    }
	    XMLTools.setValue (option_element, value);
	  }
	  command_element.appendChild (option_element);
	  option_element = null;
	  name = null;
	  value = null;
	}
                
	type = null;
	names = null;
	arguments = null;
      }
      tokenizer = null;
    }
    catch(Exception exception) {
      // This catch clause had been left empty.  If this is deliberate then
      // we should have a comment here explaining why there is no need to
      // print anything out.  Am assuming this is mistake for now, and
      // have added in a call to printStackTrace()
      System.err.println("Malformed plugin statement");
      exception.printStackTrace();
    }
    return command_element;
  }
    
  /* search types are now handled as formats - leave this here to convert in case we have an old config file */
  static private Element parseSearchType (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      // First token is command type (searchtype)
      tokenizer.nextToken ();
      if(tokenizer.hasMoreTokens ()) {
	command_element = document.createElement (StaticStrings.FORMAT_ELEMENT);
	command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, "SearchTypes");
	String value = tokenizer.nextToken ();
	while(tokenizer.hasMoreTokens ()) {
	  value += ","+tokenizer.nextToken ();
	}
	value = Codec.transform (value, Codec.GREENSTONE_TO_DOM);
	XMLTools.setValue (command_element, value);
      }
    }
    catch(Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseSubCollection (String command_str, Document document) {
    Element command_element = null;
    try {
      CommandTokenizer tokenizer = new CommandTokenizer (command_str);
      if(tokenizer.countTokens () >= 3) {
	command_element = document.createElement (StaticStrings.SUBCOLLECTION_ELEMENT);
	// First token is command type
	tokenizer.nextToken ();
	// Then subcollection identifier
	command_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, tokenizer.nextToken ());
	// Then finally the pattern used to build the subcollection partition
	String full_pattern_str = tokenizer.nextToken ();
	// Set inclusion/exclusion flag and remove any exclamation mark
	boolean exclusion = full_pattern_str.startsWith (StaticStrings.EXCLAMATION_CHARACTER);
	if (exclusion) {
	  full_pattern_str = full_pattern_str.substring (1, full_pattern_str.length ());
	  command_element.setAttribute (StaticStrings.TYPE_ATTRIBUTE, StaticStrings.EXCLUDE_STR);
	}
	else {
	  command_element.setAttribute (StaticStrings.TYPE_ATTRIBUTE, StaticStrings.INCLUDE_STR);
	}

	// Let's make sure it is a valid Greenstone configuration line
	String[] results = full_pattern_str.split("\\" + StaticStrings.SEPARATOR_CHARACTER, 3);

	if (results.length >= 2) {
	  String content_str = results[0];
	  // Since the contents of indexes have to be certain keywords, or metadata elements, if the content isn't a keyword and doesn't yet have a namespace, append the extracted metadata namespace.
	  if (!content_str.equals (StaticStrings.FILENAME_STR) && content_str.indexOf (StaticStrings.NS_SEP) == -1) {
	    content_str = StaticStrings.EXTRACTED_NAMESPACE + content_str;
	  }
	  command_element.setAttribute (StaticStrings.CONTENT_ATTRIBUTE, content_str);
	  XMLTools.setValue (command_element, results[1]);
	  if (results.length >= 3) {
	    command_element.setAttribute (StaticStrings.OPTIONS_ATTRIBUTE, results[2]);
	  }
	}
      }
    }
    catch(Exception exception) {
      exception.printStackTrace ();
    }
    return command_element;
  }
    
  static private Element parseSubCollectionDefaultIndex (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      if(tokenizer.countTokens () == 2) {
	command_element = document.createElement (StaticStrings.SUBCOLLECTION_DEFAULT_INDEX_ELEMENT);
	tokenizer.nextToken ();
	//command_element.setAttribute(CONTENT_ATTRIBUTE, tokenizer.nextToken());
	String content_str = tokenizer.nextToken ();
	StringTokenizer content_tokenizer = new StringTokenizer (content_str, StaticStrings.COMMA_CHARACTER);
	while(content_tokenizer.hasMoreTokens ()) {
	  Element content_element = document.createElement (StaticStrings.CONTENT_ELEMENT);
	  content_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, content_tokenizer.nextToken ());
	  command_element.appendChild (content_element);
	  content_element = null;
	}
	content_tokenizer = null;
	content_str = null;
      }
      tokenizer = null;
    }
    catch(Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseSubCollectionIndex (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      tokenizer.nextToken ();
      if(tokenizer.hasMoreTokens ()) {
	command_element = document.createElement (StaticStrings.SUBCOLLECTION_INDEXES_ELEMENT);
      }
      while(tokenizer.hasMoreTokens ()) {
	Element subcollectionindex_element = document.createElement (StaticStrings.INDEX_ELEMENT);
	//command_element.setAttribute(CONTENT_ATTRIBUTE, tokenizer.nextToken());
	String content_str = tokenizer.nextToken ();
	StringTokenizer content_tokenizer = new StringTokenizer (content_str, StaticStrings.COMMA_CHARACTER);
	while(content_tokenizer.hasMoreTokens ()) {
	  Element content_element = document.createElement (StaticStrings.CONTENT_ELEMENT);
	  content_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, content_tokenizer.nextToken ());
	  subcollectionindex_element.appendChild (content_element);
	  content_element = null;
	}
	content_tokenizer = null;
	content_str = null;
	command_element.appendChild (subcollectionindex_element);
	subcollectionindex_element = null;
      }
      tokenizer = null;
    }
    catch (Exception exception) {
    }
    return command_element;
  }
    
  static private Element parseSuperCollection (String command_str, Document document) {
    Element command_element = null;
    try {
      StringTokenizer tokenizer = new StringTokenizer (command_str);
      if(tokenizer.countTokens () >= 3) {
	command_element = document.createElement (StaticStrings.SUPERCOLLECTION_ELEMENT);
	tokenizer.nextToken ();
	while(tokenizer.hasMoreTokens ()) {
	  Element collection_element = document.createElement (StaticStrings.COLLECTION_ELEMENT);
	  collection_element.setAttribute (StaticStrings.NAME_ATTRIBUTE, tokenizer.nextToken ());
	  command_element.appendChild (collection_element);
	  collection_element = null;
	}
      }
      tokenizer = null;
    }
    catch(Exception exception) {
    }
    return command_element;
  }
    
  static private String pluginToString (Element command_element) {
    if(command_element.getAttribute (StaticStrings.SEPARATOR_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) { 
      return "";
    }
    StringBuffer text = new StringBuffer (StaticStrings.PLUGIN_STR);
    text.append (StaticStrings.TAB_CHARACTER);
    text.append (command_element.getAttribute (StaticStrings.TYPE_ATTRIBUTE));
    // Retrieve, and output, the arguments
    NodeList option_elements = command_element.getElementsByTagName (StaticStrings.OPTION_ELEMENT);
    int option_elements_length = option_elements.getLength ();
    if(option_elements_length > 0) {
      for(int j = 0; j < option_elements_length; j++) {
	Element option_element = (Element) option_elements.item (j);
	if(option_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) {
	  text.append (StaticStrings.SPACE_CHARACTER);
	  text.append (StaticStrings.MINUS_CHARACTER);
	  text.append (option_element.getAttribute (StaticStrings.NAME_ATTRIBUTE));
	  String value_str = XMLTools.getValue (option_element);
	  if (value_str.length () > 0) {
	    text.append (StaticStrings.SPACE_CHARACTER);
	    if(value_str.indexOf (StaticStrings.SPACE_CHARACTER) != -1) {
	      // enclose in quotes
	      text.append(StaticStrings.SPEECH_CHARACTER);
	      text.append(value_str);
	      text.append(StaticStrings.SPEECH_CHARACTER);
	    } else {
		    
	      text.append(value_str);
	    }
	  }
	      
	  value_str = null;
	}
	option_element = null;
      }
    }
    option_elements = null;
	
    return text.toString ();
  }

  static public String generateStringVersion(Document document) {

    StringBuffer collect_cfg_string_buffer = new StringBuffer ();
    NodeList command_elements = document.getDocumentElement ().getChildNodes ();
    boolean just_wrote_blank_line = false;  // Prevent two or more blank lines in a row
    for (int i = 0; i < command_elements.getLength (); i++) {
      Node command_node = command_elements.item (i);
      if (!(command_node instanceof Element)) {
	// We're only interested in Elements
	continue;
      }
      Element command_element = (Element) command_node;
            
      // Handle NewLine elements (blank lines)
      if (command_element.getNodeName ().equals (CollectionConfiguration.NEWLINE_ELEMENT) && !just_wrote_blank_line) {
	collect_cfg_string_buffer.append ("\n");
	just_wrote_blank_line = true;
      }
            
      // Anything else we write to file, but only if it has been assigned, except for index and level commands
      // (which just get commented out if unassigned -- a side effect of MG & MGPP compatibility)
      else if (!command_element.getAttribute (StaticStrings.ASSIGNED_ATTRIBUTE).equals (StaticStrings.FALSE_STR) || command_element.getNodeName ().equals (StaticStrings.INDEXES_ELEMENT) || command_element.getNodeName ().equals (StaticStrings.INDEX_DEFAULT_ELEMENT) || command_element.getNodeName ().equals (StaticStrings.INDEXOPTIONS_ELEMENT) || command_element.getNodeName ().equals (StaticStrings.INDEXOPTION_DEFAULT_ELEMENT)) {
	String command = toString(command_element);
                
	if (command != null && command.length ()> 0 ) {
	  collect_cfg_string_buffer.append (command + "\n");
	  just_wrote_blank_line = false;
	}
      }
    }
        
    return collect_cfg_string_buffer.toString ();
  }

   
}
