Index: trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/AZListClassifier.java
===================================================================
--- trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/AZListClassifier.java	(revision 6496)
+++ trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/AZListClassifier.java	(revision 6496)
@@ -0,0 +1,321 @@
+package org.greenstone.gsdl3.gs3build.classifier;
+
+import java.io.*;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.XMLReaderFactory;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.greenstone.gsdl3.gs3build.doctypes.DocumentID;
+import org.greenstone.gsdl3.gs3build.doctypes.DocumentInterface;
+import org.greenstone.gsdl3.gs3build.util.XMLTools;
+import org.greenstone.gsdl3.gs3build.util.GS3SQLConnection;
+import org.greenstone.gsdl3.gs3build.database.*;
+import org.greenstone.gsdl3.gs3build.util.MultiMap;
+
+public class AZListClassifier implements ClassifierInterface
+{
+  GS3SQLConnection database;
+  MultiMap         alphaMap;
+  List             fields;
+
+  class AZDocumentItem
+  { public DocumentID documentId;
+    public String     title;
+
+    public AZDocumentItem(DocumentID documentId, String title)
+    { this.documentId = documentId;
+      this.title = title;
+    }
+
+    public String getTitle()
+    { return this.title;
+    }
+
+    public DocumentID getID()
+    { return this.documentId;
+    }
+
+    public String toString()
+    { return this.title;
+    }
+  }
+
+  private void sortList(String key)
+  { List list = this.alphaMap.getAll(key);
+    if (list.size() > 1) {
+      int first, last;
+
+      first = 0;
+      last = list.size() - 1;
+      while (first != last)
+      { int at = (first + last) / 2;
+
+        if (list.get(at).toString().compareTo(list.get(list.size() - 1).toString()) > 0) {
+	  last = at;
+        }
+	else {
+	  first = at + 1;
+	}
+      }
+
+      Object newObject = list.get(list.size()-1);
+      last = list.size() - 1;
+      while (last > first) {
+	list.set(last, list.get(last-1));
+	last --;
+      }
+      list.set(first, newObject);
+    }
+  }
+
+  public AZListClassifier(List fields)
+  { this.fields = fields;
+    this.alphaMap = new MultiMap();
+    //  System.out.println("AZList :"+fields.size());
+  }
+
+  public AZListClassifier()
+  { this.fields = new ArrayList();
+  }
+
+  public void configure(List parameters)
+  {
+    Iterator iterator = parameters.iterator();
+    while (iterator.hasNext()) {
+      String param = iterator.next().toString();
+      if (param.equals("-metadata")) {
+	if (iterator.hasNext()) {
+	  String field = iterator.next().toString();
+	  if (field != null && field.length() > 0) {
+	    this.fields.add(field);
+	  }
+	}
+      }
+    }
+  }
+
+  public void setDatabase(GS3SQLConnection connection)
+  { this.database = connection;
+  }
+
+  public boolean classifyDocument(DocumentID documentID, DocumentInterface document)
+  { // get the metadata item from the document
+    Iterator thisField = this.fields.iterator();
+
+    // for every field listed as being a scannable field...
+    while (thisField.hasNext())
+    { String fieldName = thisField.next().toString();
+			
+      // ...get the values for that field...
+      List values = document.getDocumentMetadataItem(fieldName);
+
+      if (values == null)
+      {	continue;
+      }
+
+      // ...and send them to the classifier
+      Iterator thisValue = values.iterator();
+      while (thisValue.hasNext()) 
+      { String value = thisValue.next().toString();
+
+	String classText = null;
+
+	int c = 0;
+	while (c < value.length() && classText == null)
+	{ if (Character.isDigit(value.charAt(c))) 
+	  { classText = "0-9";
+	  }
+	  else if (Character.isLetter(value.charAt(c))) 
+	  { classText = value.substring(c, c+1).toLowerCase();
+	  }
+  	  c ++;
+	}
+
+	if (classText != null) {
+	    //	  System.out.println(classText + " " + documentID);
+	  this.alphaMap.put(classText, new AZDocumentItem(documentID, value));
+	  this.sortList(classText);
+	}
+      }
+    }
+
+    return true;    
+  }
+
+  public void startClassifierPass(int pass)
+  {
+  }
+
+  public int getClassifierPasses()
+  { return 1;
+  }
+
+  public void endClassifierPass(int pass)
+  {
+  }
+
+  public void completeClassification()
+  {
+    this.writeSQL(this.database);
+  }
+
+  public int writeSQLClassifyNode(GS3SQLConnection connection, String parentId, String label, String name, String description)
+  { GS3SQLAction action;
+    GS3SQLSelect select;
+    GS3SQLInsert insert;
+  
+    int classifyRef;
+
+    // check for existing node
+    select = new GS3SQLSelect("classifiers");
+    select.addField("ClassifyRef");
+    GS3SQLWhereItem whereItem = new GS3SQLWhereItem("ClassifyID", "=", label);
+    GS3SQLWhere where = new GS3SQLWhere(whereItem);
+    select.setWhere(where);
+
+    connection.execute(select.toString());
+
+    try {
+      ResultSet results = connection.getResultSet();
+      if (results != null && results.first()) {
+	GS3SQLUpdate update = new GS3SQLUpdate("classifiers");
+	update.setWhere(where);
+	action = update;
+ 
+	classifyRef = results.getInt("ClassifyRef");
+      }
+      else {
+	insert = new GS3SQLInsert("classifiers");
+
+	if (parentId.length() > 0) {
+	  insert.addValue("ParentID", parentId);
+	}
+	else {
+	  insert.addValue("ParentID", label);
+	}
+
+	action = insert;
+      }
+      action.addValue("ClassifyID", label);
+      action.addValue("Name", name);
+      action.addValue("Description", description);
+
+      connection.execute(action.toString());
+      classifyRef = -1;
+    }
+    catch (SQLException sqlEx) {
+      System.err.println(sqlEx);
+      return -1;
+    }
+
+    // get the ClassifyRef if we don't already have it (have done a
+    // insert action above)...
+    if (classifyRef == -1) {
+      connection.execute(select.toString());
+      
+      try {
+	ResultSet results = connection.getResultSet();
+	if (results == null || !results.first()) {
+	  return -1;
+	}
+	
+	classifyRef = results.getInt("ClassifyRef");
+      }
+      catch (SQLException sqlEx) {
+	System.err.println(sqlEx);
+	return -1;
+      }
+    }
+
+    return classifyRef;
+  }
+
+  public boolean writeSQL(GS3SQLConnection connection)
+  { GS3SQLAction action;
+    GS3SQLSelect select;
+    GS3SQLInsert insert;
+  
+    String prefix = "CLAZ"+this.fields.get(0).toString();
+
+    int parentClassify = this.writeSQLClassifyNode(connection, "", prefix, "", "");
+
+    if (parentClassify < 0) 
+    { return false;
+    }
+
+    List children;
+    Iterator keys;
+    
+    // TODO: cope with change rather than create from scratch...
+    
+    keys = this.alphaMap.keySet().iterator();
+    while (keys.hasNext()) {
+      Object key = keys.next();
+
+      List childDocs = (List) this.alphaMap.getAll(key);
+
+      if (childDocs != null) {
+	String className = prefix + "." + key.toString();
+
+	int classifyRef = this.writeSQLClassifyNode(connection, prefix, className, "", "");
+
+	// note the child documents...
+        Iterator iterator = childDocs.iterator();
+	int order = 1;
+	while (iterator.hasNext()) {
+	  AZDocumentItem documentItem = (AZDocumentItem) iterator.next();
+	  DocumentID docId = documentItem.getID();
+
+	  insert = new GS3SQLInsert("classdocuments");
+	  insert.addValue("ClassifyRef", Integer.toString(classifyRef), GS3SQLField.INTEGER_TYPE);
+	  insert.addValue("DocID", docId.toString());
+	  insert.addValue("DocOrder", Integer.toString(order), GS3SQLField.INTEGER_TYPE);
+
+	  connection.execute(insert.toString());
+
+	  order ++;
+	}
+      }
+    }
+
+    /*
+    else {
+      // TODO: clear 'dead' child classifications
+
+      // delete child documents
+      GS3SQLDelete delete = new GS3SQLDelete("classdocuments");
+      delete.setWhere(where);
+
+      connection.execute(delete.toString());
+    }
+
+    // post the child nodes...
+    Iterator iterator = this.childNodes.iterator();
+    while (iterator.hasNext()) {
+      AbstractHierarchyNode childNode = (AbstractHierarchyNode) iterator.next();
+
+      if (!childNode.writeSQL(connection)) {
+	return false;
+      }
+    }
+    */
+
+    return true;
+  }
+}
+
+
+
+
+
Index: trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/AbstractHierarchyNode.java
===================================================================
--- trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/AbstractHierarchyNode.java	(revision 6495)
+++ trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/AbstractHierarchyNode.java	(revision 6496)
@@ -29,4 +29,22 @@
   AbstractHierarchyNode parent; // the parent of the node
 
+  class AbstractHierarchyDocument
+  { DocumentID documentId;
+    String     sortKey;
+
+    public AbstractHierarchyDocument(DocumentID documentId, String sortKey)
+    { this.documentId = documentId;
+      this.sortKey    = sortKey;
+    }
+
+    public DocumentID getID()
+    { return this.documentId;
+    }
+
+    public String toString()
+    { return this.sortKey;
+    }
+  } 
+
   /**
    *  Simple node
@@ -53,4 +71,5 @@
     this.matches    = new ArrayList();
     this.matches.add(this.id);
+    this.matches.add(this.name);
   }
     
@@ -88,6 +107,35 @@
   }
 
-  public void addDocument(DocumentID document)
-  { this.childDocs.add(document);
+  public void addDocument(DocumentID document, String sortKey)
+  { this.childDocs.add(new AbstractHierarchyDocument(document, sortKey));
+
+    if (sortKey != null) {
+      int first = 0;
+      int last  = this.childDocs.size() - 1;
+
+      while (first != last) {
+	int at = (first + last) / 2;
+	
+	if (this.childDocs.get(at) == null) {
+	  last = at;
+	  continue;
+	}
+
+	if (this.childDocs.get(at).toString().compareTo(sortKey) > 0) {
+	  last = at;
+	}
+	else {
+	  first = at + 1;
+	}
+      }
+
+      Object newItem = this.childDocs.get(this.childDocs.size() - 1);
+      last = this.childDocs.size() - 1;
+      while (last > first) {
+	this.childDocs.set(last, this.childDocs.get(last-1));
+	last --;
+      }
+      this.childDocs.set(first, newItem);
+    }
   }
 
@@ -185,5 +233,5 @@
    *         classifications that it fell within.
    */
-  abstract public void getClassifications(DocumentID documentID, List values, 
+  abstract public void getClassifications(DocumentID documentID, List values, String sortKey,
 					  ClassifierObserverInterface observer);
 
@@ -280,13 +328,21 @@
     // note the child documents...
     iterator = this.childDocs.iterator();
+    int order = 1;
     while (iterator.hasNext()) {
-      DocumentID docId = (DocumentID) iterator.next();
+      AbstractHierarchyDocument hierarchyDoc = (AbstractHierarchyDocument) iterator.next();
+      DocumentID docId = hierarchyDoc.getID();
+
       insert = new GS3SQLInsert("classdocuments");
       insert.addValue("ClassifyRef", Integer.toString(classifyRef), GS3SQLField.INTEGER_TYPE);
       insert.addValue("DocID", docId.toString());
+      insert.addValue("DocOrder", Integer.toString(order), GS3SQLField.INTEGER_TYPE);
 
       connection.execute(insert.toString());
+
+      order ++;
     }
     return true;
   }
 }
+
+
Index: trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/ClassifierManager.java
===================================================================
--- trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/ClassifierManager.java	(revision 6495)
+++ trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/ClassifierManager.java	(revision 6496)
@@ -82,4 +82,5 @@
 	    
 	    if (document.isModified()) {
+	      //	      System.out.println("Writing document " + document.getID());
 	      this.documents.modifiedDocument(document);
 	    }
Index: trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/GS2HierarchyClassifier.java
===================================================================
--- trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/GS2HierarchyClassifier.java	(revision 6495)
+++ trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/GS2HierarchyClassifier.java	(revision 6496)
@@ -33,5 +33,6 @@
     public void recordClassification(String label)
     { this.document.addDocumentMetadata("gsdl3", "classified", label);
-      System.out.println("Assigned document " + this.document.getID().toString() + " to " + label);
+      this.document.setModified(true);
+      //      System.out.println("Assigned document " + this.document.getID().toString() + " to " + label);
     }
 
@@ -65,5 +66,5 @@
      *         classifications that it fell within.
      */
-    public void getClassifications(DocumentID documentID, List values, 
+    public void getClassifications(DocumentID documentID, List values, String sortKey,
 				   ClassifierObserverInterface observer)
     { // Go through the list of values, and if this node matches, record
@@ -73,6 +74,6 @@
       while (valueList.hasNext())
       {	if (this.isMatch(valueList.next().toString()))
-	{ observer.recordClassification(this.name);
-	  this.addDocument(documentID);
+	{ observer.recordClassification(this.id);
+	  this.addDocument(documentID, sortKey);
 	}
       }
@@ -82,5 +83,5 @@
 
       while (childList.hasNext())
-      { ((GS2HierarchyNode) childList.next()).getClassifications(documentID, values, observer);
+      { ((GS2HierarchyNode) childList.next()).getClassifications(documentID, values, sortKey, observer);
       }
     }
@@ -238,5 +239,5 @@
     while (thisField.hasNext())
     { String fieldName = thisField.next().toString();
-			
+
       // ...get the values for that field...
       List values = document.getDocumentMetadataItem(fieldName);
@@ -253,6 +254,16 @@
       }
 
+      // get the sort key for the metadata item if possible
+      String sortKey = null;
+
+      if (this.sortBy != null) {
+	List sortKeys = document.getDocumentMetadataItem(this.sortBy);
+	if (sortKeys != null && sortKeys.size() > 0) {
+	  sortKey = sortKeys.get(0).toString();
+	}
+      }
+
       // ...and send them to the classifier
-      this.hierarchy.getClassifications(documentID, values, classifyObserver);
+      this.hierarchy.getClassifications(documentID, values, sortKey, classifyObserver);
     }
     return true;
Index: trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/HierarchyClassifier.java
===================================================================
--- trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/HierarchyClassifier.java	(revision 6495)
+++ trunk/gsdl3/src/java/org/greenstone/gsdl3/gs3build/classifier/HierarchyClassifier.java	(revision 6496)
@@ -21,5 +21,5 @@
 public class HierarchyClassifier implements ClassifierInterface
 { 
-  class HierarchyClassifierObserver
+  class HierarchyClassifierObserver implements ClassifierObserverInterface
   { List              classifications;
     DocumentInterface document;
@@ -38,78 +38,14 @@
   }
 
-  class HierarchyNode
-  { String descriptor;    // the textual descriptor used on screen or long-hand
-    String path;          // the index number, letter assignment or other item
-                          //   used to identify the position of the item in the
-                          //   hierarchy
-    String name;          // an identifier used by the GLI for maintenance
-                          //   purposes; this plays no active role in the
-                          //   rebuilding process (at the moment)
-    List childNodes;      // the child classification nodes of this node
-    List childDocs;       // the child documents of this node
-    List matches;         // the other metadata values that may be matched
-                          //   against the classifier
-    HierarchyNode parent; // the parent of the node
-
-    /**
+  class HierarchyNode extends AbstractHierarchyNode
+  { /**
      *  Simple node
      */
     public HierarchyNode()
-    { this.descriptor = null;
-      this.name       = null;
-      this.path       = null;
-      this.childNodes = new ArrayList();
-      this.childDocs  = new ArrayList();
-      this.parent     = null;
+    { super();
     }
     
-    public void addChild(HierarchyNode child)
-    { this.childNodes.add(child);
-      child.setParent(this);
-    }
-
-    public void addDocument(DocumentID document)
-    { this.childDocs.add(document);
-    }
-
-    public void setParent(HierarchyNode parent)
-    { this.parent = parent;
-    }
-    
-    public HierarchyNode getParent()
-    { return this.parent;
-    }
-
-    public void setDescriptor(String descriptor)
-    { this.descriptor = descriptor;
-    }
-
-    public void setPath(String path)
-    { this.path = path;
-    }
-
-    public void setName(String name)
-    { this.name = name;
-    }
-
-    public void addMatch(String match)
-    { this.matches.add(match);
-    }
-
-    public boolean isMatch (String toMatch)
-    { Iterator thisMatch = this.matches.iterator();
-			
-      while (thisMatch.hasNext())
-      { String thisMatchText = thisMatch.next().toString();
-
-        if (thisMatchText.equals(toMatch))
-	{ return true;
-	}
-      }
-      return false;
-    }
-
-    public void getClassifications(DocumentID documentID, List values, 
-				   HierarchyClassifierObserver observer)
+    public void getClassifications(DocumentID documentID, List values, String sortKey,
+				   ClassifierObserverInterface observer)
     { Iterator valueList = values.iterator();
 
@@ -117,11 +53,13 @@
       {	if (this.isMatch(valueList.next().toString()))
 	{ observer.recordClassification(this.name);
-	  this.addDocument(documentID);
-	}
-      }
+	  this.addDocument(documentID, sortKey);
+	}
+      }
+     
+      // recurse into the child nodes for them to do the same...
       Iterator childList = this.childNodes.iterator();
 
       while (childList.hasNext())
-      { ((HierarchyNode) childList.next()).getClassifications(documentID, values, observer);
+      { ((HierarchyNode) childList.next()).getClassifications(documentID, values, sortKey, observer);
       }
     }
@@ -137,6 +75,6 @@
     StringBuffer match;
     boolean inElement;
-    HierarchyNode rootNode = null;
-    HierarchyNode currentNode = null;
+    AbstractHierarchyNode rootNode = null;
+    AbstractHierarchyNode currentNode = null;
     List          rootNodes = null;
 
@@ -195,5 +133,5 @@
       }
       else if (localName.equals("Path"))
-      {	this.currentNode.setPath(XMLTools.cleanString(this.path.toString()));
+      {	this.currentNode.setID(XMLTools.cleanString(this.path.toString()));
         this.path = null;
       }
@@ -227,14 +165,15 @@
     }
 
-    public HierarchyNode getHierarchy()
+    public AbstractHierarchyNode getHierarchy()
     { return this.rootNode;
     }
   }
-
-  private HierarchyNode hierarchy;
-  private List          fields;
-  private String        sort;
-
-  public HierarchyClassifier(File basefile, List fields, String sort)
+  
+  private GS3SQLConnection database;
+  private AbstractHierarchyNode    hierarchy;
+  private List             fields;
+  private String           sortBy;
+
+  public HierarchyClassifier(File basefile, List fields, String sortBy)
   {	
     try 
@@ -250,5 +189,5 @@
       this.hierarchy = handler.getHierarchy();
       this.fields = fields;
-      this.sort   = sort;
+      this.sortBy = sortBy;
     }
     catch (SAXException saxException)
@@ -265,8 +204,20 @@
   public void configure(List parameters)
   {
+    Iterator iterator = parameters.iterator();
+    while (iterator.hasNext()) {
+      String param = iterator.next().toString();
+      if (param.equals("-metadata")) {
+	if (iterator.hasNext()) {
+	  String field = iterator.next().toString();
+	  if (field != null && field.length() > 0) {
+	    this.fields.add(field);
+	  }
+	}
+      }
+    }
   }
 
   public void setDatabase(GS3SQLConnection database)
-  {
+  { this.database = database;
   }
 
@@ -298,6 +249,21 @@
       }
 
+      if (documentID == null) {
+	System.out.println("Bad documentID");
+	continue;
+      }
+
+      // get the sort key for the metadata item if possible
+      String sortKey = null;
+
+      if (this.sortBy != null) {
+	List sortKeys = document.getDocumentMetadataItem(this.sortBy);
+	if (sortKeys != null && sortKeys.size() > 0) {
+	  sortKey = sortKeys.get(0).toString();
+	}
+      }
+
       // ...and send them to the classifier
-      this.hierarchy.getClassifications(documentID, values, classifyObserver);
+      this.hierarchy.getClassifications(documentID, values, sortKey, classifyObserver);
     }
     return true;
@@ -318,4 +284,7 @@
   public void completeClassification()
   { // TODO: store the classifications to file...
+    if (this.hierarchy != null) {
+      this.hierarchy.writeSQL(this.database);
+    }
   }
 }
