changeset 590:69bc69381ac4 stream

more work on stream input and more cleanup
author robcast
date Thu, 06 Jan 2011 20:42:29 +0100
parents 73e041c710d3
children f7e2b6f29b6d
files servlet/src/digilib/auth/HashTree.java servlet/src/digilib/auth/XMLAuthOps.java servlet/src/digilib/image/ImageInfoDocuImage.java servlet/src/digilib/image/ImageLoaderDocuImage.java servlet/src/digilib/io/AliasingDocuDirCache.java servlet/src/digilib/io/DocuDirectory.java servlet/src/digilib/io/DocuDirent.java servlet/src/digilib/io/DocuDirentImpl.java servlet/src/digilib/io/ImageCacheStream.java servlet/src/digilib/io/ImageFile.java servlet/src/digilib/io/ImageFileSet.java servlet/src/digilib/io/ImageStream.java servlet/src/digilib/io/MetadataMap.java servlet/src/digilib/io/XMLListLoader.java servlet/src/digilib/io/XMLMetaLoader.java servlet/src/digilib/meta/MetadataMap.java servlet/src/digilib/meta/XMLMetaLoader.java servlet/src/digilib/servlet/DigilibConfiguration.java servlet/src/digilib/util/HashTree.java servlet/src/digilib/util/XMLListLoader.java
diffstat 20 files changed, 761 insertions(+), 642 deletions(-) [+]
line wrap: on
line diff
--- a/servlet/src/digilib/auth/HashTree.java	Thu Jan 06 17:33:01 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/*  HashTree -- Tree in a Hashtable
-
- Digital Image Library servlet components
-
- Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
-
- 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.
- 
- Please read license.txt for the full details. A copy of the GPL
- may be found at http://www.gnu.org/copyleft/lgpl.html
-
- 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
- */
-
-package digilib.auth;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-/**
- * Tree representation wrapper for a HashMap.
- * 
- * The HashTree is constructed from a HashMap filled with 'branches' with
- * 'leaves'. The branches are stored as String keys in the HashMap. The String
- * values are leaves.
- * 
- * Branches are matched in 'twigs' separated by 'twig separator' Strings. The
- * return values for a match are leaf values separated by 'leaf separator'
- * Strings.
- * 
- * @author casties
- */
-public class HashTree {
-
-    private Map<String, String> table;
-
-    private String twigSep = "/";
-
-    private String leafSep = ",";
-
-    /**
-     * Constructor of a HashTree.
-     * 
-     * Creates a HashTree wrapper around a given HashMap, using the given twig
-     * separator and leaf separator.
-     * 
-     * @param t
-     * @param twig_separator
-     * @param leaf_separator
-     */
-    public HashTree(Map<String, String> t, String twig_separator, String leaf_separator) {
-        table = t;
-        twigSep = twig_separator;
-        leafSep = leaf_separator;
-        optimizeTable();
-    }
-
-    void optimizeTable() {
-    }
-
-    /**
-     * Matches the given branch against the HashTree.
-     * 
-     * Returns a LinkedList of all leaves on all matching branches in the tree.
-     * Branches in the tree match if they are substrings starting at the same
-     * root.
-     * 
-     * @param branch
-     * @return
-     */
-    List<String> match(String branch) {
-        String b = "";
-        String m;
-        LinkedList<String> matches = new LinkedList<String>();
-
-        // split branch
-        StringTokenizer twig = new StringTokenizer(branch, twigSep);
-        // walk branch and check with tree
-        while (twig.hasMoreTokens()) {
-            if (b.length() == 0) {
-                b = twig.nextToken();
-            } else {
-                b += twigSep + twig.nextToken();
-            }
-            m = table.get(b);
-            if (m != null) {
-                if (m.indexOf(leafSep) < 0) {
-                    // single leaf
-                    matches.add(m);
-                } else {
-                    // split leaves
-                    StringTokenizer leaf = new StringTokenizer(m, leafSep);
-                    while (leaf.hasMoreTokens()) {
-                        matches.add(leaf.nextToken());
-                    }
-                }
-            }
-        }
-        if (matches.size() > 0) {
-            return matches;
-        } else {
-            return null;
-        }
-    }
-}
--- a/servlet/src/digilib/auth/XMLAuthOps.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/auth/XMLAuthOps.java	Thu Jan 06 20:42:29 2011 +0100
@@ -26,8 +26,9 @@
 
 import javax.servlet.http.HttpServletRequest;
 
-import digilib.io.XMLListLoader;
 import digilib.servlet.DigilibRequest;
+import digilib.util.HashTree;
+import digilib.util.XMLListLoader;
 
 /** Implementation of AuthOps using XML files.
  *
--- a/servlet/src/digilib/image/ImageInfoDocuImage.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/image/ImageInfoDocuImage.java	Thu Jan 06 20:42:29 2011 +0100
@@ -3,7 +3,6 @@
  */
 package digilib.image;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 
@@ -18,31 +17,28 @@
  */
 public abstract class ImageInfoDocuImage extends DocuImageImpl {
 
-    /** Check image size and type and store in ImageFile f */
+    /* Check image size and type and store in ImageFile f */
     public ImageInput identify(ImageInput ii) throws IOException {
-        // fileset to store the information
-        File f = ii.getFile();
-        if (f == null) {
-            throw new IOException("File not found!");
-        }
-        RandomAccessFile raf = new RandomAccessFile(f, "r");
+        logger.debug("identifying (ImageInfo) " + ii);
         // set up ImageInfo object
         ImageInfo iif = new ImageInfo();
-        iif.setInput(raf);
+        if (ii.hasImageInputStream()) {
+            iif.setInput(ii.getImageInputStream());
+        } else if (ii.hasFile()) {
+            RandomAccessFile raf = new RandomAccessFile(ii.getFile(), "r");
+            iif.setInput(raf);
+        } else {
+            return null;
+        }
         iif.setCollectComments(false);
         iif.setDetermineImageNumber(false);
-        logger.debug("identifying (ImageInfo) " + f);
         // try with ImageInfo first
         if (iif.check()) {
             ImageSize d = new ImageSize(iif.getWidth(), iif.getHeight());
             ii.setSize(d);
             ii.setMimetype(iif.getMimeType());
-            //logger.debug("  format:"+iif.getFormatName());
-            raf.close();
             logger.debug("image size: " + ii.getSize());
             return ii;
-        } else {
-            raf.close();
         }
         return null;
     }
--- a/servlet/src/digilib/image/ImageLoaderDocuImage.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/image/ImageLoaderDocuImage.java	Thu Jan 06 20:42:29 2011 +0100
@@ -29,7 +29,6 @@
 import java.awt.image.ConvolveOp;
 import java.awt.image.Kernel;
 import java.awt.image.RescaleOp;
-import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.RandomAccessFile;
@@ -53,7 +52,7 @@
 
 /** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */
 public class ImageLoaderDocuImage extends ImageInfoDocuImage {
-
+    
 	/** image object */
 	protected BufferedImage img;
 	
@@ -63,9 +62,14 @@
 	/** ImageIO image reader */
 	protected ImageReader reader;
 
-	/** File that was read */
-	protected File imgFile;
-
+	protected static Kernel[] convolutionKernels = {
+	        null,
+	        new Kernel(1, 1, new float[] {1f}),
+            new Kernel(2, 2, new float[] {0.25f, 0.25f, 0.25f, 0.25f}),
+            new Kernel(3, 3, new float[] {1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f})
+	};
+	
+	
 	/* loadSubimage is supported. */
 	public boolean isSubimageSupported() {
 		return true;
@@ -128,7 +132,12 @@
         /*
          * try ImageReader
          */
-        reader = getReader(input);
+        try {
+            reader = getReader(input);
+        } catch (FileOpException e) {
+            // maybe just our class doesn't know what to do
+            return null;
+        }
         // set size
         ImageSize d = new ImageSize(reader.getWidth(0), reader.getHeight(0));
         input.setSize(d);
@@ -174,7 +183,7 @@
 				logger.debug("reusing Reader");
 				return reader;
 			}
-			// clean up old reader
+			// clean up old reader (this shouldn't really happen)
 			logger.debug("cleaning Reader!");
 			dispose();
 		}
@@ -217,9 +226,7 @@
 			throws FileOpException {
 		logger.debug("loadSubimage");
 		try {
-			if ((reader == null) || (imgFile != ii.getFile())) {
-				getReader(ii);
-			}
+			reader = getReader(ii);
 			// set up reader parameters
 			ImageReadParam readParam = reader.getDefaultReadParam();
 			readParam.setSourceRegion(region);
@@ -337,19 +344,23 @@
 	}
 
 	public void blur(int radius) throws ImageOpException {
-		// DEBUG
 		logger.debug("blur: " + radius);
 		// minimum radius is 2
 		int klen = Math.max(radius, 2);
-		// FIXME: use constant kernels for most common sizes
-		int ksize = klen * klen;
-		// kernel is constant 1/k
-		float f = 1f / ksize;
-		float[] kern = new float[ksize];
-		for (int i = 0; i < ksize; i++) {
-			kern[i] = f;
+		Kernel blur = null;
+		if (klen < convolutionKernels.length) {
+            blur = convolutionKernels[klen];
+		} else {
+            // calculate our own kernel
+            int ksize = klen * klen;
+            // kernel is constant 1/k
+            float f = 1f / ksize;
+            float[] kern = new float[ksize];
+            for (int i = 0; i < ksize; ++i) {
+                kern[i] = f;
+            }
+            blur = new Kernel(klen, klen, kern);
 		}
-		Kernel blur = new Kernel(klen, klen, kern);
 		// blur with convolve operation
 		ConvolveOp blurOp = new ConvolveOp(blur, ConvolveOp.EDGE_NO_OP,
 				renderHint);
@@ -415,7 +426,7 @@
 
 	/**
 	 * Ensures that the array f is in the right order to map the images RGB
-	 * components. (not shure what happens
+	 * components. (not sure what happens otherwise)
 	 */
 	public float[] rgbOrdered(float[] fa) {
 		/*
--- a/servlet/src/digilib/io/AliasingDocuDirCache.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/io/AliasingDocuDirCache.java	Thu Jan 06 20:42:29 2011 +0100
@@ -28,6 +28,7 @@
 
 import digilib.io.FileOps.FileClass;
 import digilib.servlet.DigilibConfiguration;
+import digilib.util.XMLListLoader;
 
 /**
  * @author casties
--- a/servlet/src/digilib/io/DocuDirectory.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/io/DocuDirectory.java	Thu Jan 06 20:42:29 2011 +0100
@@ -31,6 +31,8 @@
 import org.xml.sax.SAXException;
 
 import digilib.io.FileOps.FileClass;
+import digilib.meta.MetadataMap;
+import digilib.meta.XMLMetaLoader;
 
 /**
  * @author casties
--- a/servlet/src/digilib/io/DocuDirent.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/io/DocuDirent.java	Thu Jan 06 20:42:29 2011 +0100
@@ -2,6 +2,8 @@
 
 import java.io.File;
 
+import digilib.meta.MetadataMap;
+
 public interface DocuDirent extends Comparable<Object> {
 
     /**
--- a/servlet/src/digilib/io/DocuDirentImpl.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/io/DocuDirentImpl.java	Thu Jan 06 20:42:29 2011 +0100
@@ -28,6 +28,8 @@
 import org.apache.log4j.Logger;
 
 import digilib.io.FileOps.FileClass;
+import digilib.meta.MetadataMap;
+import digilib.meta.XMLMetaLoader;
 
 /**
  * Abstract directory entry in a DocuDirectory.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/ImageCacheStream.java	Thu Jan 06 20:42:29 2011 +0100
@@ -0,0 +1,49 @@
+/**
+ * 
+ */
+package digilib.io;
+
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+
+/**
+ * @author casties
+ *
+ */
+public class ImageCacheStream extends ImageStream {
+
+    public ImageCacheStream(InputStream stream, String mimeType) {
+        super(stream, mimeType);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see digilib.io.ImageInput#hasImageInputStream()
+     */
+    @Override
+    public boolean hasImageInputStream() {
+        return true;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see digilib.io.ImageInput#getImageInputStream()
+     */
+    @Override
+    public ImageInputStream getImageInputStream() {
+        /*
+         * TODO: which type of stream backing? 
+         * In general, it is preferable to
+         * use a FileCacheImageInputStream when reading from a regular
+         * InputStream. This class is provided for cases where it is not
+         * possible to create a writable temporary file.
+         */
+        ImageInputStream iis = new MemoryCacheImageInputStream(this.stream);
+        return iis;
+    }
+
+}
--- a/servlet/src/digilib/io/ImageFile.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/io/ImageFile.java	Thu Jan 06 20:42:29 2011 +0100
@@ -22,7 +22,12 @@
 package digilib.io;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import javax.imageio.stream.FileImageInputStream;
+import javax.imageio.stream.ImageInputStream;
 
 import digilib.image.ImageSize;
 import digilib.servlet.DigilibConfiguration;
@@ -114,8 +119,39 @@
 		return name;
 	}
 
-	/**
-	 * @return File
+	
+	/* (non-Javadoc)
+     * @see digilib.io.ImageInput#hasImageInputStream()
+     */
+    @Override
+    public boolean hasImageInputStream() {
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see digilib.io.ImageInput#getImageInputStream()
+     */
+    @Override
+    public ImageInputStream getImageInputStream() {
+        try {
+            RandomAccessFile rf = new RandomAccessFile(file, "r");
+            return new FileImageInputStream(rf);
+        } catch (IOException e) {
+            // what now?
+        }
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see digilib.io.ImageInput#hasFile()
+     */
+    @Override
+    public boolean hasFile() {
+        return true;
+    }
+
+    /* (non-Javadoc)
+	 * @see digilib.io.ImageInput#getFile()
 	 */
 	public File getFile() {
 		return file;
--- a/servlet/src/digilib/io/ImageFileSet.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/io/ImageFileSet.java	Thu Jan 06 20:42:29 2011 +0100
@@ -11,6 +11,8 @@
 import org.apache.log4j.Logger;
 
 import digilib.io.FileOps.FileClass;
+import digilib.meta.MetadataMap;
+import digilib.meta.XMLMetaLoader;
 
 /**
  * @author casties
--- a/servlet/src/digilib/io/ImageStream.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/io/ImageStream.java	Thu Jan 06 20:42:29 2011 +0100
@@ -7,18 +7,35 @@
 
 /**
  * @author casties
- *
+ * 
  */
 public class ImageStream extends ImageInput {
 
-	protected InputStream stream = null;
-	
-	public ImageStream(InputStream stream, String mimeType) {
-		this.stream = stream;
-		this.mimetype = mimeType;
-	}
-	
-	public InputStream getStream() {
-		return stream;
-	}
+    protected InputStream stream = null;
+
+    public ImageStream(InputStream stream, String mimeType) {
+        this.stream = stream;
+        this.mimetype = mimeType;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see digilib.io.ImageInput#hasInputStream()
+     */
+    @Override
+    public boolean hasInputStream() {
+        return true;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see digilib.io.ImageInput#getInputStream()
+     */
+    @Override
+    public InputStream getInputStream() {
+        return stream;
+    }
+
 }
--- a/servlet/src/digilib/io/MetadataMap.java	Thu Jan 06 17:33:01 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-/**
- * 
- */
-package digilib.io;
-
-import java.util.HashMap;
-
-/** Map for metadata related to files.
- * @author casties
- *
- */
-@SuppressWarnings("serial")
-public class MetadataMap extends HashMap<String, String> {
-
-}
--- a/servlet/src/digilib/io/XMLListLoader.java	Thu Jan 06 17:33:01 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-/* XMLListLoader -- Load an XML list into a Hashtable
-
-  Digital Image Library servlet components
-
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
-
-  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.
-   
-  Please read license.txt for the full details. A copy of the GPL
-  may be found at http://www.gnu.org/copyleft/lgpl.html
-
-  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
-
-package digilib.io;
-
-// JAXP packages
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.apache.log4j.Logger;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/** Loads a simple XML list into a HashMap.
- * 
- * The XML file has an outer <code>list_tag</code>. Every entry is an 
- * <code>entry_tag</code> with two attributes: the <code>key_att</code>
- * key and the <code>value_att</code> value.
- * 
- * The file is read by the <code>loadURL</code> method, that returns a
- * HashMap with the key-value pairs.
- * 
- * @author casties
- */
-public class XMLListLoader {
-
-	private Logger logger = Logger.getLogger(this.getClass());
-	private String listTag = "list";
-	private String entryTag = "entry";
-	private String keyAtt = "key";
-	private String valueAtt = "value";
-
-	public XMLListLoader() {
-	}
-
-	public XMLListLoader(
-		String list_tag,
-		String entry_tag,
-		String key_att,
-		String value_att) {
-		logger.debug("xmlListLoader("+list_tag+","+entry_tag+","+key_att+","+value_att+")");
-		listTag = list_tag;
-		entryTag = entry_tag;
-		keyAtt = key_att;
-		valueAtt = value_att;
-	}
-
-	/**
-	 *  inner class XMLListParser to be called by the parser
-	 */
-	private class XMLListParser extends DefaultHandler {
-
-		private Map<String, String> listData;
-		private LinkedList<String> tagSpace;
-
-		public Map<String, String> getData() {
-			return listData;
-		}
-
-		// Parser calls this once at the beginning of a document
-		public void startDocument() throws SAXException {
-			listData = new HashMap<String, String>();
-			tagSpace = new LinkedList<String>();
-		}
-
-		// Parser calls this for each element in a document
-		public void startElement(
-			String namespaceURI,
-			String localName,
-			String qName,
-			Attributes atts)
-			throws SAXException {
-			//System.out.println("<"+qName);
-			// open a new namespace
-			tagSpace.addLast(qName);
-
-			// ist it an entry tag?
-			if (qName.equals(entryTag)) {
-				// is it inside a list tag?
-				if ((listTag.length() > 0) && (!tagSpace.contains(listTag))) {
-					logger.error("BOO: Entry "
-							+ entryTag
-							+ " not inside list "
-							+ listTag);
-					throw new SAXParseException(
-						"Entry " + entryTag + " not inside list " + listTag,
-						null);
-				}
-				// get the attributes
-				String key = atts.getValue(keyAtt);
-				String val = atts.getValue(valueAtt);
-				if ((key == null) || (val == null)) {
-					logger.error("BOO: Entry "
-							+ entryTag
-							+ " does not have Attributes "
-							+ keyAtt
-							+ ", "
-							+ valueAtt);
-					throw new SAXParseException(
-						"Entry "
-							+ entryTag
-							+ " does not have Attributes "
-							+ keyAtt
-							+ ", "
-							+ valueAtt,
-						null);
-				}
-				// add the values
-				//System.out.println("DATA: "+key+" = "+val);
-				listData.put(key, val);
-			}
-		}
-
-		public void endElement(
-			String namespaceURI,
-			String localName,
-			String qName)
-			throws SAXException {
-			// exit the namespace
-			tagSpace.removeLast();
-		}
-
-	}
-
-	/**
-	 *  load and parse a file (as URL)
-	 *    returns HashMap with list data
-	 */
-	public Map<String, String> loadURL(String path) throws SAXException, IOException {
-		//System.out.println("loadurl ("+path+")");
-		// Create a JAXP SAXParserFactory and configure it
-		SAXParserFactory spf = SAXParserFactory.newInstance();
-		spf.setNamespaceAware(true);
-
-		SAXParser parser = null;
-		try {
-			// Create a JAXP SAXParser
-			parser = spf.newSAXParser();
-
-		} catch (ParserConfigurationException e) {
-			throw new SAXException(e);
-		}
-
-		// create a list parser (keeps the data!)
-		XMLListParser listParser = new XMLListParser();
-
-		// Tell the SAXParser to parse the XML document
-		parser.parse(path, listParser);
-
-		return listParser.getData();
-	}
-
-}
--- a/servlet/src/digilib/io/XMLMetaLoader.java	Thu Jan 06 17:33:01 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,287 +0,0 @@
-/* XMLMetaLoader -- Load an XML format metadata into a Hashtable
-
-  Digital Image Library servlet components
-
-  Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
-
-  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.
-   
-  Please read license.txt for the full details. A copy of the GPL
-  may be found at http://www.gnu.org/copyleft/lgpl.html
-
-  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
-
-package digilib.io;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.apache.log4j.Logger;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-public class XMLMetaLoader {
-
-	private Logger logger = Logger.getLogger(this.getClass());
-	private String metaTag = "meta";
-	private String fileTag = "file";
-	private String fileNameTag = "name";
-	private String filePathTag = "path";
-	private String imgTag = "img";
-	private String collectTag = "context";
-
-	public XMLMetaLoader() {
-	}
-
-	/**
-	 *  inner class XMLMetaParser to be called by the parser
-	 */
-	private class XMLMetaParser extends DefaultHandler {
-
-		private LinkedList<String> tags;
-		private Map<String, MetadataMap> files;
-		private MetadataMap meta;
-		private StringBuffer content;
-		private boolean collecting;
-		private StringBuffer collectedContent;
-		private String fileName;
-		private String filePath;
-
-		/**
-		 * extracts the elements name from either localName ln or qName qn.
-		 * 
-		 * @param ln localName
-		 * @param qn qName
-		 * @return element name
-		 */
-		private String getName(String ln, String qn) {
-			if (ln != null) {
-				if (ln.length() > 0) {
-					return ln;
-				}
-			}
-			// else it's qName (or nothing)
-			return qn;
-		}
-
-		/**
-		 * returns all attributes as a String
-		 * 
-		 * @param attrs
-		 * @return 
-		 */
-		private String getAttrString(Attributes attrs) {
-			StringBuffer s = new StringBuffer();
-			for (int i = 0; i < attrs.getLength(); i++) {
-				String key = getName(attrs.getLocalName(i), attrs.getQName(i));
-				s.append(" "+key+"=\""+attrs.getValue(i)+"\"");
-			}
-			return s.toString();
-		}
-
-			
-		// Parser calls this once at the beginning of a document
-		public void startDocument() throws SAXException {
-			tags = new LinkedList<String>();
-			files = new HashMap<String, MetadataMap>();
-			collecting = false;
-			collectedContent = null;
-		}
-
-		// Parser calls this for each element in a document
-		public void startElement(
-			String namespaceURI,
-			String localName,
-			String qName,
-			Attributes atts)
-			throws SAXException {
-
-			String name = getName(localName, qName);
-			// open a new tag
-			tags.addLast(name);
-			// start new content (no nesting of tags and content)
-			content = new StringBuffer();
-
-			if (name.equals(metaTag)) {
-				// new meta tag
-				meta = new MetadataMap();
-				collectedContent = new StringBuffer();
-			} else if (name.equals(fileTag)) {
-				// new file tag
-				fileName = null;
-				filePath = null;
-				meta = new MetadataMap();
-				collectedContent = new StringBuffer();
-			} else if (name.equals(collectTag)) {
-				// start collecting
-				collecting = true;
-				if (collectedContent == null) {
-					collectedContent = new StringBuffer();
-				}
-			}
-			
-			// record mode
-			if (collecting) {
-				collectedContent.append("<"+name);
-				collectedContent.append(getAttrString(atts));
-				collectedContent.append(">");
-			}
-		}
-
-		// parser calls this for all tag content (possibly more than once)
-		public void characters(char[] ch, int start, int length)
-			throws SAXException {
-			// append data to current string buffer
-			if (content == null) {
-				content = new StringBuffer();
-			}
-			content.append(ch, start, length);
-		}
-
-		// parser calls this at the end of each element
-		public void endElement(
-			String namespaceURI,
-			String localName,
-			String qName)
-			throws SAXException {
-
-			String name = getName(localName, qName);
-			// exit the tag
-			tags.removeLast();
-			String lastTag = (tags.isEmpty()) ? "" : tags.getLast();
-
-			// was it a file/name tag?
-			if (name.equals(fileNameTag) && lastTag.equals(fileTag)) {
-				// save name as filename
-				if ((content != null) && (content.length() > 0)) {
-					fileName = content.toString().trim();
-				}
-				content = null;
-				return;
-			}
-
-			// was it a file/path tag?
-			if (name.equals(filePathTag) && lastTag.equals(fileTag)) {
-				// save path as filepath 
-				if ((content != null) && (content.length() > 0)) {
-					filePath = content.toString().trim();
-				}
-				content = null;
-				return;
-			}
-
-			// was it a file tag?
-			if (name.equals(fileTag)) {
-				// is there meta to save?
-				if ((meta != null) && (meta.size() > 0)) {
-					// file name is (optional file/path) / file/name
-					String fn = null;
-
-					if (fileName != null) {
-						if (filePath != null) {
-							fn = filePath + "/" + fileName;
-						} else {
-							fn = fileName;
-						}
-					} else {
-						// no file name, no file
-						content = null;
-						return;
-					}
-					// save meta in file list 
-					files.put(fn, meta);
-				}
-				content = null;
-				return;
-			}
-
-			// was it a meta tag outside a file tag?
-			if (name.equals(metaTag) && !tags.contains(fileTag)) {
-				// save meta as dir meta
-				if ((meta != null) && (meta.size() > 0)) {
-					files.put("", meta);
-				}
-				content = null;
-				return;
-			}
-
-			// is this inside an digilib info (=img) tag?
-			if (lastTag.equals(imgTag)) {
-				// then add whatever this is
-				if ((content != null) && (content.length() > 0)) {
-					meta.put(name, content.toString().trim());
-				}
-				content = null;
-				return;
-			}
-
-			// is this the end of collectTag?
-			if (name.equals(collectTag)) {
-				collecting = false;
-				collectedContent.append("</"+collectTag+">\n");
-				// store collected stuff
-				meta.put(collectTag, collectedContent.toString());
-				//logger.debug("collected: '"+collectedContent+"'");
-				content = null;
-				return;
-			}
-
-			// write collected content
-			if (collecting) {
-				String s = "";
-				if ((content != null) && (content.length() > 0)) {
-					s = content.toString().trim();
-				}
-				//logger.debug("collect:"+name+" = "+s);
-				collectedContent.append(s);
-				collectedContent.append("</"+name+">\n");
-				content = null;
-				return;
-			}
-		}
-
-	}
-
-	/**
-	 *  load and parse a file (as URL)
-	 *    returns HashMap with list data
-	 */
-	public Map<String, MetadataMap> loadURL(String path) throws SAXException, IOException {
-		logger.debug("loading meta: "+path);
-		// Create a JAXP SAXParserFactory and configure it
-		SAXParserFactory spf = SAXParserFactory.newInstance();
-		spf.setNamespaceAware(true);
-
-		SAXParser parser = null;
-		try {
-			// Create a JAXP SAXParser
-			parser = spf.newSAXParser();
-
-		} catch (ParserConfigurationException e) {
-			throw new SAXException(e);
-		}
-
-		// create a list parser (keeps the data!)
-		XMLMetaParser listParser = new XMLMetaParser();
-
-		// Tell the SAXParser to parse the XML document
-		parser.parse(path, listParser);
-
-		return listParser.files;
-	}
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/meta/MetadataMap.java	Thu Jan 06 20:42:29 2011 +0100
@@ -0,0 +1,15 @@
+/**
+ * 
+ */
+package digilib.meta;
+
+import java.util.HashMap;
+
+/** Map for metadata related to files.
+ * @author casties
+ *
+ */
+@SuppressWarnings("serial")
+public class MetadataMap extends HashMap<String, String> {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/meta/XMLMetaLoader.java	Thu Jan 06 20:42:29 2011 +0100
@@ -0,0 +1,287 @@
+/* XMLMetaLoader -- Load an XML format metadata into a Hashtable
+
+  Digital Image Library servlet components
+
+  Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+
+  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.
+   
+  Please read license.txt for the full details. A copy of the GPL
+  may be found at http://www.gnu.org/copyleft/lgpl.html
+
+  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+package digilib.meta;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.log4j.Logger;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class XMLMetaLoader {
+
+	private Logger logger = Logger.getLogger(this.getClass());
+	private String metaTag = "meta";
+	private String fileTag = "file";
+	private String fileNameTag = "name";
+	private String filePathTag = "path";
+	private String imgTag = "img";
+	private String collectTag = "context";
+
+	public XMLMetaLoader() {
+	}
+
+	/**
+	 *  inner class XMLMetaParser to be called by the parser
+	 */
+	private class XMLMetaParser extends DefaultHandler {
+
+		private LinkedList<String> tags;
+		private Map<String, MetadataMap> files;
+		private MetadataMap meta;
+		private StringBuffer content;
+		private boolean collecting;
+		private StringBuffer collectedContent;
+		private String fileName;
+		private String filePath;
+
+		/**
+		 * extracts the elements name from either localName ln or qName qn.
+		 * 
+		 * @param ln localName
+		 * @param qn qName
+		 * @return element name
+		 */
+		private String getName(String ln, String qn) {
+			if (ln != null) {
+				if (ln.length() > 0) {
+					return ln;
+				}
+			}
+			// else it's qName (or nothing)
+			return qn;
+		}
+
+		/**
+		 * returns all attributes as a String
+		 * 
+		 * @param attrs
+		 * @return 
+		 */
+		private String getAttrString(Attributes attrs) {
+			StringBuffer s = new StringBuffer();
+			for (int i = 0; i < attrs.getLength(); i++) {
+				String key = getName(attrs.getLocalName(i), attrs.getQName(i));
+				s.append(" "+key+"=\""+attrs.getValue(i)+"\"");
+			}
+			return s.toString();
+		}
+
+			
+		// Parser calls this once at the beginning of a document
+		public void startDocument() throws SAXException {
+			tags = new LinkedList<String>();
+			files = new HashMap<String, MetadataMap>();
+			collecting = false;
+			collectedContent = null;
+		}
+
+		// Parser calls this for each element in a document
+		public void startElement(
+			String namespaceURI,
+			String localName,
+			String qName,
+			Attributes atts)
+			throws SAXException {
+
+			String name = getName(localName, qName);
+			// open a new tag
+			tags.addLast(name);
+			// start new content (no nesting of tags and content)
+			content = new StringBuffer();
+
+			if (name.equals(metaTag)) {
+				// new meta tag
+				meta = new MetadataMap();
+				collectedContent = new StringBuffer();
+			} else if (name.equals(fileTag)) {
+				// new file tag
+				fileName = null;
+				filePath = null;
+				meta = new MetadataMap();
+				collectedContent = new StringBuffer();
+			} else if (name.equals(collectTag)) {
+				// start collecting
+				collecting = true;
+				if (collectedContent == null) {
+					collectedContent = new StringBuffer();
+				}
+			}
+			
+			// record mode
+			if (collecting) {
+				collectedContent.append("<"+name);
+				collectedContent.append(getAttrString(atts));
+				collectedContent.append(">");
+			}
+		}
+
+		// parser calls this for all tag content (possibly more than once)
+		public void characters(char[] ch, int start, int length)
+			throws SAXException {
+			// append data to current string buffer
+			if (content == null) {
+				content = new StringBuffer();
+			}
+			content.append(ch, start, length);
+		}
+
+		// parser calls this at the end of each element
+		public void endElement(
+			String namespaceURI,
+			String localName,
+			String qName)
+			throws SAXException {
+
+			String name = getName(localName, qName);
+			// exit the tag
+			tags.removeLast();
+			String lastTag = (tags.isEmpty()) ? "" : tags.getLast();
+
+			// was it a file/name tag?
+			if (name.equals(fileNameTag) && lastTag.equals(fileTag)) {
+				// save name as filename
+				if ((content != null) && (content.length() > 0)) {
+					fileName = content.toString().trim();
+				}
+				content = null;
+				return;
+			}
+
+			// was it a file/path tag?
+			if (name.equals(filePathTag) && lastTag.equals(fileTag)) {
+				// save path as filepath 
+				if ((content != null) && (content.length() > 0)) {
+					filePath = content.toString().trim();
+				}
+				content = null;
+				return;
+			}
+
+			// was it a file tag?
+			if (name.equals(fileTag)) {
+				// is there meta to save?
+				if ((meta != null) && (meta.size() > 0)) {
+					// file name is (optional file/path) / file/name
+					String fn = null;
+
+					if (fileName != null) {
+						if (filePath != null) {
+							fn = filePath + "/" + fileName;
+						} else {
+							fn = fileName;
+						}
+					} else {
+						// no file name, no file
+						content = null;
+						return;
+					}
+					// save meta in file list 
+					files.put(fn, meta);
+				}
+				content = null;
+				return;
+			}
+
+			// was it a meta tag outside a file tag?
+			if (name.equals(metaTag) && !tags.contains(fileTag)) {
+				// save meta as dir meta
+				if ((meta != null) && (meta.size() > 0)) {
+					files.put("", meta);
+				}
+				content = null;
+				return;
+			}
+
+			// is this inside an digilib info (=img) tag?
+			if (lastTag.equals(imgTag)) {
+				// then add whatever this is
+				if ((content != null) && (content.length() > 0)) {
+					meta.put(name, content.toString().trim());
+				}
+				content = null;
+				return;
+			}
+
+			// is this the end of collectTag?
+			if (name.equals(collectTag)) {
+				collecting = false;
+				collectedContent.append("</"+collectTag+">\n");
+				// store collected stuff
+				meta.put(collectTag, collectedContent.toString());
+				//logger.debug("collected: '"+collectedContent+"'");
+				content = null;
+				return;
+			}
+
+			// write collected content
+			if (collecting) {
+				String s = "";
+				if ((content != null) && (content.length() > 0)) {
+					s = content.toString().trim();
+				}
+				//logger.debug("collect:"+name+" = "+s);
+				collectedContent.append(s);
+				collectedContent.append("</"+name+">\n");
+				content = null;
+				return;
+			}
+		}
+
+	}
+
+	/**
+	 *  load and parse a file (as URL)
+	 *    returns HashMap with list data
+	 */
+	public Map<String, MetadataMap> loadURL(String path) throws SAXException, IOException {
+		logger.debug("loading meta: "+path);
+		// Create a JAXP SAXParserFactory and configure it
+		SAXParserFactory spf = SAXParserFactory.newInstance();
+		spf.setNamespaceAware(true);
+
+		SAXParser parser = null;
+		try {
+			// Create a JAXP SAXParser
+			parser = spf.newSAXParser();
+
+		} catch (ParserConfigurationException e) {
+			throw new SAXException(e);
+		}
+
+		// create a list parser (keeps the data!)
+		XMLMetaParser listParser = new XMLMetaParser();
+
+		// Tell the SAXParser to parse the XML document
+		parser.parse(path, listParser);
+
+		return listParser.files;
+	}
+
+}
--- a/servlet/src/digilib/servlet/DigilibConfiguration.java	Thu Jan 06 17:33:01 2011 +0100
+++ b/servlet/src/digilib/servlet/DigilibConfiguration.java	Thu Jan 06 20:42:29 2011 +0100
@@ -36,9 +36,9 @@
 import digilib.image.DocuImageImpl;
 import digilib.io.FileOps;
 import digilib.io.ImageInput;
-import digilib.io.XMLListLoader;
 import digilib.util.Parameter;
 import digilib.util.ParameterMap;
+import digilib.util.XMLListLoader;
 
 /**
  * Class to hold the digilib servlet configuration parameters. The parameters
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/HashTree.java	Thu Jan 06 20:42:29 2011 +0100
@@ -0,0 +1,113 @@
+/*  HashTree -- Tree in a Hashtable
+
+ Digital Image Library servlet components
+
+ Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+
+ 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.
+ 
+ Please read license.txt for the full details. A copy of the GPL
+ may be found at http://www.gnu.org/copyleft/lgpl.html
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ */
+
+package digilib.util;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * Tree representation wrapper for a HashMap.
+ * 
+ * The HashTree is constructed from a HashMap filled with 'branches' with
+ * 'leaves'. The branches are stored as String keys in the HashMap. The String
+ * values are leaves.
+ * 
+ * Branches are matched in 'twigs' separated by 'twig separator' Strings. The
+ * return values for a match are leaf values separated by 'leaf separator'
+ * Strings.
+ * 
+ * @author casties
+ */
+public class HashTree {
+
+    private Map<String, String> table;
+
+    private String twigSep = "/";
+
+    private String leafSep = ",";
+
+    /**
+     * Constructor of a HashTree.
+     * 
+     * Creates a HashTree wrapper around a given HashMap, using the given twig
+     * separator and leaf separator.
+     * 
+     * @param t
+     * @param twig_separator
+     * @param leaf_separator
+     */
+    public HashTree(Map<String, String> t, String twig_separator, String leaf_separator) {
+        table = t;
+        twigSep = twig_separator;
+        leafSep = leaf_separator;
+        optimizeTable();
+    }
+
+    private void optimizeTable() {
+    }
+
+    /**
+     * Matches the given branch against the HashTree.
+     * 
+     * Returns a LinkedList of all leaves on all matching branches in the tree.
+     * Branches in the tree match if they are substrings starting at the same
+     * root.
+     * 
+     * @param branch
+     * @return
+     */
+    public List<String> match(String branch) {
+        String b = "";
+        String m;
+        LinkedList<String> matches = new LinkedList<String>();
+
+        // split branch
+        StringTokenizer twig = new StringTokenizer(branch, twigSep);
+        // walk branch and check with tree
+        while (twig.hasMoreTokens()) {
+            if (b.length() == 0) {
+                b = twig.nextToken();
+            } else {
+                b += twigSep + twig.nextToken();
+            }
+            m = table.get(b);
+            if (m != null) {
+                if (m.indexOf(leafSep) < 0) {
+                    // single leaf
+                    matches.add(m);
+                } else {
+                    // split leaves
+                    StringTokenizer leaf = new StringTokenizer(m, leafSep);
+                    while (leaf.hasMoreTokens()) {
+                        matches.add(leaf.nextToken());
+                    }
+                }
+            }
+        }
+        if (matches.size() > 0) {
+            return matches;
+        } else {
+            return null;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/XMLListLoader.java	Thu Jan 06 20:42:29 2011 +0100
@@ -0,0 +1,178 @@
+/* XMLListLoader -- Load an XML list into a Hashtable
+
+  Digital Image Library servlet components
+
+  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+
+  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.
+   
+  Please read license.txt for the full details. A copy of the GPL
+  may be found at http://www.gnu.org/copyleft/lgpl.html
+
+  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+package digilib.util;
+
+// JAXP packages
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.log4j.Logger;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** Loads a simple XML list into a HashMap.
+ * 
+ * The XML file has an outer <code>list_tag</code>. Every entry is an 
+ * <code>entry_tag</code> with two attributes: the <code>key_att</code>
+ * key and the <code>value_att</code> value.
+ * 
+ * The file is read by the <code>loadURL</code> method, that returns a
+ * HashMap with the key-value pairs.
+ * 
+ * @author casties
+ */
+public class XMLListLoader {
+
+	private Logger logger = Logger.getLogger(this.getClass());
+	private String listTag = "list";
+	private String entryTag = "entry";
+	private String keyAtt = "key";
+	private String valueAtt = "value";
+
+	public XMLListLoader() {
+	}
+
+	public XMLListLoader(
+		String list_tag,
+		String entry_tag,
+		String key_att,
+		String value_att) {
+		logger.debug("xmlListLoader("+list_tag+","+entry_tag+","+key_att+","+value_att+")");
+		listTag = list_tag;
+		entryTag = entry_tag;
+		keyAtt = key_att;
+		valueAtt = value_att;
+	}
+
+	/**
+	 *  inner class XMLListParser to be called by the parser
+	 */
+	private class XMLListParser extends DefaultHandler {
+
+		private Map<String, String> listData;
+		private LinkedList<String> tagSpace;
+
+		public Map<String, String> getData() {
+			return listData;
+		}
+
+		// Parser calls this once at the beginning of a document
+		public void startDocument() throws SAXException {
+			listData = new HashMap<String, String>();
+			tagSpace = new LinkedList<String>();
+		}
+
+		// Parser calls this for each element in a document
+		public void startElement(
+			String namespaceURI,
+			String localName,
+			String qName,
+			Attributes atts)
+			throws SAXException {
+			//System.out.println("<"+qName);
+			// open a new namespace
+			tagSpace.addLast(qName);
+
+			// ist it an entry tag?
+			if (qName.equals(entryTag)) {
+				// is it inside a list tag?
+				if ((listTag.length() > 0) && (!tagSpace.contains(listTag))) {
+					logger.error("BOO: Entry "
+							+ entryTag
+							+ " not inside list "
+							+ listTag);
+					throw new SAXParseException(
+						"Entry " + entryTag + " not inside list " + listTag,
+						null);
+				}
+				// get the attributes
+				String key = atts.getValue(keyAtt);
+				String val = atts.getValue(valueAtt);
+				if ((key == null) || (val == null)) {
+					logger.error("BOO: Entry "
+							+ entryTag
+							+ " does not have Attributes "
+							+ keyAtt
+							+ ", "
+							+ valueAtt);
+					throw new SAXParseException(
+						"Entry "
+							+ entryTag
+							+ " does not have Attributes "
+							+ keyAtt
+							+ ", "
+							+ valueAtt,
+						null);
+				}
+				// add the values
+				//System.out.println("DATA: "+key+" = "+val);
+				listData.put(key, val);
+			}
+		}
+
+		public void endElement(
+			String namespaceURI,
+			String localName,
+			String qName)
+			throws SAXException {
+			// exit the namespace
+			tagSpace.removeLast();
+		}
+
+	}
+
+	/**
+	 *  load and parse a file (as URL)
+	 *    returns HashMap with list data
+	 */
+	public Map<String, String> loadURL(String path) throws SAXException, IOException {
+		//System.out.println("loadurl ("+path+")");
+		// Create a JAXP SAXParserFactory and configure it
+		SAXParserFactory spf = SAXParserFactory.newInstance();
+		spf.setNamespaceAware(true);
+
+		SAXParser parser = null;
+		try {
+			// Create a JAXP SAXParser
+			parser = spf.newSAXParser();
+
+		} catch (ParserConfigurationException e) {
+			throw new SAXException(e);
+		}
+
+		// create a list parser (keeps the data!)
+		XMLListParser listParser = new XMLListParser();
+
+		// Tell the SAXParser to parse the XML document
+		parser.parse(path, listParser);
+
+		return listParser.getData();
+	}
+
+}