001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/sam/trunk/component/src/java/org/sakaiproject/tool/assessment/qti/helper/AuthoringXml.java $
003: * $Id: AuthoringXml.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.helper;
021:
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.InputStreamReader;
025: import java.io.StringReader;
026: import java.io.StringWriter;
027: import java.util.ArrayList;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032: import org.springframework.core.io.ClassPathResource;
033: import javax.xml.parsers.DocumentBuilder;
034: import javax.xml.parsers.DocumentBuilderFactory;
035: import javax.xml.parsers.ParserConfigurationException;
036:
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039: import org.jaxen.JaxenException;
040: import org.jaxen.XPath;
041: import org.jaxen.dom.DOMXPath;
042: import org.w3c.dom.Attr;
043: import org.w3c.dom.CharacterData;
044: import org.w3c.dom.DOMException;
045: import org.w3c.dom.Document;
046: import org.w3c.dom.Element;
047: import org.w3c.dom.Node;
048: import org.w3c.dom.Text;
049: import org.xml.sax.InputSource;
050: import org.xml.sax.SAXException;
051:
052: import org.sakaiproject.tool.assessment.qti.constants.QTIVersion;
053:
054: /**
055: * <p>Utility to load XML templates from Sprint context or local file system.</p>
056: * <p> </p>
057: * <p>Copyright: Copyright (c) 2005 Sakai</p>
058: * <p> </p>
059: * @author Ed Smiley esmiley@stanford.edu
060: * @version $Id: AuthoringXml.java 9274 2006-05-10 22:50:48Z daisyf@stanford.edu $
061: */
062:
063: public class AuthoringXml {
064: private static Log log = LogFactory.getLog(AuthoringXml.class);
065:
066: public static final String SETTINGS_FILE = "SAM.properties";
067: // paths
068: public static final String TEMPLATE_PATH = "xml/author/";
069: public static final String SURVEY_PATH = "survey/";
070: //assessment template
071: public static final String ASSESSMENT = "assessmentTemplate.xml";
072: // section template
073: public static final String SECTION = "sectionTemplate.xml";
074: // item templates
075: public static final String ITEM_AUDIO = "audioRecordingTemplate.xml";
076: public static final String ITEM_ESSAY = "essayTemplate.xml";
077: public static final String ITEM_FIB = "fibTemplate.xml";
078: public static final String ITEM_FIN = "finTemplate.xml";
079: public static final String ITEM_FILE = "fileUploadTemplate.xml";
080: public static final String ITEM_MATCH = "matchTemplate.xml";
081: public static final String ITEM_MCMC = "mcMCTemplate.xml";
082: public static final String ITEM_MCSC = "mcSCTemplate.xml";
083: public static final String ITEM_SURVEY = "mcSurveyTemplate.xml";
084: public static final String ITEM_TF = "trueFalseTemplate.xml";
085: public static final String ITEM_MATCHING = "matchTemplate.xml";
086: public static final String SURVEY_10 = SURVEY_PATH + "10.xml";
087: public static final String SURVEY_5 = SURVEY_PATH + "5.xml";
088: public static final String SURVEY_AGREE = SURVEY_PATH + "AGREE.xml";
089: public static final String SURVEY_AVERAGE = SURVEY_PATH
090: + "AVERAGE.xml";
091: public static final String SURVEY_EXCELLENT = SURVEY_PATH
092: + "EXCELLENT.xml";
093: public static final String SURVEY_STRONGLY = SURVEY_PATH
094: + "STRONGLY_AGREE.xml";
095: public static final String SURVEY_UNDECIDED = SURVEY_PATH
096: + "UNDECIDED.xml";
097: public static final String SURVEY_YES = SURVEY_PATH + "YES.xml";
098:
099: private static final String QTI_12_PATH = "v1p2";
100: private static final String QTI_20_PATH = "v2p0";
101:
102: public Map validTemplates = null;
103: private int qtiVersion;
104: private String qtiPath;
105:
106: private AuthoringXml() {
107: initTemplates();
108: }
109:
110: public AuthoringXml(int qtiVersion) {
111: this .qtiVersion = qtiVersion;
112: if (qtiVersion == QTIVersion.VERSION_1_2) {
113: qtiPath = QTI_12_PATH;
114: } else if (qtiVersion == QTIVersion.VERSION_2_0) {
115: qtiPath = QTI_20_PATH;
116: } else {
117: throw new IllegalArgumentException(
118: "Unsupported qti version");
119: }
120:
121: initTemplates();
122: }
123:
124: private void initTemplates() {
125: validTemplates = new HashMap();
126: validTemplates.put(ASSESSMENT, "assessmentTemplate.xml");
127: validTemplates.put(SECTION, "assessmentTemplate.xml");
128: validTemplates.put(ITEM_AUDIO, "audioRecordingTemplate.xml");
129: validTemplates.put(ITEM_ESSAY, "essayTemplate.xml");
130: validTemplates.put(ITEM_FIB, "fibTemplate.xml");
131: validTemplates.put(ITEM_FIN, "finTemplate.xml");
132: validTemplates.put(ITEM_FILE, "fileUploadTemplate.xml");
133: validTemplates.put(ITEM_MATCH, "matchTemplate.xml");
134: validTemplates.put(ITEM_MCMC, "mcMCTemplate.xml");
135: validTemplates.put(ITEM_MCSC, "mcSCTemplate.xml");
136: validTemplates.put(ITEM_SURVEY, "mcSurveyTemplate.xml");
137: validTemplates.put(ITEM_TF, "trueFalseTemplate.xml");
138: validTemplates.put(SURVEY_10, SURVEY_PATH + "10.xml");
139: validTemplates.put(SURVEY_5, SURVEY_PATH + "5.xml");
140: validTemplates.put(SURVEY_AGREE, SURVEY_PATH + "AGREE.xml");
141: validTemplates.put(SURVEY_AVERAGE, SURVEY_PATH + "AVERAGE.xml");
142: validTemplates.put(SURVEY_EXCELLENT, SURVEY_PATH
143: + "EXCELLENT.xml");
144: validTemplates.put(SURVEY_STRONGLY, SURVEY_PATH
145: + "STRONGLY_AGREE.xml");
146: validTemplates.put(SURVEY_UNDECIDED, SURVEY_PATH
147: + "UNDECIDED.xml");
148: validTemplates.put(SURVEY_YES, SURVEY_PATH + "YES.xml");
149: }
150:
151: /**
152: * test that a String is a valid template key
153: * @param s a key
154: * @return true if it is a valid key
155: */
156: public boolean valid(String s) {
157: return validTemplates.containsKey(s);
158: }
159:
160: /**
161: * get template as stream using spring's ClassPathResource
162: * @param templateName
163: * @param context
164: * @return
165: */
166: public InputStream getTemplateInputStream(String templateName) {
167: InputStream is = null;
168:
169: try {
170: if (!this .valid(templateName)) {
171: throw new IllegalArgumentException(
172: "not a valid template: " + templateName);
173: }
174: ClassPathResource resource = new ClassPathResource(
175: TEMPLATE_PATH + qtiPath + "/" + templateName);
176: is = resource.getInputStream();
177: } catch (Exception e) {
178: log.error(e.getMessage(), e);
179: }
180:
181: return is;
182: }
183:
184: /**
185: * get template as stream using local context
186: * this presupposes a path of TEMPLATE_PATH off of /
187: * this is useful for unit testing
188: * @param templateName
189: * @return the input stream
190: */
191: /*
192: This method is probably not needed now that FacesContext has been
193: replaced for ClassPathResource (which just needs spring)..
194:
195: public InputStream getTemplateInputStream(String templateName)
196: {
197: InputStream is = null;
198:
199: try
200: {
201:
202: Properties props =
203: PathInfo.getInstance().getSettingsProperties(SETTINGS_FILE);
204: String basePath = props.getProperty("templateBasePath");
205:
206: if (!this.valid(templateName))
207: {
208: throw new IllegalArgumentException("not a valid template: " +
209: templateName);
210: }
211: is = new FileInputStream(basePath + TEMPLATE_PATH + "/" + qtiPath + "/" + templateName);
212:
213: }
214: catch (FileNotFoundException e)
215: {
216: log.error(e.getMessage(), e);
217: }
218: catch (IOException e1)
219: {
220: log.error(e1.getMessage(), e1);
221: }
222: catch (Exception e)
223: {
224: log.error(e.getMessage(), e);
225: }
226: return is;
227: }
228: */
229:
230: /**
231: * get a template as a string from its input stream
232: * @param templateName
233: * @return the xml string
234: */
235: public String getTemplateAsString(InputStream templateStream) {
236: InputStreamReader reader;
237: String xmlString = null;
238: try {
239: reader = new InputStreamReader(templateStream);
240: StringWriter out = new StringWriter();
241: int c;
242:
243: while ((c = reader.read()) != -1) {
244: out.write(c);
245: }
246:
247: reader.close();
248: xmlString = (String) out.toString();
249: } catch (Exception e) {
250: log.error(e.getMessage(), e);
251: }
252:
253: return xmlString;
254:
255: }
256:
257: public boolean isAssessment(String documentType) {
258: if (ASSESSMENT.equals(documentType)) {
259: return true;
260: }
261: return false;
262: }
263:
264: public boolean isSection(String documentType) {
265: if (SECTION.equals(documentType)) {
266: return true;
267: }
268: return false;
269: }
270:
271: public boolean isItem(String documentType) {
272: if (valid(documentType) && documentType.startsWith("ITEM_")) {
273: return true;
274: }
275: return false;
276: }
277:
278: public boolean isSurveyFragment(String documentType) {
279: if (valid(documentType) && documentType.startsWith("SURVEY_")) {
280: return true;
281: }
282: return false;
283: }
284:
285: /**
286: * Based on method in XmlStringBuffer
287: * @author rpembry
288: * @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
289: * @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
290: * @param document Document
291: * @param xpath
292: * @param element
293: * @return modified Document
294: */
295: public Document update(Document document, String xpath,
296: Element element) {
297: if (log.isDebugEnabled()) {
298: log.debug("update(String " + xpath + ", Element " + element
299: + ")");
300: }
301:
302: List itemResults = this .selectNodes(document, xpath);
303: Iterator iterator = itemResults.iterator();
304: while (iterator.hasNext()) {
305: Element node = (Element) iterator.next();
306: Element replacement = (Element) node.getOwnerDocument()
307: .importNode(element, true);
308: node.getParentNode().replaceChild(replacement, node);
309: }
310:
311: if (itemResults.size() == 0) {
312: String parentPath = xpath.substring(0, xpath
313: .lastIndexOf("/"));
314: addElement(document, parentPath, element);
315: }
316:
317: return document;
318: }
319:
320: /**
321: * perform Update on this object
322: * Based on method originally in XmlStringBuffer
323: * @author rashmi
324: * @author casong
325: * @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
326: * @param document Document
327: * @param xpath :- xpath and
328: * @param value :- Value of xpath
329: *
330: * @return modified Document
331: * @throws DOMException DOCUMENTATION PENDING
332: * @throws Exception DOCUMENTATION PENDING
333: */
334: public Document update(Document document, String xpath, String value)
335: throws DOMException, Exception {
336: if (log.isDebugEnabled()) {
337: log.debug("update(String " + xpath + ", String " + value
338: + ")");
339: }
340:
341: try {
342: Element newElement = null;
343: Attr newAttribute = null;
344: List newElementList = this .selectNodes(document, xpath);
345: int aIndex = xpath.indexOf("@");
346: int size = newElementList.size();
347: if (size > 1) {
348: log.warn("UPDATING MORE THAN ONE ELEMENT");
349: }
350:
351: if ((aIndex == -1) && (size != 0)) {
352: for (int i = 0; i < size; i++) {
353: newElement = (Element) newElementList.get(i);
354: Node childNode = newElement.getFirstChild();
355:
356: if (childNode == null) {
357: DocumentBuilderFactory dbf = DocumentBuilderFactory
358: .newInstance();
359: DocumentBuilder db = dbf.newDocumentBuilder();
360: Document core = db.newDocument();
361: Text newElementText = core
362: .createTextNode(newElement
363: .getNodeName());
364: newElementText.setNodeValue(value);
365: Text clonedText = (Text) newElement
366: .getOwnerDocument().importNode(
367: newElementText, true);
368: newElement.appendChild(clonedText);
369: } else {
370: CharacterData newElementText = (CharacterData) newElement
371: .getFirstChild();
372: newElementText.setNodeValue(value);
373: }
374: }
375: }
376:
377: if ((aIndex != -1) && (size != 0)) {
378: newAttribute = (Attr) newElementList.set(0,
379: newAttribute);
380: if (newAttribute != null) {
381: newAttribute.setValue(value);
382: }
383: }
384: } catch (Exception ex) {
385: log.error(ex.getMessage(), ex);
386: }
387:
388: return document;
389: }
390:
391: /**
392: * Based on method in XmlStringBuffer
393: * @author rpembry
394: * @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
395: * @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
396: *
397: * @param document Document
398: * @param parentXpath
399: * @param element
400: * @return modified Document
401: */
402: public Document addElement(Document document, String parentXpath,
403: Element element) {
404: if (log.isDebugEnabled()) {
405: log.debug("addElement(String " + parentXpath + ", Element "
406: + element + ")");
407: }
408:
409: List nodes = this .selectNodes(document, parentXpath);
410: Iterator iterator = nodes.iterator();
411: while (iterator.hasNext()) {
412: Element parent = (Element) iterator.next();
413: if (!parent.equals(element.getOwnerDocument())) {
414: element = (Element) parent.getOwnerDocument()
415: .importNode(element, true);
416: }
417:
418: parent.insertBefore(element, null);// inserts at end, as before-reference is null
419: }
420: return document;
421: }
422:
423: /**
424: * Based on method in XmlStringBuffer
425: * @author rpembry
426: * @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
427: * @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
428: *
429: * @param document Document
430: * @param elementXpath
431: * @param attributeName
432: * @return modified Document
433: */
434: public Document addAttribute(Document document,
435: String elementXpath, String attributeName) {
436: if (log.isDebugEnabled()) {
437: log.debug("addAttribute(String " + elementXpath
438: + ", String" + attributeName + ")");
439: }
440:
441: List nodes = this .selectNodes(document, elementXpath);
442: int size = nodes.size();
443: for (int i = 0; i < size; i++) {
444: Element element = (Element) nodes.get(i);
445: element.setAttribute(attributeName, "");
446: }
447:
448: return document;
449: }
450:
451: /**
452: * Based on method in XmlStringBuffer
453: * @author rpembry
454: * @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
455: * @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
456: *
457: * @return a List of Nodes
458: */
459: public final List selectNodes(Document document, String xpath) {
460: if (log.isDebugEnabled()) {
461: log.debug("selectNodes(String " + xpath + ")");
462: }
463:
464: List result = new ArrayList();
465:
466: try {
467: XPath path = new DOMXPath(xpath);
468: result = path.selectNodes(document);
469: } catch (JaxenException je) {
470: log.error(je.getMessage(), je);
471: }
472:
473: return result;
474: }
475:
476: /**
477: * read in XML document from input stream
478: * @param inputStream source for XML document
479: * @return the Document
480: */
481: public Document readXMLDocument(InputStream inputStream) {
482: if (log.isDebugEnabled()) {
483: log.debug("readDocument(InputStream " + inputStream);
484: }
485:
486: Document document = null;
487: DocumentBuilderFactory builderFactory = DocumentBuilderFactory
488: .newInstance();
489: builderFactory.setNamespaceAware(true);
490:
491: try {
492: DocumentBuilder documentBuilder = builderFactory
493: .newDocumentBuilder();
494: document = documentBuilder.newDocument();
495: document = documentBuilder.parse(inputStream);
496: } catch (ParserConfigurationException e) {
497: log.error(e.getMessage(), e);
498: } catch (SAXException e) {
499: log.error(e.getMessage(), e);
500: } catch (IOException e) {
501: log.error(e.getMessage(), e);
502: }
503:
504: return document;
505: }
506:
507: /**
508: * Read a DOM Document from xml in a string.
509: * @param in The string containing the XML
510: * @return A new DOM Document with the xml contents.
511: */
512: public static Document readDocumentFromString(String in) {
513: try {
514: DocumentBuilder docBuilder = null;
515:
516: DocumentBuilderFactory dbf = DocumentBuilderFactory
517: .newInstance();
518: dbf.setNamespaceAware(false);
519: docBuilder = dbf.newDocumentBuilder();
520: InputSource inputSource = new InputSource(new StringReader(
521: in));
522: Document doc = docBuilder.parse(inputSource);
523: return doc;
524: } catch (Exception any) {
525: log.warn("Xml.readDocumentFromString: " + any.toString());
526: return null;
527: }
528:
529: }
530:
531: public int getQtiVersion() {
532: return qtiVersion;
533: }
534:
535: public void setQtiVersion(int qtiVersion) {
536: this .qtiVersion = qtiVersion;
537: }
538:
539: public String getQtiPath() {
540: return qtiPath;
541: }
542:
543: public void setQtiPath(String qtiPath) {
544: this .qtiPath = qtiPath;
545: }/// readDocumentFromString
546:
547: }
|