001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/sam/trunk/component/src/java/org/sakaiproject/tool/assessment/qti/util/XmlStringBuffer.java $
003: * $Id: XmlStringBuffer.java 9274 2006-05-10 22:50:48Z daisyf@stanford.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 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.ByteArrayOutputStream;
023: import java.io.IOException;
024: import java.io.StringReader;
025: import java.util.Iterator;
026: import java.util.List;
027: import javax.xml.parsers.DocumentBuilder;
028: import javax.xml.parsers.DocumentBuilderFactory;
029: import javax.xml.parsers.ParserConfigurationException;
030: import javax.xml.transform.Result;
031: import javax.xml.transform.Source;
032: import javax.xml.transform.Transformer;
033: import javax.xml.transform.TransformerException;
034: import javax.xml.transform.TransformerFactory;
035: import javax.xml.transform.dom.DOMSource;
036: import javax.xml.transform.stream.StreamResult;
037:
038: import org.apache.commons.collections.ReferenceMap;
039: import org.apache.commons.logging.Log;
040: import org.apache.commons.logging.LogFactory;
041: import org.jaxen.JaxenException;
042: import org.jaxen.XPath;
043: import org.jaxen.dom.DOMXPath;
044: import org.jdom.JDOMException;
045: import org.jdom.input.SAXBuilder;
046: import org.jdom.output.DOMOutputter;
047: import org.w3c.dom.Attr;
048: import org.w3c.dom.CharacterData;
049: import org.w3c.dom.DOMException;
050: import org.w3c.dom.Document;
051: import org.w3c.dom.Element;
052: import org.w3c.dom.Node;
053: import org.w3c.dom.Text;
054: import org.xml.sax.SAXException;
055:
056: /**
057: * <p>Copyright: Copyright (c) 2004</p>
058: * <p>Organization: Sakai Project</p>
059: * @author rshastri
060: * @author Ed Smiley esmiley@stanford.edu
061: * @version $Id: XmlStringBuffer.java 9274 2006-05-10 22:50:48Z daisyf@stanford.edu $
062: */
063: public class XmlStringBuffer implements java.io.Serializable {
064: private static Log log = LogFactory.getLog(XmlStringBuffer.class);
065:
066: /**
067: * Explicitly setting serialVersionUID insures future versions can be
068: * successfully restored. It is essential this variable name not be changed
069: * to SERIALVERSIONUID, as the default serialization methods expects this
070: * exact name.
071: */
072: private static final long serialVersionUID = 1;
073:
074: //The following need not be persisted and so are declared transient
075: private transient Document document = null;
076: private transient DocumentBuilder builder = null;
077: private transient ReferenceMap cache = null;
078: private StringBuffer xml;
079:
080: /**
081: * Constructor to be accessed by subclasses.
082: */
083: protected XmlStringBuffer() {
084: this .xml = new StringBuffer();
085: }
086:
087: /**
088: * Constructs an XmlStringBuffer whose initial value is String.
089: *
090: * @param xml XML string
091: */
092: public XmlStringBuffer(String xml) {
093: this .xml = new StringBuffer(xml);
094: }
095:
096: /**
097: * Constructs an XmlStringBuffer whose initial value is Document
098: *
099: * @param document XML document
100: */
101: public XmlStringBuffer(Document document) {
102: this .document = document;
103: }
104:
105: /**
106: * Constructs an XmlStringBuffer whose initial value is Document
107: *
108: * @param jdomDoc
109: *
110: * @deprecated using XmlStringBuffer(org.w3c.dom.Document document) instead.
111: */
112: public XmlStringBuffer(org.jdom.Document jdomDoc) {
113: try {
114: this .document = new DOMOutputter().output(jdomDoc);
115: } catch (JDOMException e) {
116: log.error(e.getMessage(), e);
117: }
118: }
119:
120: /**
121: * Clears the xml
122: */
123: public final void clear() {
124: this .xml.setLength(0);
125: this .reset();
126: }
127:
128: /**
129: * replace the current xml with the given string
130: *
131: * @param xml XML replacement string
132: *
133: * @deprecated
134: */
135: public final void replace(String xml) {
136: this .xml = new StringBuffer(xml);
137: this .reset();
138: }
139:
140: /**
141: * Get a document
142: *
143: * @return document
144: *
145: * @throws ParserConfigurationException
146: * @throws SAXException
147: * @throws IOException
148: */
149: public final Document getDocument()
150: throws ParserConfigurationException, SAXException,
151: IOException {
152: if (this .document == null) {
153: this .parseContent();
154: }
155:
156: return this .document;
157: }
158:
159: /**
160: * internal
161: *
162: * @return reference map
163: */
164: private ReferenceMap getCache() {
165: if (this .cache == null) {
166: this .cache = new ReferenceMap();
167: }
168:
169: return this .cache;
170: }
171:
172: /**
173: * inernal, clear cache
174: */
175: private void clearCache() {
176: if (this .cache == null) {
177: this .cache = new ReferenceMap();
178: } else {
179: this .cache.clear();
180: }
181: }
182:
183: /**
184: * parse content to JDOM
185: *
186: * @return JDOM document
187: *
188: * @throws JDOMException =
189: * @throws IOException
190: */
191: private final org.jdom.Document parseContentToJDOM()
192: throws JDOMException, IOException {
193: if (log.isDebugEnabled()) {
194: log.debug("parseContentToJDOM()");
195: }
196:
197: String xmlString = this .stringValue();
198: org.jdom.Document result = null;
199: try {
200: SAXBuilder saxbuilder = new SAXBuilder();
201: result = saxbuilder.build(new StringReader(xmlString));
202: } catch (JDOMException ex) {
203: log.error("Exception thrown while parsing XML:\n"
204: + ex.getMessage(), ex);
205: throw ex;
206: } catch (IOException ie) {
207: log.error("Exception thrown while parsing XML:\n"
208: + ie.getMessage(), ie);
209: throw ie;
210: }
211:
212: return result;
213: }
214:
215: /**
216: * parse the content
217: *
218: * @throws ParserConfigurationException
219: * @throws SAXException
220: * @throws IOException
221: */
222: private final void parseContent()
223: throws ParserConfigurationException, SAXException,
224: IOException {
225: if (log.isDebugEnabled()) {
226: log.debug("parseContent()");
227: }
228:
229: this .clearCache();
230: DocumentBuilderFactory dbfi = null;
231: DocumentBuilder builder = null;
232: StringReader sr = null;
233: org.xml.sax.InputSource is = null;
234: try {
235: if (builder == null) {
236: dbfi = DocumentBuilderFactory.newInstance();
237: builder = dbfi.newDocumentBuilder();
238: String s = this .xml.toString();
239: if (s == null) {
240: log.warn("string value null");
241: s = "";
242: }
243: sr = new StringReader(s);
244: is = new org.xml.sax.InputSource(sr);
245: this .document = builder.parse(is);
246: }
247: } catch (ParserConfigurationException e) {
248: log.error(e.getMessage(), e);
249: throw e;
250: } catch (SAXException e) {
251: log.error(e.getMessage(), e);
252: log.error("DocumentBuilderFactory dbfi = " + dbfi);
253: log.error("StringReader sr = " + sr);
254: log.error("InputSource is = " + is);
255: log.error("StringBuffer xml = " + this .xml);
256: throw e;
257: } catch (IOException e) {
258: log.error(e.getMessage(), e);
259: throw e;
260: }
261: }
262:
263: /**
264: * string value of document
265: *
266: * @return the string
267: */
268: public final String stringValue() {
269: if (log.isDebugEnabled()) {
270: log.debug("stringValue()");
271: }
272:
273: if (document == null) {
274: return this .xml.toString();
275: } else {
276: ByteArrayOutputStream out = new ByteArrayOutputStream();
277: Source xmlSource = new DOMSource(document);
278: Result outputTarget = new StreamResult(out);
279: Transformer tf;
280: try {
281: tf = TransformerFactory.newInstance().newTransformer();
282: tf.transform(xmlSource, outputTarget);
283: } catch (TransformerException e) {
284: log.error(e.getMessage(), e);
285: }
286: return out.toString();
287: }
288: }
289:
290: /**
291: * is the xml empty?
292: *
293: * @return true/false
294: */
295: public final boolean isEmpty() {
296: return ((this .xml == null) || (this .xml.length() == 0));
297: }
298:
299: /**
300: *
301: */
302: private void reset() {
303: this .document = null;
304: this .cache = null;
305: }
306:
307: /**
308: * xpath lookup
309: *
310: * @param xpath
311: * @param type
312: *
313: * @return value
314: */
315: public String selectSingleValue(String xpath, String type) {
316: // user passes the if its an element or attribute
317: String value = null;
318: List list = this .selectNodes(xpath);
319: if (list != null) {
320: int no = list.size();
321: try {
322: if (list.size() > 0) {
323: Document document = this .getDocument();
324: if ((type != null) && type.equals("element")) {
325: Element element = (Element) list.get(0);
326:
327: CharacterData elementText = (CharacterData) element
328: .getFirstChild();
329: Integer getTime = null;
330: if ((elementText != null)
331: && (elementText.getNodeValue() != null)
332: && (elementText.getNodeValue().trim()
333: .length() > 0)) {
334: value = elementText.getNodeValue();
335: }
336: }
337:
338: if ((type != null) && type.equals("attribute")) {
339: Attr attr = (Attr) list.get(0);
340:
341: CharacterData elementText = (CharacterData) attr
342: .getFirstChild();
343:
344: Integer getTime = null;
345: if ((elementText != null)
346: && (elementText.getNodeValue() != null)
347: && (elementText.getNodeValue().trim()
348: .length() > 0)) {
349: value = elementText.getNodeValue();
350: }
351: }
352: }
353: } catch (ParserConfigurationException e) {
354: log.error(e.getMessage(), e);
355: } catch (SAXException e) {
356: log.error(e.getMessage(), e);
357: } catch (IOException e) {
358: log.error(e.getMessage(), e);
359: }
360: }
361:
362: return value;
363: }
364:
365: /**
366: * get nodes
367: *
368: * @param xpath
369: *
370: * @return list of nodes
371: */
372: public final List selectNodes(String xpath) {
373: if (log.isDebugEnabled()) {
374: log.debug("selectNodes(String " + xpath + ")");
375: }
376:
377: // First try retrieving it from the cache
378: List result = null;
379: result = (List) this .getCache().get(xpath);
380:
381: // TODO need to invalidate cache when underlying document changes!
382: result = null;
383: if (result == null) {
384: try {
385: XPath path = new DOMXPath(xpath);
386: result = path.selectNodes(this .getDocument());
387: } catch (JaxenException je) {
388: log.error(je.getMessage(), je);
389: } catch (ParserConfigurationException e) {
390: log.error(e.getMessage(), e);
391: } catch (SAXException e) {
392: log.error(e.getMessage(), e);
393: } catch (IOException e) {
394: log.error(e.getMessage(), e);
395: }
396: } else {
397: log.debug("found in cache");
398: }
399:
400: return result;
401: }
402:
403: /**
404: * perform Update on this object
405: *
406: * @param xpath :- xpath and
407: * @param value :- Value of xpath
408: *
409: * @return XmlStringBuffer
410: *
411: * @throws DOMException
412: * @throws Exception
413: */
414:
415: // Rashmi Aug 19th changed by Pamela on Sept 10th.
416: // Rashmi - replacing updateJDOM as on Sep 15
417: public final XmlStringBuffer update(String xpath, String value)
418: throws DOMException, Exception {
419: if (log.isDebugEnabled()) {
420: log.debug("update(String " + xpath + ", String " + value
421: + ")");
422: }
423:
424: try {
425: Element newElement = null;
426: Attr newAttribute = null;
427: List newElementList = this .selectNodes(xpath);
428: int aIndex = xpath.indexOf("@");
429: int size = newElementList.size();
430: if (size > 1) {
431: log.info("UPDATING MORE THAN ONE ELEMENT");
432: }
433:
434: if ((aIndex == -1) && (size != 0)) {
435: for (int i = 0; i < size; i++) {
436: newElement = (Element) newElementList.get(i);
437: Node childNode = newElement.getFirstChild();
438:
439: if (childNode == null) {
440: DocumentBuilderFactory dbf = DocumentBuilderFactory
441: .newInstance();
442: DocumentBuilder db = dbf.newDocumentBuilder();
443: Document document = db.newDocument();
444: Text newElementText = document
445: .createTextNode(newElement
446: .getNodeName());
447: newElementText.setNodeValue(value);
448: Text clonedText = (Text) newElement
449: .getOwnerDocument().importNode(
450: newElementText, true);
451: newElement.appendChild(clonedText);
452: } else {
453: CharacterData newElementText = (CharacterData) newElement
454: .getFirstChild();
455: newElementText.setNodeValue(value);
456: }
457: }
458: }
459:
460: if ((aIndex != -1) && (size != 0)) {
461: newAttribute = (Attr) newElementList.set(0,
462: newAttribute);
463: if (newAttribute != null) {
464: newAttribute.setValue(value);
465: }
466: }
467: } catch (Exception ex) {
468: log.error(ex.getMessage(), ex);
469: }
470:
471: return this ;
472: }
473:
474: /**
475: * update element, xpath
476: *
477: * @param xpath
478: * @param element
479: */
480: public final void update(String xpath, Element element) {
481: if (log.isDebugEnabled()) {
482: log.debug("update(String " + xpath + ", Element " + element
483: + ")");
484: }
485:
486: List itemResults = this .selectNodes(xpath);
487: Iterator iterator = itemResults.iterator();
488: while (iterator.hasNext()) {
489: Element node = (Element) iterator.next();
490: Element replacement = (Element) node.getOwnerDocument()
491: .importNode(element, true);
492: node.getParentNode().replaceChild(replacement, node);
493: }
494:
495: if (itemResults.size() == 0) {
496: String parentPath = xpath.substring(0, xpath
497: .lastIndexOf("/"));
498: addElement(parentPath, element);
499: }
500: }
501:
502: /**
503: * DOCUMENT ME!
504: *
505: * @param xpath
506: * @param element
507: *
508: * @deprecated addElement(String, org.w3c.dom.Element)
509: */
510: public final void addJDOMElement(String xpath,
511: org.jdom.Element element) {
512: try {
513: List nodes = this .selectNodes(xpath);
514: int size = nodes.size();
515: for (int i = 0; i < size; i++) {
516: org.jdom.Element node = (org.jdom.Element) nodes.get(i);
517: node.addContent(element);
518: }
519: } catch (Exception ex) {
520: log.error(ex.getMessage(), ex);
521: }
522: }
523:
524: /**
525: * insert element
526: *
527: * @param afterNode
528: * @param parentXpath
529: * @param childXpath
530: */
531: public void insertElement(String afterNode, String parentXpath,
532: String childXpath) {
533: try {
534: String nextXpath = parentXpath + "/" + afterNode;
535:
536: //*************************************************************
537: DocumentBuilderFactory dbf = DocumentBuilderFactory
538: .newInstance();
539: DocumentBuilder db = dbf.newDocumentBuilder();
540: Document document = db.newDocument();
541: Element element = document.createElement(childXpath);
542:
543: //**************************************************************
544: Element parent = null;
545: List parentNodes = this .selectNodes(parentXpath);
546: Iterator iteratorNext = parentNodes.iterator();
547: while (iteratorNext.hasNext()) {
548: parent = (Element) iteratorNext.next();
549: }
550:
551: if (parent != null) {
552: List nodes = this .selectNodes(nextXpath);
553: Iterator iterator = nodes.iterator();
554: Element nextSibling = null;
555: while (iterator.hasNext()) {
556: nextSibling = (Element) iterator.next();
557: }
558:
559: if ((nextSibling != null)
560: && !nextSibling.equals(element
561: .getOwnerDocument())) {
562: element = (Element) parent.getOwnerDocument()
563: .importNode(element, true);
564: parent.insertBefore(element, nextSibling);
565: }
566: }
567: } catch (ParserConfigurationException pce) {
568: log.error("Exception thrown from insertElement() : "
569: + pce.getMessage());
570: pce.printStackTrace();
571: }
572: }
573:
574: /**
575: *
576: *
577: * @param parentXpath
578: * @param childXpath
579: */
580: public final void add(String parentXpath, String childXpath) {
581: Element childElement = createChildElement(childXpath);
582: this .addElement(parentXpath, childElement);
583: }
584:
585: /**
586: * create child
587: *
588: * @param childXpath
589: *
590: * @return
591: */
592: private final Element createChildElement(String childXpath) {
593: int index = childXpath.indexOf("/");
594: String elementName = childXpath;
595: String subChildXpath = null;
596: Element element = null;
597: Element child = null;
598: if (index > 0) {
599: elementName = childXpath.substring(0, index);
600: subChildXpath = childXpath.substring(index + 1);
601: child = createChildElement(subChildXpath);
602: }
603: try {
604: DocumentBuilderFactory dbf = DocumentBuilderFactory
605: .newInstance();
606: DocumentBuilder db = dbf.newDocumentBuilder();
607: Document document = db.newDocument();
608: element = document.createElement(elementName);
609: element = document.createElement(elementName);
610: if (child != null) {
611: Node importedNode = document.importNode(child, true);
612: element.appendChild(importedNode);
613: }
614: } catch (ParserConfigurationException pce) {
615: log.error("Exception thrown from createChildElement(): "
616: + pce.getMessage());
617: pce.printStackTrace();
618: }
619:
620: return element;
621: }
622:
623: /**
624: * add element
625: *
626: * @param parentXpath
627: * @param element
628: */
629: public final void addElement(String parentXpath, Element element) {
630: if (log.isDebugEnabled()) {
631: log.debug("addElement(String " + parentXpath + ", Element "
632: + element + ")");
633: }
634:
635: List nodes = this .selectNodes(parentXpath);
636: Iterator iterator = nodes.iterator();
637: while (iterator.hasNext()) {
638: Element parent = (Element) iterator.next();
639: if (!parent.equals(element.getOwnerDocument())) {
640: element = (Element) parent.getOwnerDocument()
641: .importNode(element, true);
642: }
643:
644: parent.appendChild(element);
645: }
646: }
647:
648: /**
649: * add attribute
650: *
651: * @param elementXpath
652: * @param attributeName
653: */
654: public final void addAttribute(String elementXpath,
655: String attributeName) {
656: if (log.isDebugEnabled()) {
657: log.debug("addAttribute(String " + elementXpath
658: + ", String" + attributeName + ")");
659: }
660:
661: List nodes = this .selectNodes(elementXpath);
662: int size = nodes.size();
663: for (int i = 0; i < size; i++) {
664: Element element = (Element) nodes.get(i);
665: element.setAttribute(attributeName, "");
666: }
667: }
668:
669: /**
670: * remove element
671: *
672: * @param xpath
673: */
674: public final void removeElement(String xpath) {
675: if (log.isDebugEnabled()) {
676: log.debug("removeElement(String " + xpath + ")");
677: }
678:
679: List nodes = this .selectNodes(xpath);
680: Iterator iterator = nodes.iterator();
681: while (iterator.hasNext()) {
682: Node node = (Node) iterator.next();
683: Node parent = node.getParentNode();
684: parent.removeChild(node);
685: }
686: }
687:
688: /**
689: * Synchronizes object prior to serialization
690: *
691: * @param out ObjectOutputStream
692: *
693: * @throws IOException
694: */
695: private void writeObject(java.io.ObjectOutputStream out)
696: throws IOException {
697: if (log.isDebugEnabled()) {
698: log.debug("writeObject(ObjectOutputStream " + out + ")");
699: }
700:
701: this .xml = new StringBuffer(this.stringValue());
702: out.defaultWriteObject();
703: }
704: }
|