Skip to content

Instantly share code, notes, and snippets.

@benjchristensen
Created October 15, 2011 23:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benjchristensen/1290326 to your computer and use it in GitHub Desktop.
Save benjchristensen/1290326 to your computer and use it in GitHub Desktop.
XMLUtilty
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import junit.framework.TestCase;
import org.junit.Test;
/**
* Utility for generating XML from Maps/Lists
*/
public class XMLUtility {
private static Logger logger = Logger.getLogger(XMLUtility.class.getName());
/**
* Pass in a Map and this method will return an XML string.
* <p>
* The map can contain Objects, int[], Object[] and Collections and they will be converted into string representations.
* <p>
* Nested maps can be included as values and the XML will have nested object notation.
* <p>
* Arrays/Collections can have Maps in them as well.
* <p>
* See the unit tests for examples.
*
* @param xmlData
* @return
*/
public static String xmlFromMap(Map<String, Object> xmlData) {
return xmlFromMap(null, xmlData);
}
private static String xmlFromMap(String elementName, Map<String, Object> dataMap) {
try {
XMLString xml = new XMLString();
for (String key : dataMap.keySet()) {
Object data = dataMap.get(key);
if (data instanceof Map) {
addMap(xml, key, (Map<String, Object>) data);
} else if (data instanceof Object[]) {
/* it's an object array, so we'll iterate the elements and put them all in here */
xml.addEntity(stringArrayFromObjectArray(key, (Object[]) data));
} else if (data instanceof Collection) {
/* it's a collection, so we'll iterate the elements and put them all in here */
xml.addEntity(stringArrayFromObjectArray(key, ((Collection) data).toArray()));
} else if (data instanceof int[]) {
xml.addEntity(stringArrayFromPrimitiveArray(key, (int[]) data));
} else if (data instanceof XMLCapableObject) {
xml.addElement(key, xmlFromMap(key, ((XMLCapableObject) data).xmlMap()));
} else {
/* all other objects we assume we are to just put the string value in */
xml.addElement(key, String.valueOf(data));
}
}
logger.log(Level.FINER, "created xml from map => " + xml.toString());
return xml.toString();
} catch (Exception e) {
logger.log(Level.SEVERE, "Could not create XML from Map. ", e);
return "{}";
}
}
/**
* Return a string if all values are only String/Object/Primitives otherwise NULL.
* <p>
* Instead of returning a boolean if true then looping again to build it, we do the check and build the string in a single loop.
*
* @param xmlData
* @return
*/
private static String getMapAsAttributeXMLString(String elementName, Map<String, Object> xmlData) {
XMLString xml = new XMLString();
xml.startAttributeElement(elementName);
for (String key : xmlData.keySet()) {
Object data = xmlData.get(key);
if (data instanceof Map) {
return null;
} else if (data instanceof Object[]) {
return null;
} else if (data instanceof Collection) {
return null;
} else if (data instanceof int[]) {
return null;
} else if (data instanceof XMLCapableObject) {
return null;
} else {
xml.addAttribute(key, String.valueOf(data));
}
}
xml.endElementWithoutBody();
return xml.toString();
}
/*
* return a string like: <key>one</key><key>two</key><key>three</key>
*/
private static String stringArrayFromObjectArray(String key, Object[] data) {
XMLString xml = new XMLString();
for (Object o : data) {
if (o instanceof Map) {
addMap(xml, key, (Map<String, Object>) o);
} else if (o instanceof XMLCapableObject) {
addMap(xml, key, ((XMLCapableObject) o).xmlMap());
} else {
xml.addElement(key, String.valueOf(o));
}
}
return xml.toString();
}
/**
* Add a Map<String, Object> to the given XML and determine if it should have attributes or not.
*
* @param xml
* @param key
* @param map
*/
private static void addMap(XMLString xml, String key, Map<String, Object> map) {
String entityAsAttributes = getMapAsAttributeXMLString(key, map);
if (entityAsAttributes != null) {
/* the map was able to be formatted as a single element with attributes, so add it directly */
xml.addEntity(entityAsAttributes);
} else {
/* it's a nested map, so we'll recursively add the XML of this map to the current XML */
xml.addElement(key, xmlFromMap(key, map));
}
}
/*
* return a string like: <key>1</key><key>2</key><key>3</key>
*/
private static String stringArrayFromPrimitiveArray(String key, int[] data) {
XMLString xml = new XMLString();
for (Object o : data) {
xml.addElement(key, String.valueOf(o));
}
return xml.toString();
}
private static class XMLString {
StringBuilder xml = new StringBuilder();
public XMLString startAttributeElement(String key) {
xml.append("<").append(key);
return this;
}
public XMLString startElement(String key) {
xml.append("<").append(key).append(">");
return this;
}
public XMLString addEntity(String value) {
xml.append(value);
return this;
}
public XMLString endElement(String key) {
xml.append("</").append(key).append(">");
return this;
}
public XMLString endElementWithoutBody() {
xml.append("/>");
return this;
}
public XMLString addAttribute(String key, String value) {
xml.append(" ").append(key).append("=\"").append(value).append("\"");
return this;
}
public XMLString addElement(String key, String value) {
startElement(key);
xml.append(value);
endElement(key);
return this;
}
public String toString() {
return xml.toString();
}
}
public static class UnitTest extends TestCase {
// I'm using LinkedHashMap in the testing so I get consistent ordering for the expected results
public void testSimpleOne() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
xmlData.put("myKey", "myValue");
String xml = xmlFromMap(xmlData);
String expected = "<myKey>myValue</myKey>";
assertEquals(expected, xml);
}
public void testSimpleTwo() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
xmlData.put("myKey", "myValue");
xmlData.put("myKey2", "myValue2");
String xml = xmlFromMap(xmlData);
String expected = "<myKey>myValue</myKey><myKey2>myValue2</myKey2>";
assertEquals(expected, xml);
}
public void testMapAsAttributesFromList() {
List<Map<String, Object>> elements = new ArrayList<Map<String, Object>>();
for (int i = 0; i < 2; i++) {
Map<String, Object> element = new LinkedHashMap<String, Object>();
element.put("myKey", "myValue");
element.put("myKey2", "myValue2");
elements.add(element);
}
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
xmlData.put("item", elements);
String xml = xmlFromMap(xmlData);
String expected = "<item myKey=\"myValue\" myKey2=\"myValue2\"/><item myKey=\"myValue\" myKey2=\"myValue2\"/>";
assertEquals(expected, xml);
}
public void testMapAsAttributesInMap() {
Map<String, Object> element = new LinkedHashMap<String, Object>();
element.put("myKey", "myValue");
element.put("myKey2", "myValue2");
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
xmlData.put("item", element);
xmlData.put("key", "value");
String xml = xmlFromMap(xmlData);
String expected = "<item myKey=\"myValue\" myKey2=\"myValue2\"/><key>value</key>";
assertEquals(expected, xml);
}
public void testNestedMapOne() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
xmlData.put("myKey", "myValue");
Map<String, Object> xmlData2 = new LinkedHashMap<String, Object>();
xmlData2.put("myNestedKey", "myNestedValue");
xmlData.put("myNestedData", xmlData2);
String xml = xmlFromMap(xmlData);
String expected = "<myKey>myValue</myKey><myNestedData myNestedKey=\"myNestedValue\"/>";
assertEquals(expected, xml);
}
public void testNestedMapTwo() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
xmlData.put("myKey", "myValue");
Map<String, Object> xmlData2 = new LinkedHashMap<String, Object>();
xmlData2.put("myNestedKey", "myNestedValue");
xmlData2.put("myNestedKey2", "myNestedValue2");
xmlData.put("myNestedData", xmlData2);
String xml = xmlFromMap(xmlData);
String expected = "<myKey>myValue</myKey><myNestedData myNestedKey=\"myNestedValue\" myNestedKey2=\"myNestedValue2\"/>";
assertEquals(expected, xml);
}
public void testArrayOne() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
int[] numbers = { 1, 2, 3, 4 };
xmlData.put("myKey", numbers);
String xml = xmlFromMap(xmlData);
String expected = "<myKey>1</myKey><myKey>2</myKey><myKey>3</myKey><myKey>4</myKey>";
assertEquals(expected, xml);
}
public void testArrayTwo() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
String[] values = { "one", "two", "three", "four" };
xmlData.put("myKey", values);
String xml = xmlFromMap(xmlData);
String expected = "<myKey>one</myKey><myKey>two</myKey><myKey>three</myKey><myKey>four</myKey>";
assertEquals(expected, xml);
}
public void testCollectionOne() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
ArrayList<String> values = new ArrayList<String>();
values.add("one");
values.add("two");
values.add("three");
values.add("four");
xmlData.put("myKey", values);
String xml = xmlFromMap(xmlData);
String expected = "<myKey>one</myKey><myKey>two</myKey><myKey>three</myKey><myKey>four</myKey>";
assertEquals(expected, xml);
}
public void testMapAndList() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
xmlData.put("myKey", "myValue");
int[] numbers = { 1, 2, 3, 4 };
xmlData.put("myNumbers", numbers);
Map<String, Object> xmlData2 = new LinkedHashMap<String, Object>();
xmlData2.put("myNestedKey", "myNestedValue");
xmlData2.put("myNestedKey2", "myNestedValue2");
String[] values = { "one", "two", "three", "four" };
xmlData2.put("myStringNumbers", values);
xmlData.put("myNestedData", xmlData2);
String xml = xmlFromMap(xmlData);
String expected = "<myKey>myValue</myKey><myNumbers>1</myNumbers><myNumbers>2</myNumbers><myNumbers>3</myNumbers><myNumbers>4</myNumbers><myNestedData><myNestedKey>myNestedValue</myNestedKey><myNestedKey2>myNestedValue2</myNestedKey2><myStringNumbers>one</myStringNumbers><myStringNumbers>two</myStringNumbers><myStringNumbers>three</myStringNumbers><myStringNumbers>four</myStringNumbers></myNestedData>";
assertEquals(expected, xml);
}
@Test
public void testArrayOfMaps() {
Map<String, Object> xmlData = new LinkedHashMap<String, Object>();
ArrayList<Map<String, Object>> messages = new ArrayList<Map<String, Object>>();
Map<String, Object> message1 = new LinkedHashMap<String, Object>();
message1.put("a", "valueA1");
message1.put("b", "valueB1");
messages.add(message1);
Map<String, Object> message2 = new LinkedHashMap<String, Object>();
message2.put("a", "valueA2");
message2.put("b", "valueB2");
messages.add(message2);
xmlData.put("messages", messages);
String xml = xmlFromMap(xmlData);
String expected = "<messages a=\"valueA1\" b=\"valueB1\"/><messages a=\"valueA2\" b=\"valueB2\"/>";
assertEquals(expected, xml);
}
}
public static interface XMLCapableObject {
Map<String, Object> xmlMap();
}
}
@benjchristensen
Copy link
Author

This is a companion to JSONUtilty so that a Map<String, Object> can be output as either JSON or XML.

@benjchristensen
Copy link
Author

Example of how this can be used in a RESTful app that can respond with JSON and XML:

@GET
@Path("{id}")
@Produces("application/xml")
public Response getXML(@PathParam("id") String id) {
    return Response.ok(XMLUtility.xmlFromMap(get(id))).type(MediaType.APPLICATION_XML).build();
}

@GET
@Path("{id}")
@Produces("application/json")
public Response getJSON(@PathParam("id") String id) {
    return Response.ok(JSONUtility.jsonFromMap(get(id))).type(MediaType.APPLICATION_JSON).build();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment