RFC: XML-Loader

[java]



import java.io.File;

import java.io.IOException;

import java.io.InputStreamReader;

import java.util.ArrayList;



import org.xml.sax.Attributes;

import org.xml.sax.InputSource;

import org.xml.sax.SAXException;

import org.xml.sax.XMLReader;

import org.xml.sax.ext.DefaultHandler2;

import org.xml.sax.helpers.XMLReaderFactory;



import com.jme3.asset.AssetInfo;

import com.jme3.asset.AssetLoader;





/****************************************************************************

  • Smart Multi-Purpose XMLLoader for JM3s AssetManger, providing an easy and
  • robust way to import generic XML-data.
  • @author .rhavin grobert
  • @version 0.4.1 */

    public abstract class XMLLoader extends DefaultHandler2

    implements AssetLoader

    {

    //

// members
/** The Object we want to create */
private Object object = null;
/** The stack of currently processed tags */
protected ArrayList<String> TagStack = new ArrayList<String>();
/** The AssetInfo given by the AssetManager */
protected AssetInfo assetInfo = null;
/** The current skip-level */
private int skiplevel = 0;
/** The path we're loading from */
private String strPath = "";

//
// Public methods
/** get the currently processed tag */
public String tagCurrent()
{
int iLast = TagStack.size() - 1;
if (iLast < 0)
return "";
return TagStack.get(iLast);
}
/** Get object */
public Object getObject()
{return object;}
/** Set object directly */
public void setObject(Object o)
{object = o;}
/** Shortcut to the currently loaded files path */
public String getPath()
{return strPath;}
/** Shortcut to load another asset */
public Object loadAsset(String strAsset)
{
if (assetInfo == null)
return null;
return assetInfo.getManager().loadAsset(strAsset);
}

//
// To-Override abstract methods
/*********************************************************************
* This method is called when the documents processing starts and
* should construct or otherwise set-up the object that the loader
* should return. You *MAY* return null and decide what object to
* construct later, for example, when first (root) tag is opened, by
* calling setObject().
* @return The object you want to fill from xml-file. */
protected abstract Object XMLbegin();

/*********************************************************************
* This method is called on every starting tag.
* @param uri The namespace URI, as specified by xmlns-attribute.
* @param localName The local name string.
* @param qName The name of the tag, this is usually what you're
* looking for.
* @param attribs The tags attribute reference.
* @return Return true if you want to parse this tag and its childs
* or false if this tag and all its content should be skipped. You'll
* still get an end-method call for this tag. */
protected abstract boolean XMLstart(String uri, String localName,
String qName, Attributes attribs);

/*********************************************************************
* This method is called each time a tag is closed.
* It is also called for empty tags.
* @param uri The namespace URI, as specified by xmlns-attribute.
* @param localName The local name string.
* @param qName The name of the tag, this is usually what you're
* looking for. */
protected abstract void XMLend(String uri, String name, String qName);

/*********************************************************************
* This method is called at the documents end. Do final processing
* in this method. */
protected abstract void XMLfinal();

/*********************************************************************
* This method is called for all non-markup. Due to formating of the
* XML-file, it may sometimes completely consist of white-spaces.
* @param string The String consisting of non-markup. */
protected abstract void XMLchars(String string);

//
// Methods implementing loaders functionality
/*********************************************************************
* Load asset from the given input stream, parsing it into an
* app-usable object. Specified by JM3 interface AssetLoader */
@Override
public Object load(AssetInfo i) throws IOException
{
assetInfo = i;
strPath = getFolder(i.getKey().getName(), true);

XMLReader xr;
try
{
xr = XMLReaderFactory.createXMLReader();
xr.setContentHandler(this);
xr.setErrorHandler(this);
InputStreamReader r = new InputStreamReader(i.openStream());
xr.parse(new InputSource(r));
r.close();
return object;
}
catch (SAXException e)
{throw new IOException(i.getKey()+ " : " + e.toString());}
}

/*********************************************************************
* Handler called by the SAX2 event management at document start.
* We initialize the loaders return-object here. */
@Override
public void startDocument()
{
// let derived class construct the object.
object = XMLbegin();
}

/*********************************************************************
* Handler called by the SAX2 event management on every starting tag
* @param uri The namespace URI, as specified by xmlns-attribute.
* @param localName The local name (without prefix), or the empty
* string if namespace processing is not being performed.
* @param qName The qualified name (with prefix) of the tag, this is
* usually what you're looking for.
* @param attribs The tags attribute reference.
* @throws SAXException if something wired happens. */
@Override
public void startElement(String uri, String localName, String qName,
Attributes attribs) throws SAXException
{
// in skip-mode, we just follow structure until end-tag
if (skiplevel != 0)
{
skiplevel++;
return;
}
// add element to processing stack
TagStack.add(qName);
// let derived class handle element
if (XMLstart(uri, localName, qName, attribs))
return;
// we should skip this tag, set skip-level
skiplevel = 1;
}

/*********************************************************************
* Handler called by the SAX2 event management each time a tag is
* closed. It is also called for empty tags.
* @param uri The namespace URI, as specified by xmlns-attribute.
* @param localName The local name (without prefix), or the empty
* string if namespace processing is not being performed.
* @param qName The qualified name (with prefix) of the tag, this is
* usually what you're looking for. */
@Override
public void endElement(String uri, String name, String qName)
{
// in skip-mode, we just follow structure until end-tag
if (skiplevel != 0)
{
if (skiplevel-->1)
return;
// end-tag of skipped tag reached, leave skip-mode
skiplevel = 0;
}
// let derived class handle element
XMLend(uri, name, qName);
// remove element from processing stack
int iLast = TagStack.size() - 1;
if (iLast >= 0)
TagStack.remove(iLast);
}

/*********************************************************************
* Handler called by the SAX2 event management for all non-markup-
* events. Due to formating of the XML-file, it may sometimes
* completely consist of white-spaces.
* @param ch - The characters.
* @param start - The start position in the character array.
* @param length - Number of chars to use from the array.*/
@Override
public void characters(char ch[], int start, int length)
{
if (skiplevel != 0)
return;
XMLchars(String.copyValueOf(ch, start, length));
}

/*********************************************************************
* Handler called by the SAX2 event management at the end of the
* parsed document. */
@Override
public void endDocument() throws SAXException
{
// let derived class do final processing
XMLfinal();
}

/*********************************************************************
* As JM3 only uses slashes we provide this function to have a system
* independent way of getting folder paths.
* @param file The full path and name of the file.
* @param convert Set to true if you want the path to only use slashes
* as separator.
* @return The Path to the folder. */
public static String getFolder(String file, boolean convert)
{
String strFolder = "";
if (convert)
file = file.replace(File.separatorChar, '/');
// standard behavior
int idx = file.lastIndexOf('/');
if (idx > 0 && idx != file.length() - 1)
strFolder = file.substring(0, idx+1);
if (!convert || strFolder.length() > 0
|| File.separatorChar == '/')
return strFolder;
// check for system dependent separator char
idx = file.lastIndexOf(File.separatorChar);
if (idx > 0 && idx != file.length() - 1)
strFolder = file.substring(0, idx+1);
return strFolder;
}

[/java]

Thanks, so it works with the .mesh.xml and .skeleton.xml loaders?

I have just tested it with custom extension (*.dmft), now i’ll test what happens if i set it up with *.xml

epic crash expecting

IT WORKS.

Forgive me, can someone explain to me the purpose of this class. I have two theories which can be totally wrong but here they go.



Are you guys trying to read from XML and have it create an object which is mapped to the XML? If this is the case, I know c# has something that does this, with java being similar it might already be available. The c# api calls allow you to go from object to xml and vice versa.



or



Are you guys just setting up an abstract class to make it easy to parse xml files? If this is the case why not just use Xpath?



I am not just understanding whats going on and would like to know so I can determine if this would be useful to me in my project :slight_smile:



thanks guys

in JME3, you have an AssetManager that is something like an abstract layer between your application and your textures, images, sounds, etc. That AssetManager calls specific loader functions (you normally dont want to load an audio-file with a png-loader, even if listening to alpha-channel may be worth a try;-))



This class provides an generic approach to load an xml-format file. For example, i have all my textures defined in an xml-tileset-file, and as the AssetManager shall load those Textures, its just fair to let it also load the TileSet before.



About Xpath: Yes, one could do that, but normally an application will store its information in a plain way, without XSLT, recursive entities and other fancy stuff inside. SAX2 was more than enough for what i needed, but hey, no one will stop you from writing an Xpath based xml-loader for the AssetManager.



But i’ll bet mine gets 10 textures loaded in the time yours loaded one;-) OK, perhaps just only two.

I understand now what is going on. I wasn’t familiar with the differences between SAX and xpath, so I just did a search and it answered my question. Normally when I have to use xml to load things (in a non game environment) its going to be only once on initialize and xpath makes it simple. Id imagine depending on your game you may want to dynamically load assets and then xpath wouldn’t be the recommended choice.



In case anyone was interested, while I was looking up SAX vs Xpath I found the library which does the Object → XML and XML->object in java



http://jaxb.java.net/



EDIT: Full post on the topic

http://stackoverflow.com/questions/35785/xml-serialization-in-java

New version: allowing easy assetmanager interaction, providing assetinfo and allowing for use of system-defined file-separator additionally to common slash