001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/sam/trunk/component/src/java/org/sakaiproject/tool/assessment/qti/util/XmlMapper.java $
003: * $Id: XmlMapper.java 9274 2006-05-10 22:50:48Z daisyf@stanford.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the"License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.tool.assessment.qti.util;
021:
022: import java.io.Serializable;
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.Map;
026:
027: import org.apache.commons.beanutils.BeanUtils;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.w3c.dom.Document;
031: import org.w3c.dom.NamedNodeMap;
032: import org.w3c.dom.Node;
033: import org.w3c.dom.NodeList;
034:
035: import org.sakaiproject.tool.assessment.qti.util.XmlUtil;
036:
037: /**
038: * Utility class. Maps XML elements and attribute under a given node
039: * to a Map, or populates bean.
040: *
041: * Note this now supports deep copy.
042: *
043: * @author @author Ed Smiley
044: * @version $Id: XmlMapper.java 9274 2006-05-10 22:50:48Z daisyf@stanford.edu $
045: */
046: public class XmlMapper {
047: private static Log log = LogFactory.getLog(XmlMapper.class);
048:
049: public static final String ATTRIBUTE_PREFIX = "attribute_";
050:
051: /**
052: * Maps each element node and attribute under a given node to a Map.
053: * It associates each element's text value with its name, and
054: * each attribute value with a key of "attribute_" + the attribute name.
055: *
056: * If node is a document it processes it as if it were the root node.
057: *
058: * If there are multiple nodes with the same element name they will be stored
059: * in a List.
060: *
061: * NOTE:
062: * This was DESIGNED to ignore elements at more depth than root +1.
063: * It has now been modified to deep copy under the nodes, but it
064: * WILL NOT recurse and assign key value pairs below,
065: * this is by design. The elements below (e.g. XML snippets, XHTML text)
066: * are all put into the value.
067: *
068: * @param node Node
069: * @param indent String
070: * @return HashMap
071: */
072: static public Map map(Document doc) {
073: return hashNode(doc);
074: }
075:
076: /**
077: * Maps each element node to a bean property.
078: * Supports only simple types such as String, int and long, plus Lists.
079: *
080: * If node is a document it processes it as if it were the root node.
081: *
082: * If there are multiple nodes with the same element name they will be stored
083: * in a List.
084: *
085: * NOTE:
086: * This is DESIGNED to ignore elements at more depth so that simple
087: * String key value pairs are used. This WILL NOT recurse to more depth,
088: * by design. If it did so, you would have to use maps of maps.
089: *
090: * @param bean Serializable object which has the appropriate setters/getters
091: * @param doc the document
092: */
093: static public void populateBeanFromDoc(Serializable bean,
094: Document doc) {
095: try {
096: Map m = map(doc);
097: BeanUtils.populate(bean, m);
098: } catch (Exception e) {
099: log.error(e);
100: throw new RuntimeException(e);
101: }
102: }
103:
104: /**
105: * utility class, hides the implementation as a HashMap
106: * @param node
107: * @return HashMap
108: */
109: private static HashMap hashNode(Node node) {
110: HashMap hNode = new HashMap();
111:
112: int nType = node.getNodeType();
113: NodeList nodes = node.getChildNodes();
114: NamedNodeMap attributes = node.getAttributes();
115: String name = node.getNodeName();
116:
117: // node is a document, recurse
118: if (nType == Node.DOCUMENT_NODE) {
119: // find root node
120: if (nodes != null) {
121: for (int i = 0; i < nodes.getLength(); i++) {
122: // find and process root node
123: Node rnode = nodes.item(i);
124: if (rnode.getNodeType() == Node.ELEMENT_NODE) {
125: hNode = hashNode(rnode);
126:
127: break;
128: }
129: }
130: }
131: }
132:
133: //end if Node.DOCUMENT_NODE
134: if (nType == Node.ELEMENT_NODE) {
135: // add in child elements
136: if (nodes != null) {
137: for (int j = 0; j < nodes.getLength(); j++) {
138: Node cnode = nodes.item(j);
139: if (cnode.getNodeType() == Node.ELEMENT_NODE) {
140: String cname = cnode.getNodeName();
141: String ctext = ""; //textValue(cnode);
142: String ctype = getTypeAttribute(cnode);
143: // log.debug(cname + "=" + ctype);
144:
145: // if we have multiple identical entries store them in a List
146: if ("list".equals(ctype)) {
147: ArrayList list;
148: // if this element name already has a list
149: if (hNode.get(cname) instanceof ArrayList) {
150: list = (ArrayList) hNode.get(cname);
151: } else // put it in a new list
152: {
153: list = new ArrayList();
154: }
155:
156: // support for deep copy
157:
158: // list.add(ctext);
159: NodeList ccnodes = cnode.getChildNodes();
160: for (int n = 0; n < ccnodes.getLength(); n++) {
161: ctext += XmlUtil.getDOMString(ccnodes
162: .item(n));
163: }
164: list.add(ctext);
165: hNode.put(cname, list);
166: } else // scalar (default)
167: {
168: // support for deep copy
169: NodeList ccnodes = cnode.getChildNodes();
170: for (int n = 0; n < ccnodes.getLength(); n++) {
171: ctext += XmlUtil.getDOMString(ccnodes
172: .item(n));
173: }
174: hNode.put(cname, ctext);
175: }
176: }
177: }
178: }
179:
180: // add in attributes
181: if (attributes != null) {
182: for (int i = 0; i < attributes.getLength(); i++) {
183: Node current = attributes.item(i);
184: hNode.put(ATTRIBUTE_PREFIX + current.getNodeName(),
185: current.getNodeValue());
186: }
187: }
188: }
189:
190: return hNode;
191: }
192:
193: /**
194: * utility method
195: *
196: * @param nd node
197: *
198: * @return text value of node
199: */
200: private static String textValue(Node nd) {
201: String text = "";
202: NodeList nodes = nd.getChildNodes();
203: for (int i = 0; i < nodes.getLength(); i++) {
204: Node cnode = nodes.item(i);
205: if (cnode.getNodeType() == Node.TEXT_NODE) {
206: text += cnode.getNodeValue();
207: }
208: }
209:
210: return text;
211: }
212:
213: /**
214: * If there is a type attribute for the element node, return its value,
215: * otherwise return "scalar".
216: * @param node
217: * @return
218: */
219: private static String getTypeAttribute(Node node) {
220: NamedNodeMap attributes = node.getAttributes();
221: if (attributes != null) {
222: for (int i = 0; i < attributes.getLength(); i++) {
223: Node current = attributes.item(i);
224: if ("type".equals(current.getNodeName())) {
225: return current.getNodeValue();
226: }
227: }
228: }
229:
230: return "scalar";
231: }
232:
233: }
|