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.tsugi.basiclti.XMLMap Maven / Gradle / Ivy
/**********************************************************************************
* $URL$
* $Id$
**********************************************************************************
*
* Copyright (c) 2009-2016 Charles R. Severance
*
* 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.tsugi.basiclti;
/*
* 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.
*
* log.debug("{}", 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();
* log.debug("subMap={}", subMap);
* joinedMap.put("/top/id", "1234");
* joinedMap.put("/top/fun", subMap); // Graft the map onto this node
* String joinedXml = XMLMap.getXML(joinedMap, true);
* log.debug("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);
* log.debug("Rss Version={}", XMLMap.getString(rssFullMap,"/rss!version"));
* log.debug("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);
* log.debug("Rss Version={}", rssStringMap.get("/rss!version"));
* log.debug("Chan-title={}", rssStringMap.get("/rss/channel/title"));
*
* Iterating through a Full Map is pretty easy:
* for ( Map rssItem : XMLMap.getList(rssFullMap,"/rss/channel/item")) {
* log.debug("=== Item ===");
* log.debug(" 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) {
* log.debug("Id={}", XMLMap.getString(siteMap,"/id"));
* for ( Map toolMap : XMLMap.getList(siteMap,"/tools/tool")) {
* log.debug("ToolId={}", XMLMap.getString(toolMap,"/toolid"));
* for ( Map property : XMLMap.getList(toolMap, "/properties/property")) {
* log.debug("key={}", XMLMap.getString(property, "/key"));
* log.debug("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")) {
* log.debug("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 lombok.extern.slf4j.Slf4j;
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.
*/
@Slf4j
public class XMLMap {
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)
{
log.debug("> recurse path={} parentNode={}", path, 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;
log.debug("Adding path={} value={}", path, 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();
log.debug("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())) ) {
log.debug("Going down the rabbit hole path={} node={}", path, node.getNodeName());
recurse(tm, addSlash(path)+node.getNodeName(),node,doFull,d);
log.debug("Back from the rabbit hole path={} node={}", path, node.getNodeName());
done.add(node.getNodeName());
}
}
d--;
log.debug("< recurse path={} parentNode={}", path, 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;
log.debug("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 ) {
log.debug("Going down the single rabbit hole path={} node={}", path, node.getNodeName());
recurse(tm, addSlash(path)+node.getNodeName(),node,doFull,d);
log.debug("Back from the single rabbit hole path={} node={}", path, node.getNodeName());
} else {
log.debug("Going down the multi rabbit hole path={} node={}", path, node.getNodeName());
Map newMap = new TreeMap();
recurse(newMap,"/",node,doFull,d);
log.debug("Back from the multi rabbit hole path={} node={} map={}", path, node.getNodeName(), 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;
log.debug("Adding sub-map name={} list={}", nextChild, newList);
tm.put(path+"/"+nextChild, newList);
}
d--;
log.debug("< recurse path={} parentNode={}", path, 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)
{
log.debug("> 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;
log.debug("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);
log.debug("descending into Map path={} startNode={}", key, nodeToString(startNode));
iterateMap(document, startNode, subMap, d);
log.debug("back from descent Map path={} startNode={}", key, nodeToString(startNode));
} else if ( obj instanceof List ) {
List> lst = (List>) obj;
log.debug("Have a list that is this long {}", lst.size());
Iterator> listIter = lst.iterator();
int newPos = 0;
while ( listIter.hasNext() ) {
Object listObj = listIter.next();
log.debug("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;
log.debug("Retrieving key from List-Map path={}@{}", key, newPos);
Node startNode = getNodeAtPath(document, parentNode, key, newPos, d);
log.debug("descending into List-Map path={}@{} startNode={}", key, newPos, nodeToString(startNode));
iterateMap(document, startNode, subMap, d);
log.debug("back from descent List-Map path={}@{} startNode={}", key, newPos, nodeToString(startNode));
newPos++;
} else {
log.info("XMLMap Encountered an object of type {} in a List which should contain only Map objects", obj.getClass().getName());
}
}
} else {
log.debug("Found a {} do not know how to iterate.", obj.getClass().getName());
}
}
d--;
log.debug("< IterateMap parentNode = {}", nodeToString(parentNode));
}
private static void storeInDom(Document document, Node parentNode, String key, String value, int nodePos, int d)
{
log.debug("> storeInDom{}@{} = {} parent={}", key, nodePos, value, nodeToString(parentNode));
d++;
if ( document == null || key == null || value == null ) return;
if ( parentNode == null ) parentNode = document;
log.debug("parentNode I={}", nodeToString(parentNode));
String [] newPath = key.split("/");
log.debug("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;
log.debug("> getNodeAtPath path@{}={} parentNode={}", nodePos, path, 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--;
log.debug("< getNodeAtPath returning={}", nodeToString(parentNode));
return parentNode;
}
@SuppressWarnings("static-access")
private static Node getOrAddChildNode(Document doc, Node parentNode, String nodeName,int whichNode, int d)
{
log.debug("> getOrAddChildNode name={}@{} parentNode={}", nodeName, whichNode, 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);
log.debug("Index String = {}", indStr);
nodeName = nodeName.substring(0,begpos);
log.debug("New Nodename={}", nodeName);
Integer iVal = new Integer(indStr);
log.debug("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--;
log.debug("< getOrAddChildNode found name={}", nodeToString(node));
log.debug("foundNodes = {} looking for node={}", foundNodes, whichNode);
if ( foundNodes >= whichNode ) return node;
}
}
}
Element newNode = null;
while ( foundNodes < whichNode ) {
foundNodes++;
log.debug("Adding node at position {} moving toward {}", foundNodes, whichNode);
if ( nodeName == null ) continue;
newNode = doc.createElement(nodeName);
log.debug("Adding {} at {} in {}", nodeName, nodeToString(parentNode), doc);
parentNode.appendChild(newNode);
log.debug("xml={}", documentToString(doc,false));
log.debug("getOrAddChildNode added newnode={}", nodeToString(newNode));
}
d--;
log.debug("< 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);
log.debug("Deleting key={}", key);
}
}
// Actually remove...
Iterator setIter = delSet.iterator();
while( setIter.hasNext() ) {
String key = setIter.next();
tm.remove(key);
}
}
// Assume the Object is a String - get it or return null if it is anything but a String
public static String getString(Map 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")) {
* log.debug("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);
log.debug("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;
// If Debug is turned on - let the chips fly, exceptions and
// All...
if ( doDebug ) {
String pretty1 = XMLMap.prettyPrint(xmlString);
String pretty2 = XMLMap.prettyPrint(pretty1);
if ( pretty1.equals(pretty2) ) return true;
log.debug("XMLMap - unit test failed");
return false;
}
// For Debug off - we first try it silently and in a try/catch block
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
log.debug("XMLMap - unit test failed");
log.debug(xmlString);
String pretty1 = XMLMap.prettyPrint(xmlString);
log.debug("Pretty Print Version pass 1\n{}", pretty1);
String pretty2 = XMLMap.prettyPrint(pretty1);
log.debug("Pretty Print Version pass 2\n{}", pretty2);
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) {
log.debug("Running XMLMap (www.mdom.org) unit tests..");
if ( !allUnitTests() ) return;
log.debug("Unit tests passed...");
runSamples();
}
public static void runSamples() {
log.debug("Running XMLMap (www.mdom.org) Samples...");
// Test the parsing of a Basic string Map
Map tm = XMLMap.getMap(simpleText);
log.debug("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");
log.debug("simpleMap\n{}", simpleMap);
String simpleXml = XMLMap.getXML(simpleMap, true);
log.debug("simpleXml\n{}", simpleXml);
unitTest(simpleXml,false);
// Do a select of a subMap
Map subMap = XMLMap.selectSubMap(tm, "/a/c");
Map joinedMap = new TreeMap();
log.debug("subMap={}", subMap);
joinedMap.put("/top/id", "1234");
joinedMap.put("/top/fun", subMap); // Graft the map onto this node
log.debug("joinedMap\n{}", joinedMap);
String joinedXml = XMLMap.getXML(joinedMap, true);
log.debug("joinedXML\n{}", joinedXml);
unitTest(joinedXml,false);
// Do an Array
Map arrayMap = new TreeMap();
String [] arrayStr = { "first", "second", "third" };
arrayMap.put("/root/stuff", arrayStr);
log.debug("arrayMap\n{}", arrayMap);
String arrayXml = XMLMap.getXML(arrayMap, true);
log.debug("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;
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 {
log.debug("\n MISMATCH AND/OR SOME ERROR HAS OCCURED - REDO in VERBODE MODE");
log.debug("Starting out newMap={}", newMap);
complexXml = XMLMap.getXML(newMap, true);
unitTest(complexXml,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
log.debug("\nParsing Sites Structure");
for ( Map siteMap : XMLMap.getList(sitesText,"/sites/site")) {
log.debug("Site={}", siteMap);
log.debug("Id={}", XMLMap.getString(siteMap,"/id"));
for ( Map toolMap : XMLMap.getList(siteMap,"/tools/tool")) {
log.debug("Tool={}", toolMap);
log.debug("ToolId={}", XMLMap.getString(toolMap,"/toolid"));
for ( Map property : XMLMap.getList(toolMap, "/properties/property")) {
log.debug("key={}", XMLMap.getString(property, "/key"));
log.debug("val={}", XMLMap.getString(property, "/val"));
}
}
}
log.debug("\nParsing RSS Feed");
log.debug(XMLMap.prettyPrint(rssText));
Map rssFullMap = XMLMap.getFullMap(rssText);
log.debug("RSS Full Map\n{}", rssFullMap);
log.debug("Rss Version={}", XMLMap.getString(rssFullMap,"/rss!version"));
log.debug("Chan-desc={}", XMLMap.getString(rssFullMap,"/rss/channel/description"));
log.debug("Chan-title={}", XMLMap.getString(rssFullMap,"/rss/channel/title"));
Map rssStringMap = XMLMap.flattenMap(rssFullMap);
log.debug("RSS Flat String Only Map\n{}", rssStringMap);
log.debug("Rss Version={}", rssStringMap.get("/rss!version"));
log.debug("Chan-desc={}", rssStringMap.get("/rss/channel/description"));
log.debug("Chan-title={}", rssStringMap.get("/rss/channel/title"));
for ( Map rssItem : XMLMap.getList(rssFullMap,"/rss/channel/item")) {
log.debug("=== Item ===");
log.debug(" Item-title={}", XMLMap.getString(rssItem, "/title"));
log.debug(" Item-description={}", XMLMap.getString(rssItem, "/description"));
log.debug(" 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/
*/