/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/sam/trunk/samigo-qti/src/java/org/sakaiproject/tool/assessment/qti/helper/AuthoringXml.java $
* $Id: AuthoringXml.java 106463 2012-04-02 12:20:09Z david.horwitz@uct.ac.za $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008, 2009 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.tool.assessment.qti.helper;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.core.io.ClassPathResource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.dom.DOMXPath;
import org.w3c.dom.Attr;
import org.w3c.dom.CharacterData;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.sakaiproject.tool.assessment.qti.constants.QTIVersion;
/**
* <p>Utility to load XML templates from Sprint context or local file system.</p>
* <p> </p>
* <p>Copyright: Copyright (c) 2005 Sakai</p>
* <p> </p>
* @author Ed Smiley esmiley@stanford.edu
* @version $Id: AuthoringXml.java 106463 2012-04-02 12:20:09Z david.horwitz@uct.ac.za $
*/
public class AuthoringXml
{
private static Log log = LogFactory.getLog(AuthoringXml.class);
public static final String SETTINGS_FILE = "SAM.properties";
// paths
public static final String TEMPLATE_PATH = "xml/author/";
public static final String SURVEY_PATH = "survey/";
//assessment template
public static final String ASSESSMENT = "assessmentTemplate.xml";
// section template
public static final String SECTION = "sectionTemplate.xml";
// item templates
public static final String ITEM_AUDIO = "audioRecordingTemplate.xml";
public static final String ITEM_ESSAY = "essayTemplate.xml";
public static final String ITEM_FIB = "fibTemplate.xml";
public static final String ITEM_FIN = "finTemplate.xml";
public static final String ITEM_FILE = "fileUploadTemplate.xml";
public static final String ITEM_MATCH = "matchTemplate.xml";
public static final String ITEM_MCMC = "mcMCTemplate.xml";
public static final String ITEM_MCMC_SS = "mcMCSSTemplate.xml";
public static final String ITEM_MCSC = "mcSCTemplate.xml";
public static final String ITEM_SURVEY = "mcSurveyTemplate.xml";
public static final String ITEM_TF = "trueFalseTemplate.xml";
public static final String ITEM_MATCHING = "matchTemplate.xml";
public static final String ITEM_MXSURVEY = "mxSurveyTemplate.xml";
public static final String ITEM_CALCQ = "calculatedQuestionTemplate.xml"; // CALCULATED_QUESTION
public static final String SURVEY_10 = SURVEY_PATH + "10.xml";
public static final String SURVEY_5 = SURVEY_PATH + "5.xml";
public static final String SURVEY_AGREE = SURVEY_PATH + "AGREE.xml";
public static final String SURVEY_AVERAGE = SURVEY_PATH + "AVERAGE.xml";
public static final String SURVEY_EXCELLENT = SURVEY_PATH +
"EXCELLENT.xml";
public static final String SURVEY_STRONGLY = SURVEY_PATH +
"STRONGLY_AGREE.xml";
public static final String SURVEY_UNDECIDED = SURVEY_PATH +
"UNDECIDED.xml";
public static final String SURVEY_YES = SURVEY_PATH + "YES.xml";
private static final String QTI_12_PATH = "v1p2";
private static final String QTI_20_PATH = "v2p0";
public Map validTemplates = null;
private int qtiVersion;
private String qtiPath;
public AuthoringXml(int qtiVersion)
{
this.qtiVersion = qtiVersion;
if (qtiVersion == QTIVersion.VERSION_1_2)
{
qtiPath = QTI_12_PATH;
}
else if (qtiVersion == QTIVersion.VERSION_2_0)
{
qtiPath = QTI_20_PATH;
}
else
{
throw new IllegalArgumentException("Unsupported qti version");
}
}
/**
* test that a String is a valid template key
* @param s a key
* @return true if it is a valid key
*/
public boolean valid(String s)
{
return validTemplates.containsKey(s);
}
/**
* get template as stream using spring's ClassPathResource
* @param templateName
* @param context
* @return
*/
public InputStream getTemplateInputStream(String templateName)
{
InputStream is = null;
try
{
ClassPathResource resource =
new ClassPathResource(TEMPLATE_PATH + qtiPath + "/" + templateName);
is = resource.getInputStream();
}
catch (Exception e)
{
log.error(e.getMessage(), e);
}
return is;
}
/**
* get template as stream using local context
* this presupposes a path of TEMPLATE_PATH off of /
* this is useful for unit testing
* @param templateName
* @return the input stream
*/
/*
This method is probably not needed now that FacesContext has been
replaced for ClassPathResource (which just needs spring)..
public InputStream getTemplateInputStream(String templateName)
{
InputStream is = null;
try
{
Properties props =
PathInfo.getInstance().getSettingsProperties(SETTINGS_FILE);
String basePath = props.getProperty("templateBasePath");
if (!this.valid(templateName))
{
throw new IllegalArgumentException("not a valid template: " +
templateName);
}
is = new FileInputStream(basePath + TEMPLATE_PATH + "/" + qtiPath + "/" + templateName);
}
catch (FileNotFoundException e)
{
log.error(e.getMessage(), e);
}
catch (IOException e1)
{
log.error(e1.getMessage(), e1);
}
catch (Exception e)
{
log.error(e.getMessage(), e);
}
return is;
}
*/
/**
* get a template as a string from its input stream
* @param templateName
* @return the xml string
*/
public String getTemplateAsString(InputStream templateStream)
{
InputStreamReader reader;
String xmlString = null;
try
{
reader = new InputStreamReader(templateStream);
StringWriter out = new StringWriter();
int c;
while ( (c = reader.read()) != -1)
{
out.write(c);
}
reader.close();
xmlString = (String) out.toString();
}
catch (Exception e)
{
log.error(e.getMessage(), e);
}
return xmlString;
}
public boolean isAssessment(String documentType)
{
if (ASSESSMENT.equals(documentType))
{
return true;
}
return false;
}
public boolean isSection(String documentType)
{
if (SECTION.equals(documentType))
{
return true;
}
return false;
}
public boolean isItem(String documentType)
{
if (documentType.startsWith("ITEM_"))
{
return true;
}
return false;
}
public boolean isSurveyFragment(String documentType)
{
if (documentType.startsWith("SURVEY_"))
{
return true;
}
return false;
}
/**
* Based on method in XmlStringBuffer
* @author rpembry
* @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
* @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
* @param document Document
* @param xpath
* @param element
* @return modified Document
*/
public Document update(Document document, String xpath, Element element)
{
if (log.isDebugEnabled())
{
log.debug("update(String " + xpath + ", Element " + element + ")");
}
List itemResults = this.selectNodes(document, xpath);
Iterator iterator = itemResults.iterator();
while (iterator.hasNext())
{
Element node = (Element) iterator.next();
Element replacement =
(Element) node.getOwnerDocument().importNode(element, true);
node.getParentNode().replaceChild(replacement, node);
}
if (itemResults.size() == 0)
{
String parentPath = xpath.substring(0, xpath.lastIndexOf("/"));
addElement(document, parentPath, element);
}
return document;
}
/**
* perform Update on this object
* Based on method originally in XmlStringBuffer
* @author rashmi
* @author casong
* @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
* @param document Document
* @param xpath :- xpath and
* @param value :- Value of xpath
*
* @return modified Document
* @throws DOMException DOCUMENTATION PENDING
* @throws Exception DOCUMENTATION PENDING
*/
public Document update(Document document, String xpath,
String value) throws DOMException, Exception
{
if (log.isDebugEnabled())
{
log.debug("update(String " + xpath + ", String " + value + ")");
}
try
{
Element newElement = null;
Attr newAttribute = null;
List newElementList = this.selectNodes(document, xpath);
int aIndex = xpath.indexOf("@");
int size = newElementList.size();
if (size > 1)
{
log.warn("UPDATING MORE THAN ONE ELEMENT");
}
if ( (aIndex == -1) && (size != 0))
{
for (int i = 0; i < size; i++)
{
newElement = (Element) newElementList.get(i);
Node childNode = newElement.getFirstChild();
if (childNode == null)
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document core = db.newDocument();
Text newElementText =
core.createTextNode(newElement.getNodeName());
newElementText.setNodeValue(value);
Text clonedText =
(Text) newElement.getOwnerDocument().importNode(
newElementText, true);
newElement.appendChild(clonedText);
}
else
{
CharacterData newElementText =
(CharacterData) newElement.getFirstChild();
newElementText.setNodeValue(value);
}
}
}
if ( (aIndex != -1) && (size != 0))
{
newAttribute = (Attr) newElementList.set(0, null);
if (newAttribute != null)
{
newAttribute.setValue(value);
}
}
}
catch (Exception ex)
{
log.error(ex.getMessage(), ex);
}
return document;
}
/**
* Based on method in XmlStringBuffer
* @author rpembry
* @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
* @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
*
* @param document Document
* @param parentXpath
* @param element
* @return modified Document
*/
public Document addElement(Document document, String parentXpath,
Element element)
{
if (log.isDebugEnabled())
{
log.debug(
"addElement(String " + parentXpath + ", Element " + element + ")");
}
List nodes = this.selectNodes(document, parentXpath);
Iterator iterator = nodes.iterator();
while (iterator.hasNext())
{
Element parent = (Element) iterator.next();
if (!parent.getOwnerDocument().equals(element.getOwnerDocument()))
{
element = (Element) parent.getOwnerDocument().importNode(element, true);
}
parent.insertBefore(element, null);// inserts at end, as before-reference is null
}
return document;
}
/**
* Based on method in XmlStringBuffer
* @author rpembry
* @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
* @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
*
* @param document Document
* @param elementXpath
* @param attributeName
* @return modified Document
*/
public Document addAttribute(Document document, String elementXpath,
String attributeName)
{
if (log.isDebugEnabled())
{
log.debug(
"addAttribute(String " + elementXpath + ", String" + attributeName +
")");
}
List nodes = this.selectNodes(document, elementXpath);
int size = nodes.size();
for (int i = 0; i < size; i++)
{
Element element = (Element) nodes.get(i);
element.setAttribute(attributeName, "");
}
return document;
}
/**
* Based on method in XmlStringBuffer
* @author rpembry
* @author casong changed XmlStringBuffer to be org.w3c.dom compliance,
* @author Ed Smiley esmiley@stanford.edu changed method signatures used Document
*
* @return a List of Nodes
*/
public final List selectNodes(Document document, String xpath)
{
if (log.isDebugEnabled())
{
log.debug("selectNodes(String " + xpath + ")");
}
List result = new ArrayList();
try
{
XPath path = new DOMXPath(xpath);
result = path.selectNodes(document);
}
catch (JaxenException je)
{
log.error(je.getMessage(), je);
}
return result;
}
/**
* read in XML document from input stream
* @param inputStream source for XML document
* @return the Document
*/
public Document readXMLDocument(InputStream inputStream)
{
if (log.isDebugEnabled())
{
log.debug("readDocument(InputStream " + inputStream);
}
Document document = null;
DocumentBuilderFactory builderFactory =
DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
try
{
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
document = documentBuilder.newDocument();
document = documentBuilder.parse(inputStream);
}
catch (ParserConfigurationException e)
{
log.error(e.getMessage(), e);
}
catch (SAXException e)
{
log.error(e.getMessage(), e);
}
catch (IOException e)
{
log.error(e.getMessage(), e);
}
return document;
}
/**
* Read a DOM Document from xml in a string.
* @param in The string containing the XML
* @return A new DOM Document with the xml contents.
*/
public static Document readDocumentFromString(String in)
{
try
{
DocumentBuilder docBuilder = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);
docBuilder = dbf.newDocumentBuilder();
InputSource inputSource = new InputSource(new StringReader(in));
Document doc = docBuilder.parse(inputSource);
return doc;
}
catch (Exception any)
{
log.warn("Xml.readDocumentFromString: " + any.toString());
return null;
}
}
public int getQtiVersion()
{
return qtiVersion;
}
public void setQtiVersion(int qtiVersion)
{
this.qtiVersion = qtiVersion;
}
public String getQtiPath()
{
return qtiPath;
}
public void setQtiPath(String qtiPath)
{
this.qtiPath = qtiPath;
}/// readDocumentFromString
}