Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.imsglobal.lti.XMLMap Maven / Gradle / Ivy
Go to download
BasicLTI Utilities are a set of utility classes to aid in the development of BasicLTI consumers and
providers. They deal with much of the heavy lifting and make the process more opaque to the developer.
/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/lti/trunk/lti-util/src/java/org/imsglobal/lti/XMLMap.java $
* $Id: XMLMap.java 308965 2014-04-29 06:37:09Z [email protected] $
**********************************************************************************
*
* Copyright (c) 2009 IMS GLobal Learning Consortium, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
**********************************************************************************/
package org.imsglobal.lti;
/*
* This is a little project I call mdom.org which stands for "Map-Dom" or "XML Doms in Maps"
* or "XML Documents meet Java Maps" - clearly there is homage to XPath style parsing in the
* formation of the keys in the Maps - but XPath is not used.
*
* It is my attempt to build a simple, self-contained, static class to make XML parsing
* REALLY simple in Java - the idea is to approximate the ease of taking apart and
* putting together chunks of XML in languages like Perl, Python, PHP, and Ruby.
* While the typed nature of Java makes it so there is a little extra syntax, it has
* been reduced to some pretty simple stuff with really forgiving calls.
*
* This has had several names - initially it is called XMLMap - these are pre-release
* versions ad I leave copies of this around as I move through projects and use
* the software. When I get serious, I will distribute this as org.mdom.MDom and
* distribute it as a jar with versions and all that.
*
* It is really easy to take apart XML as long as there are no repeated elements.
* The function getMap returns a map of keys and values - the keys are the path
* starting at the root node of the XML and the value is the element stored inside.
* You can get attributes as well. This example:
*
*
* B
*
* D
*
*
*
* Map xmlMap = XMLMap.getMap("B D ");
*
* Ends up with the following in the map:
*
* /a/b!x = X
* /a/b = B
* /a/c/d = D
*
* You simply use the hash get method to pull out the information.
*
* System.out.println(xmlMap.get("/a/b"))
*
* Once it is parsed into the Map - everything is quick and simple.
*
* Things are similar in creating XML - You make a TreeMap and put entries in using put()
*
* Map newMap = new TreeMap;
* newMap.put("/a/b!x","X");
* newMap.put("/a/b", "B");
* newMap.put("/a/b/c", "C");
* String newXml = XMLMap.getXML(simpleMap, true);
*
* Another technique is the concept of submaps - you can extract a submap from a Map and then
* graft it directly onto some other bit of XML.
*
* Map subMap = XMLMap.selectSubMap(tm, "/a/c");
* Map joinedMap = new TreeMap();
* System.out.println("subMap="+subMap);
* joinedMap.put("/top/id", "1234");
* joinedMap.put("/top/fun", subMap); // Graft the map onto this node
* String joinedXml = XMLMap.getXML(joinedMap, true);
* System.out.println("joinedXML\n"+joinedXml);
*
* Produces this XML:
*
*
*
* D
*
* 1234
*
*
* The portion "below" /a/c was extracted and grafted onto the new XML at /top/fun.
* You can mix strings and Maps in the same map and you can have maps within maps.
* Once you switch to the Map you can even add an array of strings to an entry.
*
* Map arrayMap = new TreeMap();
* String [] strar = { "first", "second", "third" };
* arrayMap.put("/root/stuff", strar);
* String arrayXml = XMLMap.getXML(arrayMap, true);
*
* Produces:
*
*
* first
* second
* third
*
*
* The other major concept is how we parse XML and handle multiple items - such as in an RSS feed.
* When faced with a string of XML where you expect to get sets of items you need to parse the XML
* and get a "full" map - in this case, when the XMLMap parser sees multiple peer child nodes it
* returns a List> in the entry. This makes getting lists of Maps realy easy
* but can make the basic looking things up in the map a little harder. There are two approaches to
* this - you can either flatten the map or use the getString method to pull out all of the strings.
* The getString method does not "go into" any lists of maps - flattening does flatten through
* lists of maps, picking the first element of each list.
*
* Here is a way to look up single elements in a Full Map :
*
* Map rssFullMap = XMLMap.getFullMap(rssText);
* System.out.println("Rss Version="+XMLMap.getString(rssFullMap,"/rss!version"));
* System.out.println("Chan-title="+XMLMap.getString(rssFullMap,"/rss/channel/title"));
*
* Here is how you flatten the Map int a Map and use get to lookup
*
* Map rssStringMap = XMLMap.flattenMap(rssFullMap);
* System.out.println("Rss Version="+rssStringMap.get("/rss!version"));
* System.out.println("Chan-title="+rssStringMap.get("/rss/channel/title"));
*
* Iterating through a Full Map is pretty easy:
* for ( Map rssItem : XMLMap.getList(rssFullMap,"/rss/channel/item")) {
* System.out.println("=== Item ===");
* System.out.println(" Item-title="+XMLMap.getString(rssItem, "/title"));
* }
*
* If you have nested sets of elements - you will get back a List> that can
* also be iterated. In this example, we get a list of sites, then each site has a list of tools
* and each tool has a list of properties. The getList() method returns empty lists so
* that this code works even if the elements are not present or empty - the loops simply
* iterate zero times:
*
* Map theMap = XMLMap.getFullMap(bob);
* List> theList = XMLMap.getList(theMap, "/sites/site");
* for ( Map siteMap : theList) {
* System.out.println("Id="+XMLMap.getString(siteMap,"/id"));
* for ( Map toolMap : XMLMap.getList(siteMap,"/tools/tool")) {
* System.out.println("ToolId="+XMLMap.getString(toolMap,"/toolid"));
* for ( Map property : XMLMap.getList(toolMap, "/properties/property")) {
* System.out.println("key="+XMLMap.getString(property, "/key"));
* System.out.println("val="+XMLMap.getString(property, "/val"));
* }
* }
* }
*
* You can retrieve an element within a site using getString() and then iterate through the
* sub-elements using getList().
*
* There is a convenient variation of the getList() method which takes a String which combines
* the making of the map and retrieving of the list is you have no other use for the map:
*
* for ( Map siteMap : XMLMap.getList(bob,"/sites/site")) {
* System.out.println("Id="+XMLMap.getString(siteMap,"/id"));
* ...
* }
*
* This class has static unit tests built in and a static main that can run the sample code and produce
* output. This is to insure that the jar file is 100% Self-contained.
*
* TO DO:
*
*/
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.Iterator;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
/**
* a simple utility class for REST style XML
* kind of lets us act like we are in PHP.
*/
public class XMLMap {
private static boolean DF = false;
public static Map getMap(String str)
{
if ( str == null ) return null;
Document doc = documentFromString(str);
if ( doc == null ) return null;
return getMap(doc);
}
public static Map getMap(Node doc)
{
Map tm = getObjectMap(doc, false);
if ( tm == null ) return null;
return flattenMap(tm);
}
public static Map flattenMap(Map theMap)
{
if ( theMap == null ) return null;
// Reduce to the first column of elements for the simple return value
TreeMap retval = new TreeMap ();
Iterator iter = theMap.keySet().iterator();
while( iter.hasNext() ) {
String key = iter.next();
Object value = theMap.get(key);
// No need to handle String[] - because they will not
// be stored when doFull == false
if ( value instanceof String ) {
String svalue = (String) value;
// doDebug(d,key+" = " + value);
if ( value != null ) retval.put(key,svalue);
}
}
return retval;
}
public static Map getFullMap(Node doc)
{
return getObjectMap(doc, true);
}
public static Map getFullMap(String str)
{
if ( str == null ) return null;
Document doc = documentFromString(str);
if ( doc == null ) return null;
return getObjectMap(doc, true);
}
private static Map getObjectMap(Node doc, boolean doFull)
{
if ( doc == null ) return null;
Map tm = new TreeMap();
recurse(tm, "", doc, doFull,0);
return tm;
}
// A Utility Method we expose so folks can reuse if they like
public static Document documentFromString(String input)
{
try{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder parser = factory.newDocumentBuilder();
Document document = parser.parse(new ByteArrayInputStream(input.getBytes()));
return document;
} catch (Exception e) {
return null;
}
}
private static String addSlash(String path)
{
if ( path == null ) return "/";
if ( path.trim().equals("/") ) return "/";
return path + "/";
}
@SuppressWarnings({ "unused", "static-access" })
private static void recurse(Map tm, String path, Node parentNode, boolean doFull, int d)
{
if ( DF ) doDebug(d,"> recurse path="+path+" parentNode="+ nodeToString(parentNode));
d++;
NodeList nl = parentNode.getChildNodes();
NamedNodeMap nm = parentNode.getAttributes();
// Count the TextNodes
int nodeCount = 0;
String value = null;
// Insert the text node if we find one
if ( nl != null ) for (int i = 0; i< nl.getLength(); i++ ) {
Node node = nl.item(i);
if (node.getNodeType() == node.TEXT_NODE) {
value = node.getNodeValue();
if ( value == null ) break;
if ( value.trim().length() < 1 ) break;
// doDebug(d,"Adding path="+path+" value="+node.getNodeValue());
tm.put(path,node.getNodeValue());
break; // Only the first one
}
}
// Now loop through and add the attribute values
if ( nm != null ) for (int i = 0; i< nm.getLength(); i++ ) {
Node node = nm.item(i);
if (node.getNodeType() == node.ATTRIBUTE_NODE) {
String name = node.getNodeName();
value = node.getNodeValue();
// doDebug(d,"ATTR "+path+"("+name+") = "+node.getNodeValue());
if ( name == null || name.trim().length() < 1 ||
value == null || value.trim().length() < 1 ) continue;
String newPath = path+"!"+name;
tm.put(newPath,value);
}
}
// If we are not doing the full DOM - we only traverse the first child
// with the same name - so we use a set to record which nodes
// we have gone down.
if ( ! doFull ) {
// Now descend the tree to the next level deeper !!
Set done = new HashSet();
if ( nl != null ) for (int i = 0; i< nl.getLength(); i++ ) {
Node node = nl.item(i);
if (node.getNodeType() == node.ELEMENT_NODE && ( ! done.contains(node.getNodeName())) ) {
if ( DF ) doDebug(d,"Going down the rabbit hole path="+path+" node="+node.getNodeName());
recurse(tm, addSlash(path)+node.getNodeName(),node,doFull,d);
if ( DF ) doDebug(d,"Back from the rabbit hole path="+path+" node="+node.getNodeName());
done.add(node.getNodeName());
}
}
d--;
if ( DF ) doDebug(d,"< recurse path="+path+" parentNode="+ nodeToString(parentNode));
return;
}
// If we are going to do the full expansion - we need to know when
// There are more than one child with the same name. If there are more
// One child, we make list of Maps.
Map childMap = new TreeMap();
if ( nl != null ) for (int i = 0; i< nl.getLength(); i++ ) {
Node node = nl.item(i);
if (node.getNodeType() == node.ELEMENT_NODE ) {
Integer count = childMap.get(node.getNodeName());
if ( count == null ) count = new Integer(0);
count = count + 1;
// Insert or Replace
childMap.put(node.getNodeName(), count);
}
}
if ( childMap.size() < 1 ) return;
// Now go through the children nodes and make a List of Maps
Iterator iter = childMap.keySet().iterator();
Map>> nodeMap = new TreeMap>>();
while ( iter.hasNext() ) {
String nextChild = iter.next();
if ( nextChild == null ) continue;
Integer count = childMap.get(nextChild);
if ( count == null ) continue;
if ( count < 2 ) continue;
if ( DF ) doDebug(d,"Making a List for "+nextChild);
List> newList = new ArrayList>();
nodeMap.put(nextChild,newList);
}
// Now descend the tree to the next level deeper !!
if ( nl != null ) for (int i = 0; i< nl.getLength(); i++ ) {
Node node = nl.item(i);
if (node.getNodeType() == node.ELEMENT_NODE ) {
String childName = node.getNodeName();
if ( childName == null ) continue;
List> mapList = nodeMap.get(childName);
if ( mapList == null ) {
if ( DF ) doDebug(d,"Going down the single rabbit hole path="+path+" node="+node.getNodeName());
recurse(tm, addSlash(path)+node.getNodeName(),node,doFull,d);
if ( DF ) doDebug(d,"Back from the single rabbit hole path="+path+" node="+node.getNodeName());
} else {
if ( DF ) doDebug(d,"Going down the multi rabbit hole path="+path+" node="+node.getNodeName());
Map newMap = new TreeMap();
recurse(newMap,"/",node,doFull,d);
if ( DF ) doDebug(d,"Back from the multi rabbit hole path="+path+" node="+node.getNodeName()+" map="+newMap);
if ( newMap.size() > 0 ) mapList.add(newMap);
}
}
}
// Now append the multi-node maps to our current map
Iterator iter2 = nodeMap.keySet().iterator();
while ( iter2.hasNext() ) {
String nextChild = iter2.next();
if ( nextChild == null ) continue;
List> newList = nodeMap.get(nextChild);
if ( newList == null ) continue;
if ( newList.size() < 1 ) continue;
if ( DF ) doDebug(d,"Adding sub-map name="+nextChild+" list="+newList);
tm.put(path+"/"+nextChild, newList);
}
d--;
if ( DF ) doDebug(d,"< recurse path="+path+" parentNode="+ nodeToString(parentNode));
}
public static String getXML(Map, ?> tm)
{
Document document = getXMLDom(tm);
if ( document == null ) return null;
return documentToString(document, false);
}
public static String getXMLFragment(Map, ?> tm, boolean pretty)
{
String retval = getXML(tm, pretty);
if ( retval.startsWith(" 0 ) retval = retval.substring(pos);
}
return retval;
}
public static String getXML(Map, ?> tm, boolean pretty)
{
Document document = getXMLDom(tm);
if ( document == null ) return null;
String retval = documentToString(document, pretty);
// Since the built in transform seems unable to indent
// We patch it ourselves to keep from being ugly
if ( pretty ) {
retval = prettyPostProcess(retval);
}
return retval;
}
// This process a pretty print from an input string -
// It does it the hard way - using the methods in this class.
// It may not be the ideal way to pretty print a XML String
// but it is our way and we want to be D.R.Y. here...
// As such you may see some error messages from
// the XMLMap class in the pretty printing.
public static String prettyPrint(String input)
{
Map theMap = XMLMap.getFullMap(input);
return XMLMap.getXML(theMap, true);
}
private static String prettyPostProcess(String inString)
{
StringBuffer sb = new StringBuffer();
int depth = 0;
boolean newLine = false;
for (int i=0; i tm)
{
if ( tm == null ) return null;
Document document = null;
try{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder parser = factory.newDocumentBuilder();
document = parser.newDocument();
} catch (Exception e) {
return null;
}
iterateMap(document, document.getDocumentElement(), tm, 0);
return document;
}
/* Remember that the map is a linear list of entries
/a/b B1
/a/c Map
/x X1
/y Y1
/y!r R1
/a/c!q Q1
/a/d D1
B1
X1
Y1
D1
*/
private static void iterateMap(Document document, Node parentNode, Map, ?> tm, int d)
{
if ( DF ) doDebug(d,"> IterateMap parentNode= "+ nodeToString(parentNode));
d++;
Iterator> iter = tm.keySet().iterator();
while( iter.hasNext() ) {
String key = (String) iter.next();
if ( key == null ) continue;
if ( ! key.startsWith("/") ) continue; // Skip
Object obj = tm.get(key);
if ( obj == null ) {
continue;
} if ( obj instanceof String ) {
storeInDom(document, parentNode, key, (String) obj, 0, d);
} else if ( obj instanceof String [] ) {
String [] strArray = (String []) obj;
if ( DF ) doDebug(d,"Looping through an array of length "+strArray.length);
for(int i=0; i < strArray.length; i++ ) {
storeInDom(document, parentNode, key, strArray[i], i, d);
}
} else if ( obj instanceof Map ) {
Map, ?> subMap = (Map, ?>) obj;
Node startNode = getNodeAtPath(document, parentNode, key, 0, d);
if ( DF ) doDebug(d,"descending into Map path="+key+" startNode="+ nodeToString(startNode));
iterateMap(document, startNode, subMap, d);
if ( DF ) doDebug(d,"back from descent Map path="+key+" startNode="+ nodeToString(startNode));
} else if ( obj instanceof List ) {
List> lst = (List>) obj;
if ( DF ) doDebug(d,"Have a list that is this long "+lst.size());
Iterator> listIter = lst.iterator();
int newPos = 0;
while ( listIter.hasNext() ) {
Object listObj = listIter.next();
if ( DF ) doDebug(d,"Processing List element@"+newPos+" "+listObj.getClass().getName());
if ( listObj instanceof String ) {
storeInDom(document, parentNode, key, (String) listObj, newPos, d);
newPos++;
} if ( listObj instanceof Map ) {
Map, ?> subMap = (Map, ?>) listObj;
if ( DF ) doDebug(d,"Retrieving key from List-Map path="+key+"@"+newPos);
Node startNode = getNodeAtPath(document, parentNode, key, newPos, d);
if ( DF ) doDebug(d,"descending into List-Map path="+key+"@"+newPos+" startNode="+ nodeToString(startNode));
iterateMap(document, startNode, subMap, d);
if ( DF ) doDebug(d,"back from descent List-Map path="+key+"@"+newPos+" startNode="+ nodeToString(startNode));
newPos++;
} else {
System.out.println("XMLMap Encountered an object of type "+obj.getClass().getName()+" in a List which should contain only Map objects");
}
}
} else {
if ( DF ) doDebug(d,"Found a "+obj.getClass().getName()+" do not know how to iterate.");
}
}
d--;
if ( DF ) doDebug(d,"< IterateMap parentNode = "+ nodeToString(parentNode));
}
private static void storeInDom(Document document, Node parentNode, String key, String value, int nodePos, int d)
{
if ( DF ) doDebug(d,"> storeInDom"+key+"@"+ nodePos + " = " + value + " parent="+ nodeToString(parentNode));
d++;
if ( document == null || key == null || value == null ) return;
if ( parentNode == null ) parentNode = document;
if ( DF ) doDebug(d,"parentNode I="+ nodeToString(parentNode));
String [] newPath = key.split("/");
if ( DF ) doDebug(d,"newPath = "+outStringArray(newPath));
String nodeAttr = null;
for ( int i=1; i< newPath.length; i++ )
{
String nodeName = newPath[i];
if ( i == newPath.length-1 ) {
// doDebug(d,"Splitting !="+nodeName);
// check to see if we have a nodename=attributename
String [] nodeSplit = nodeName.split("!");
if ( nodeSplit.length > 1 ) {
nodeName = nodeSplit[0];
nodeAttr = nodeSplit[1];
// doDebug(d,"new nodeName="+nodeName+" nodeAttr="+nodeAttr);
}
parentNode = getOrAddChildNode(document, parentNode, nodeName, nodePos, d);
} else {
parentNode = getOrAddChildNode(document, parentNode, nodeName, 0, d);
}
}
// doDebug(d,"parentNode after="+ nodeToString(parentNode));
if ( nodeAttr != null )
{
if ( value!= null && parentNode instanceof Element )
{
Element element = (Element) parentNode;
// doDebug(d,"Adding an attribute "+nodeAttr);
element.setAttribute(nodeAttr,value);
}
}
else if ( value != null )
{
Text newNode = document.createTextNode(value);
parentNode.appendChild(newNode);
}
d--;
// doDebug(d,"xml="+documentToString(document,false));
// doDebug(d,"< storeInDom"+key+" = " + value);
}
// Note - sadly this does not "return" the attr name - hence we need
// to replicate this code in storeInDom :(
private static Node getNodeAtPath(Document document, Node parentNode, String path, int nodePos, int d)
{
if ( parentNode == null ) parentNode = document;
if ( DF ) doDebug(d,"> getNodeAtPath path@" + nodePos + "="+path+" parentNode="+ nodeToString(parentNode));
d++;
String [] newPath = path.split("/");
// doDebug(d,"newPath = "+outStringArray(newPath));
for ( int i=1; i< newPath.length; i++ )
{
String nodeName = newPath[i];
if ( i == newPath.length-1 ) {
// doDebug(d,"Splitting !="+nodeName);
// check to see if we have a nodename=attributename
String [] nodeSplit = nodeName.split("!");
if ( nodeSplit.length > 1 ) {
nodeName = nodeSplit[0];
// doDebug(d,"new nodeName="+nodeName);
}
parentNode = getOrAddChildNode(document, parentNode, nodeName, nodePos, d);
} else {
parentNode = getOrAddChildNode(document, parentNode, nodeName, 0, d);
}
}
d--;
if ( DF ) doDebug(d,"< getNodeAtPath returning="+ nodeToString(parentNode));
return parentNode;
}
@SuppressWarnings("static-access")
private static Node getOrAddChildNode(Document doc, Node parentNode, String nodeName,int whichNode, int d)
{
if ( DF ) doDebug(d,"> getOrAddChildNode name="+nodeName+"@"+whichNode+" parentNode="+ nodeToString(parentNode));
d++;
if ( nodeName == null || parentNode == null) return null;
// Check to see if we are somewhere in an index
int begpos = nodeName.indexOf('[');
int endpos = nodeName.indexOf(']');
// doDebug(d,"Looking for bracket ipos="+begpos+" endpos="+endpos);
if ( begpos > 0 && endpos > begpos && endpos < nodeName.length() ) {
String indStr = nodeName.substring(begpos+1,endpos);
if ( DF ) doDebug(d,"Index String = "+ indStr);
nodeName = nodeName.substring(0,begpos);
if ( DF ) doDebug(d,"New Nodename="+nodeName);
Integer iVal = new Integer(indStr);
if ( DF ) doDebug(d,"Integer = "+iVal);
whichNode = iVal;
}
NodeList nl = parentNode.getChildNodes();
int foundNodes = -1;
if ( nl != null ) for (int i = 0; i< nl.getLength(); i++ ) {
Node node = nl.item(i);
// doDebug(d,"length= " +nl.getLength()+ " i="+i+" NT="+node.getNodeType());
// doDebug(d,"searching nn="+nodeName+" nc="+node.getNodeName());
if (node.getNodeType() == node.ELEMENT_NODE) {
if ( nodeName.equals(node.getNodeName()) ) {
foundNodes++;
d--;
if ( DF ) doDebug(d,"< getOrAddChildNode found name="+ nodeToString(node));
if ( DF ) doDebug(d,"foundNodes = "+foundNodes+" looking for node="+whichNode);
if ( foundNodes >= whichNode ) return node;
}
}
}
Element newNode = null;
while ( foundNodes < whichNode ) {
foundNodes++;
if ( DF ) doDebug(d,"Adding node at position " + foundNodes + " moving toward " + whichNode);
if ( nodeName == null ) continue;
newNode = doc.createElement(nodeName);
if ( DF ) doDebug(d,"Adding "+nodeName+" at "+ nodeToString(parentNode)+" in "+doc);
parentNode.appendChild(newNode);
if ( DF ) doDebug(d,"xml="+documentToString(doc,false));
if ( DF ) doDebug(d,"getOrAddChildNode added newnode="+ nodeToString(newNode));
}
d--;
if ( DF ) doDebug(d,"< getOrAddChildNode added newnode="+ nodeToString(newNode));
return newNode;
}
public static String outStringArray(String [] arr)
{
if ( arr == null ) return null;
StringBuffer sb = new StringBuffer();
for (int i = 0; i < arr.length; i++ ) {
if ( i > 0 ) sb.append(" ");
sb.append("["+i+"]=");
sb.append(arr[i]);
}
return sb.toString();
}
public static String nodeToString(Node node)
{
if ( node == null ) return null;
String retval = node.getNodeName();
while ( (node = node.getParentNode()) != null ) {
retval = node.getNodeName() + "/" + retval;
}
return "/" + retval;
}
// Optionally setup indenting to "pretty print"
// Note - this is not very pretty at least in my testing - but it is better
// than all string together
public static String documentToString(Document document, boolean pretty)
{
return nodeToString(document, pretty);
}
// Optionally setup indenting to "pretty print"
// Note - this is not very pretty at least in my testing - but it is better
// than all string together
public static String nodeToString(Node node, boolean pretty)
{
try {
javax.xml.transform.Transformer tf =
javax.xml.transform.TransformerFactory.newInstance().newTransformer();
if ( pretty ) {
tf.setOutputProperty(OutputKeys.INDENT, "yes");
tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
}
ByteArrayOutputStream baStream = new ByteArrayOutputStream();
tf.transform (new javax.xml.transform.dom.DOMSource (node),
new javax.xml.transform.stream.StreamResult (baStream));
return baStream.toString();
} catch (javax.xml.transform.TransformerException e) {
return null;
}
}
// Someone better at Generics can yell at me as to how this should have been
// done to use the same code for either objects or strings. Sorry.
public static Map selectSubMap(Map sm, String selection)
{
if ( sm == null ) return null;
selection = selection.trim();
if ( badSelection(selection) ) return null;
Map retval = new TreeMap();
selectSubMap(sm, retval, null, null, selection);
return retval;
}
public static Map selectFullSubMap(Map om, String selection)
{
if ( om == null ) return null;
selection = selection.trim();
if ( badSelection(selection) ) return null;
Map retval = new TreeMap();
selectSubMap(null, null, om, retval, selection);
return retval;
}
private static boolean badSelection(String selection)
{
if ( selection == null ) return true;
if ( selection.equals("/") ) return true;
if ( selection.length() < 2 ) return true;
if ( ! selection.startsWith("/") ) return true;
return false;
}
private static void selectSubMap(Map sm, Map sret,
Map om, Map oret, String selection)
{
Iterator iter = null;
if ( sm != null ) {
iter = sm.keySet().iterator();
} else {
iter = om.keySet().iterator();
}
while( iter.hasNext() ) {
String key = iter.next();
boolean match = false;
String newKey = null;
if ( key.equals(selection) ) {
match = true;
newKey = "/";
} else if ( selection.endsWith("/") && key.startsWith(selection)) {
match = true;
newKey = key.substring(selection.length()-1);
} else if ( key.startsWith(selection+"/") ) {
match = true;
newKey = key.substring(selection.length());
} else if ( key.startsWith(selection+"!") ) {
match = true;
newKey = "/" + key.substring(selection.length());
}
if ( ! match ) continue;
// doDebug(d,"newKey = "+newKey);
if ( sm != null ) {
String value = sm.get(key);
if ( value == null ) continue;
sret.put(newKey,value);
// doDebug(d,newKey+" = " + value);
} else {
Object value = om.get(key);
if ( value == null ) continue;
oret.put(newKey,value);
// doDebug(d,newKey+" = " + value);
}
}
}
/*
* Remove a submap. Depending if the string ends ina slash - there are
* two behaviors.
* /x/y/ All of the children are removed but the node is left intact
* /x/y All of the children are removed and the node itself and
* any attributes are removed as well (typical case)
*/
public static void removeSubMap(Map, ?> tm, String selection)
{
if ( tm == null ) return;
selection = selection.trim();
if ( badSelection(selection) ) return;
// If the selection does not end with /, generate the
// Attribute and children selections
selection = selection.trim();
String childSel = selection;
String attrSel = selection;
if ( ! selection.endsWith("/") ) {
childSel = selection + "/";
attrSel = selection + "!";
}
// Track what we will delete until loop is done
Set delSet = new HashSet();
Iterator> iter = tm.keySet().iterator();
while( iter.hasNext() ) {
Object key = iter.next();
if ( ! (key instanceof String) ) continue;
String strKey = (String) key;
if ( strKey.equals(selection) || strKey.startsWith(childSel) || strKey.startsWith(attrSel)) {
delSet.add(strKey);
// System.out.println("Deleting key="+key);
}
}
// Actually remove...
Iterator setIter = delSet.iterator();
while( setIter.hasNext() ) {
String key = setIter.next();
tm.remove(key);
}
}
private static void doDebug(int d, String str) {
if ( ! DF ) return;
for(int i=0; i theMap, String key)
{
if ( theMap == null ) return null;
Object obj = theMap.get(key);
if ( obj == null ) return null;
if ( obj instanceof String ) return (String) obj;
return null;
}
/* This goes to a set of nodes that are intended to be multiple nodes and returns a list whether there
* are one or many nodes.
*
*
*
* p1key p1val
*
*
* p2akey p2aval
* p2bkey p2bval
*
*
*
* List> p1s = XMLMap.getList(mnop,"/abc/p1s/p1");
* List> p2s = XMLMap.getList(mnop,"/abc/p2s/p2");
*
* Always return a list even if it is just an empty list so this code works:
*
* for ( Map siteMap : XMLMap.getList(mnop,"/sites/site")) {
* System.out.println("Site="+siteMap);
* }
*/
@SuppressWarnings("unchecked")
public static List> getList(Map theMap,String key)
{
ArrayList> al = new ArrayList>();
if ( theMap == null || key == null ) return al;
// If this is a nice little list of maps - we are golden - send the list back
Object obj = theMap.get(key);
if ( obj instanceof List ) return (List>) obj;
// We may have a single String value - we may have a single terminal value
// perhaps with some attributes
//
//
// 1300
//
//
// See if there is one sub map there...
Map oneMap = selectFullSubMap(theMap, key);
// System.out.println("One submap = "+oneMap);
if ( oneMap == null ) return al;
// If the map is not empty - return am empty list
// rather than a one element list with an empty map
if ( oneMap.isEmpty() ) return al;
// Make a list of one submap...
al.add(oneMap);
return al;
}
// Note that getList with the first parameter to getList is a String, it does a
// getMap and then a getList with that Map - this allows the following
// rather dense code:
// for ( Map siteMap : XMLMap.getList(xmlString,"/sites/site")) {
// The long form of this looks as follows:
// Map theMap = XMLMap.getFullMap(xmlString);
// List> theList = XMLMap.getList(theMap, "/sites/site");
// for ( Map siteMap : theList) {
// The short form should only be used if this is the only time you will parse
// to get a FullMap - otherwise - get the FullMap once and pull out the different bits
// from the map without reparsing the xmlString.
public static List> getList(String xmlInput,String key)
{
Map tmpMap = XMLMap.getFullMap(xmlInput);
return XMLMap.getList(tmpMap,key);
}
/*
* Unit Tests - Keep these public in case folks want to call them when they are
* only in possession of a jar file - makes the jar file a bearer instrument at
* the cost of some extra space.
*/
public static boolean unitTest(String xmlString, boolean doDebug)
{
if ( xmlString == null ) return false;
DF = doDebug;
// If Debug is turned on - let the chips fly, exceptions and
// All...
if ( doDebug ) {
DF = true;
String pretty1 = XMLMap.prettyPrint(xmlString);
String pretty2 = XMLMap.prettyPrint(pretty1);
if ( pretty1.equals(pretty2) ) return true;
System.out.println("XMLMap - unit test failed");
return false;
}
// For Debug off - we first try it silently and in a try/catch block
DF = false;
try {
String pretty1 = XMLMap.prettyPrint(xmlString);
String pretty2 = XMLMap.prettyPrint(pretty1);
if ( pretty1.equals(pretty2) ) return true;
}
catch (Throwable t) {
// We will re-do below so folks see the trace back -
// in the context of debug
}
// If we failed - re-do it with verbose mode on
System.out.println("XMLMap - unit test failed");
System.out.println(xmlString);
DF = true;
String pretty1 = XMLMap.prettyPrint(xmlString);
System.out.println("Pretty Print Version pass 1\n"+pretty1);
String pretty2 = XMLMap.prettyPrint(pretty1);
System.out.println("Pretty Print Version pass 2\n"+pretty2);
DF = false; // Always reset class-wide variable
return false;
}
// Some Unit Test and sample Strings
private static final String simpleText = "B D ";
private static final String sitesText = " sue fred Title sakai.web.content p1key p1val p2key p2val sakai-wiki wikikey sakai-blog ";
private static final String rssText = "Dr-Chuck's Media Television Shows and other media http://www.dr-chuck.com/media.phpTrack Days with John Merlin Williams This film is about racing street Motorcyles. http://www.dr-chuck.comMotocross Racing Dr. Chuck comes in second to last and is covered with mud. http://www.dr-chuck.com/ ";
public static boolean allUnitTests() {
if ( !unitTest(simpleText, false) ) return false;
if ( !unitTest(sitesText, false) ) return false;
if ( !unitTest(rssText, false) ) return false;
return true;
}
public static void main(String[] args) {
System.out.println("Running XMLMap (www.mdom.org) unit tests..");
if ( !allUnitTests() ) return;
System.out.println("Unit tests passed...");
runSamples();
}
public static void runSamples() {
System.out.println("Running XMLMap (www.mdom.org) Samples...");
DF = false;
// Test the parsing of a Basic string Map
Map tm = XMLMap.getMap(simpleText);
// System.out.println("tm="+tm);
// Test the production of a basic map
Map simpleMap = new TreeMap();
simpleMap.put("/a/b!x", "X");
simpleMap.put("/a/b", "B");
simpleMap.put("/a/c/d", "D");
System.out.println("simpleMap\n"+simpleMap);
String simpleXml = XMLMap.getXML(simpleMap, true);
System.out.println("simpleXml\n"+simpleXml);
unitTest(simpleXml,false);
// Do a select of a subMap
Map subMap = XMLMap.selectSubMap(tm, "/a/c");
Map joinedMap = new TreeMap();
System.out.println("subMap="+subMap);
joinedMap.put("/top/id", "1234");
joinedMap.put("/top/fun", subMap); // Graft the map onto this node
System.out.println("joinedMap\n"+joinedMap);
String joinedXml = XMLMap.getXML(joinedMap, true);
System.out.println("joinedXML\n"+joinedXml);
unitTest(joinedXml,false);
// Do an Array
Map arrayMap = new TreeMap();
String [] arrayStr = { "first", "second", "third" };
arrayMap.put("/root/stuff", arrayStr);
System.out.println("arrayMap\n"+arrayMap);
String arrayXml = XMLMap.getXML(arrayMap, true);
System.out.println("arrayXml\n"+arrayXml);
unitTest(arrayXml,false);
// Make a Map that is a combination of Maps, String, and Arrays
Map newMap = new TreeMap();
newMap.put("/Root/milton","Root-milton");
newMap.put("/Root/joe","Root-joe");
Map m2 = new TreeMap();
m2.put("/fred/a","fred-a");
m2.put("/fred/b","fred-b");
newMap.put("/Root/freds", m2);
// Add a list of maps
//
//
//
// key-0
// val-0
//
//
// key-1
// val-1
//
//
//
List> lm = new ArrayList>();
Map m3 = null;
m3 = new TreeMap();
m3.put("/key","key-0");
m3.put("/val","val-0");
lm.add(m3);
m3 = new TreeMap();
m3.put("/key","key-1");
m3.put("/val","val-1");
lm.add(m3);
newMap.put("/Root/maps/map", lm);
// Add an array of Strings
//
// first
// second
// third
//
String [] strar = { "first", "second", "third" };
newMap.put("/Root/array", strar);
// Add a list of Maps - this is a bit of a weird application - mostly as a
// completeness test to insure lists of maps and arrays are equivalent. Also
// since the getFullMap returns maps, not Arrays of strings, this is necessary
// to insure symmetry - i.e. we can take a map structure we produce and
// regenerate the XML. Most users will not use this form in construction.
//
//
// - item-1
// - item-2
//
List> l1 = new ArrayList>();
Map m4 = new TreeMap();
m4.put("/", "item-1");
l1.add(m4);
Map m5 = new TreeMap();
m5.put("/", "item-2");
l1.add(m5);
newMap.put("/Root/item", l1);
// Put in using the XMLMap bracket Syntax - not a particularly good
// Way to represent multiple items - it is just here for completeness.
newMap.put("/Root/anns/ann[0]","Root-ann[0]");
newMap.put("/Root/anns/ann[1]","Root-ann[1]");
newMap.put("/Root/bobs/bob[0]/key","Root-bobs-bob[0]-key");
newMap.put("/Root/bobs/bob[0]/val","Root-bobs-bob[0]-val");
newMap.put("/Root/bobs/bob[1]/key","Root-bobs-bob[1]-key");
newMap.put("/Root/bobs/bob[1]/val","Root-bobs-bob[1]-val");
// This is not allowed because maps cannot have duplicates
/*
Map m6 = new TreeMap();
m5.put("/two", "two-1");
m5.put("/two", "two-2");
newMap.put("/Root", m6);
*/
// Take the Map - turn it into XML and then parse the returned
// XML into a second map - take the second map and produce more XML
// If all goes well, the two generated blobs of XML should be the
// same. If anything goes wrong - we re-do it with lots of debug
String complexXml = null;
boolean success = false;
DF = false;
try {
complexXml = XMLMap.getXML(newMap, true);
success = true;
} catch(Exception e) {
success = false;
}
// If we fail - do it again with deep levels of verbosity
if ( success ) {
unitTest(complexXml,false);
} else {
DF = true;
System.out.println("\n MISMATCH AND/OR SOME ERROR HAS OCCURED - REDO in VERBODE MODE");
System.out.println("Starting out newMap="+newMap);
complexXml = XMLMap.getXML(newMap, true);
unitTest(complexXml,false);
DF = false;
}
// A different example - iterating through nested sets - demonstrating the short form
// of getSites() with the first parameter a string -the commented code below is the long form.
// Map theMap = XMLMap.getFullMap(sitesText);
// List> theList = XMLMap.getList(theMap, "/sites/site");
// for ( Map siteMap : theList) {
// The short form using convenience method if you don't need the map for anything else
System.out.println("\nParsing Sites Structure");
for ( Map siteMap : XMLMap.getList(sitesText,"/sites/site")) {
System.out.println("Site="+siteMap);
System.out.println("Id="+XMLMap.getString(siteMap,"/id"));
for ( Map toolMap : XMLMap.getList(siteMap,"/tools/tool")) {
System.out.println("Tool="+toolMap);
System.out.println("ToolId="+XMLMap.getString(toolMap,"/toolid"));
for ( Map property : XMLMap.getList(toolMap, "/properties/property")) {
System.out.println("key="+XMLMap.getString(property, "/key"));
System.out.println("val="+XMLMap.getString(property, "/val"));
}
}
}
// Lets parse some RSS as a final kind of easy but quite practical test
DF = false;
System.out.println("\nParsing RSS Feed");
// System.out.println(XMLMap.prettyPrint(rssText));
Map rssFullMap = XMLMap.getFullMap(rssText);
System.out.println("RSS Full Map\n"+rssFullMap);
System.out.println("Rss Version="+XMLMap.getString(rssFullMap,"/rss!version"));
System.out.println("Chan-desc="+XMLMap.getString(rssFullMap,"/rss/channel/description"));
System.out.println("Chan-title="+XMLMap.getString(rssFullMap,"/rss/channel/title"));
Map rssStringMap = XMLMap.flattenMap(rssFullMap);
System.out.println("RSS Flat String Only Map\n"+rssStringMap);
System.out.println("Rss Version="+rssStringMap.get("/rss!version"));
System.out.println("Chan-desc="+rssStringMap.get("/rss/channel/description"));
System.out.println("Chan-title="+rssStringMap.get("/rss/channel/title"));
for ( Map rssItem : XMLMap.getList(rssFullMap,"/rss/channel/item")) {
System.out.println("=== Item ===");
System.out.println(" Item-title="+XMLMap.getString(rssItem, "/title"));
System.out.println(" Item-description="+XMLMap.getString(rssItem, "/description"));
System.out.println(" Item-link="+XMLMap.getString(rssItem, "/link"));
}
}
}
/* Sample output from test run with lines wrapped a bit:
Running XMLMap (www.mdom.org) unit tests..
Unit tests passed...
Running XMLMap (www.mdom.org) Samples...
tm={/a/b=B, /a/b!x=X, /a/c/d=D}
simpleMap
{/a/b=B, /a/b!x=X, /a/c/d=D}
simpleXml
B
D
subMap={/d=D}
joinedMap
{/top/fun={/d=D}, /top/id=1234}
joinedXML
D
1234
arrayMap
{/root/stuff=[Ljava.lang.String;@6f50a8}
arrayXml
first
second
third
Parsing Sites Structure
Site={/id=sue}
Id=sue
Site={/id=fred, /title=Title, /tools/tool=[{/properties/property=[{/key=p1key, /val=p1val},
{/key=p2key, /val=p2val}], /toolid=sakai.web.content}, {/properties/property/key=wikikey,
/toolid=sakai-wiki}, {/toolid=sakai-blog}]}
Id=fred
Tool={/properties/property=[{/key=p1key, /val=p1val}, {/key=p2key, /val=p2val}], /toolid=sakai.web.content}
ToolId=sakai.web.content
key=p1key
val=p1val
key=p2key
val=p2val
Tool={/properties/property/key=wikikey, /toolid=sakai-wiki}
ToolId=sakai-wiki
key=wikikey
val=null
Tool={/toolid=sakai-blog}
ToolId=sakai-blog
Parsing RSS Feed
RSS Full Map
{/rss!version=2.0, /rss/channel/description=Television Shows and other media,
/rss/channel/item=[{/description=This film is about racing street Motorcyles.,
/link=http://www.dr-chuck.com, /title=Track Days with John Merlin Williams},
{/description=Dr. Chuck comes in second to last and is covered with mud.,
/link=http://www.dr-chuck.com/, /title=Motocross Racing}],
/rss/channel/link=http://www.dr-chuck.com/media.php, /rss/channel/title=Dr-Chuck's Media}
Rss Version=2.0
Chan-desc=Television Shows and other media
Chan-title=Dr-Chuck's Media
RSS Flat String Only Map
{/rss!version=2.0, /rss/channel/description=Television Shows and other media,
/rss/channel/link=http://www.dr-chuck.com/media.php, /rss/channel/title=Dr-Chuck's Media}
Rss Version=2.0
Chan-desc=Television Shows and other media
Chan-title=Dr-Chuck's Media
=== Item ===
Item-title=Track Days with John Merlin Williams
Item-description=This film is about racing street Motorcyles.
Item-link=http://www.dr-chuck.com
=== Item ===
Item-title=Motocross Racing
Item-description=Dr. Chuck comes in second to last and is covered with mud.
Item-link=http://www.dr-chuck.com/
*/