Index: lams_build/lib/lams/lams.jar
===================================================================
diff -u -rbb597b8155375e6ac4dfe280f630d323b6e5e575 -rf26fb3937b73bfdefd25a6166863ea188d5f8cb9
Binary files differ
Index: lams_central/src/java/org/lamsfoundation/lams/web/QuestionsAction.java
===================================================================
diff -u -r0dbc1d60bc9b43c26c431c9a2e15981ca0863d46 -rf26fb3937b73bfdefd25a6166863ea188d5f8cb9
--- lams_central/src/java/org/lamsfoundation/lams/web/QuestionsAction.java (.../QuestionsAction.java) (revision 0dbc1d60bc9b43c26c431c9a2e15981ca0863d46)
+++ lams_central/src/java/org/lamsfoundation/lams/web/QuestionsAction.java (.../QuestionsAction.java) (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -28,8 +28,8 @@
* Runs extraction of chosen IMS QTI zip file and prepares form for user to manually choose interesting question.
*
* @struts.action path="/questions" validate="false"
- * @struts.action-forward name="questionChoice" path="/questionChoice.jsp"
- * @struts.action-forward name="questionFile" path="/questionFile.jsp"
+ * @struts.action-forward name="questionChoice" path="/questions/questionChoice.jsp"
+ * @struts.action-forward name="questionFile" path="/questions/questionFile.jsp"
*/
public class QuestionsAction extends Action {
@SuppressWarnings("unchecked")
@@ -58,7 +58,10 @@
}
}
+ // this parameter is not really used at the moment
request.setAttribute("returnURL", returnURL);
+
+ // show only chosen types of questions
request.setAttribute("limitType", limitTypeParam);
// user did not choose a file
Fisheye: Tag f26fb3937b73bfdefd25a6166863ea188d5f8cb9 refers to a dead (removed) revision in file `lams_central/web/questionChoice.jsp'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag f26fb3937b73bfdefd25a6166863ea188d5f8cb9 refers to a dead (removed) revision in file `lams_central/web/questionFile.jsp'.
Fisheye: No comparison available. Pass `N' to diff?
Index: lams_central/web/questions/imsmanifest_template.xml
===================================================================
diff -u
--- lams_central/web/questions/imsmanifest_template.xml (revision 0)
+++ lams_central/web/questions/imsmanifest_template.xml (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -0,0 +1,33 @@
+
+
+
+ IMS Content
+ 1.1.3
+
+
+
+ [ID]
+
+
+ [TITLE] (LAMS IMS QTI export)
+
+
+
+
+
+
+ default
+ -
+ Exam 1
+
+
+
+
+
+ [FILE_LIST]
+
+
+
\ No newline at end of file
Index: lams_central/web/questions/questionChoice.jsp
===================================================================
diff -u
--- lams_central/web/questions/questionChoice.jsp (revision 0)
+++ lams_central/web/questions/questionChoice.jsp (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -0,0 +1,248 @@
+
+
+<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %>
+<%@ taglib uri="tags-lams" prefix="lams" %>
+<%@ taglib uri="tags-fmt" prefix="fmt" %>
+<%@ taglib uri="tags-core" prefix="c" %>
+<%@ taglib uri="tags-function" prefix="fn" %>
+
+
+
+ ::
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: lams_central/web/questions/questionFile.jsp
===================================================================
diff -u
--- lams_central/web/questions/questionFile.jsp (revision 0)
+++ lams_central/web/questions/questionFile.jsp (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -0,0 +1,62 @@
+
+
+<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %>
+<%@ taglib uri="tags-lams" prefix="lams" %>
+<%@ taglib uri="tags-fmt" prefix="fmt" %>
+<%@ taglib uri="tags-html" prefix="html" %>
+<%@ taglib uri="tags-core" prefix="c"%>
+
+
+
+ ::
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/questions/Question.java
===================================================================
diff -u -r0dbc1d60bc9b43c26c431c9a2e15981ca0863d46 -rf26fb3937b73bfdefd25a6166863ea188d5f8cb9
--- lams_common/src/java/org/lamsfoundation/lams/questions/Question.java (.../Question.java) (revision 0dbc1d60bc9b43c26c431c9a2e15981ca0863d46)
+++ lams_common/src/java/org/lamsfoundation/lams/questions/Question.java (.../Question.java) (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -32,6 +32,9 @@
import org.apache.commons.lang.builder.HashCodeBuilder;
public class Question {
+ // just for convenience when returning from methods
+ public static final Question[] QUESTION_ARRAY_TYPE = new Question[] {};
+
public static final String QUESTION_TYPE_MULTIPLE_CHOICE = "mc";
public static final String QUESTION_TYPE_MULTIPLE_RESPONSE = "mr";
public static final String QUESTION_TYPE_TRUE_FALSE = "tf";
Index: lams_common/src/java/org/lamsfoundation/lams/questions/QuestionExporter.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/questions/QuestionExporter.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/questions/QuestionExporter.java (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -0,0 +1,469 @@
+/****************************************************************
+ * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org)
+ * =============================================================
+ * License Information: http://lamsfoundation.org/licensing/lams/2.0/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA
+ *
+ * http://www.gnu.org/licenses/gpl.txt
+ * ****************************************************************
+ */
+
+/* $Id$ */
+package org.lamsfoundation.lams.questions;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.lamsfoundation.lams.util.CentralConstants;
+import org.lamsfoundation.lams.util.Configuration;
+import org.lamsfoundation.lams.util.ConfigurationKeys;
+import org.lamsfoundation.lams.util.FileUtil;
+import org.lamsfoundation.lams.util.zipfile.ZipFileUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Packs questions and answers into files. They can be later used in question-based 3rd party applications. Currently it
+ * supports only IMS QTI but other methods can be added as needed.
+ *
+ * @author Marcin Cieslak
+ *
+ */
+public class QuestionExporter {
+ private static final Logger log = Logger.getLogger(QuestionExporter.class);
+
+ private static final Pattern IMAGE_PATTERN = Pattern.compile("",
+ Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
+ private static final String IMAGE_MARKER = "[matimage]";
+
+ private static final String EXPORT_TEMP_FOLDER_SUFFIX = "qti";
+
+ private static final String EAR_IMAGE_FOLDER = Configuration.get(ConfigurationKeys.LAMS_EAR_DIR) + File.separator
+ + FileUtil.LAMS_WWW_DIR;
+ private static final File MANIFEST_TEMPLATE_FILE = new File(Configuration.get(ConfigurationKeys.LAMS_EAR_DIR)
+ + File.separator + "lams-central.war" + File.separator + "questions" + File.separator
+ + "imsmanifest_template.xml");
+
+ private String packageTitle = null;
+ private Question[] questions = null;
+
+ private Document doc = null;
+ private Integer itemId = null;
+ private Map images = new TreeMap();
+
+ public QuestionExporter(String title, Question[] questions) {
+ if (StringUtils.isBlank(title)) {
+ this.packageTitle = "export";
+ } else {
+ // make sure the title can be used everywhere
+ this.packageTitle = FileUtil.stripInvalidChars(title).replaceAll(" ", "_");
+ }
+
+ this.questions = questions;
+ }
+
+ /**
+ * Writes the exported QTI package to HTTP response, so it can be downloaded by user via browser.
+ */
+ public void exportQTIPackage(HttpServletRequest request, HttpServletResponse response) {
+ String packagePath = exportQTIPackage();
+ File packageFile = new File(packagePath);
+
+ try {
+ String fileName = FileUtil.getFileName(packagePath);
+ fileName = FileUtil.encodeFilenameForDownload(request, fileName);
+ response.setContentType(CentralConstants.RESPONSE_CONTENT_TYPE_DOWNLOAD);
+ response.setHeader(CentralConstants.HEADER_CONTENT_DISPOSITION, CentralConstants.HEADER_CONTENT_ATTACHMENT
+ + fileName);
+
+ // write out the ZIP to respose error
+ FileUtils.copyFile(packageFile, response.getOutputStream());
+
+ // remove the directory containing the ZIP from file system
+ FileUtils.deleteDirectory(packageFile.getParentFile());
+ } catch (IOException e) {
+ QuestionExporter.log.error("Error while exporti QTI package", e);
+ }
+ }
+
+ /**
+ * Builds a QTI ZIP package (manifest, QTI file, resources) with the given questions.
+ *
+ * @return Path to the created ZIP file
+ */
+ public String exportQTIPackage() {
+ if (log.isDebugEnabled()) {
+ log.debug("Exporting QTI ZIP package \"" + packageTitle + "\"");
+ }
+ try {
+ String rootDir = FileUtil.createTempDirectory(QuestionExporter.EXPORT_TEMP_FOLDER_SUFFIX);
+ File dir = new File(rootDir, "content");
+
+ // main QTI file
+ String xmlFileName = packageTitle + ".xml";
+ File xmlFile = new File(dir, xmlFileName);
+ String xmlFileContent = exportQTIFile();
+ FileUtils.writeStringToFile(xmlFile, xmlFileContent, "UTF-8");
+
+ File manifestFile = new File(dir, "imsmanifest.xml");
+ String manifestContent = createManifest();
+ FileUtils.writeStringToFile(manifestFile, manifestContent, "UTF-8");
+
+ // copy images used in activities from lams-www to ZIP folder
+ for (String imageName : images.keySet()) {
+ File imageFile = new File(dir, imageName);
+ FileUtils.copyFile(images.get(imageName), imageFile);
+ }
+
+ String targetZipFileName = "lams_qti_" + packageTitle + ".zip";
+
+ return ZipFileUtil.createZipFile(targetZipFileName, dir.getAbsolutePath(), rootDir);
+ } catch (Exception e) {
+ QuestionExporter.log.error("Error while exporti QTI package", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Builds QTI XML file containing structured questions & answers content.
+ *
+ * @return XML file content
+ */
+ public String exportQTIFile() {
+ itemId = 1000;
+ DocumentBuilder docBuilder;
+ try {
+ docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ doc = docBuilder.newDocument();
+ } catch (ParserConfigurationException e) {
+ QuestionExporter.log.error("Error while instantinating Document Builder", e);
+ return null;
+ }
+
+ Element rootElem = (Element) doc.appendChild(doc.createElement("questestinterop"));
+ Element assessmentElem = (Element) rootElem.appendChild(doc.createElement("assessment"));
+ assessmentElem.setAttribute("title", packageTitle);
+ assessmentElem.setAttribute("ident", "A1001");
+ Element sectionElem = (Element) assessmentElem.appendChild(doc.createElement("section"));
+ sectionElem.setAttribute("title", "Main");
+ sectionElem.setAttribute("ident", "S1002");
+
+ for (Question question : questions) {
+ Element itemElem = null;
+ if (Question.QUESTION_TYPE_MULTIPLE_CHOICE.equals(question.getType())) {
+ itemElem = exportMultipleChoiceQuestion(question);
+ }
+
+ if (itemElem == null) {
+ QuestionExporter.log.warn("Unknow type \"" + question.getType() + " of question \""
+ + question.getTitle() + "\"");
+ } else {
+ sectionElem.appendChild(itemElem);
+ }
+ }
+
+ return writeOutDoc();
+ }
+
+ /**
+ * Creates a XML element with contents of a single multiple choice question.
+ */
+ private Element exportMultipleChoiceQuestion(Question question) {
+ Element itemElem = doc.createElement("item");
+ itemElem.setAttribute("title", question.getTitle());
+ itemId++;
+ itemElem.setAttribute("ident", "QUE_" + itemId);
+
+ // question text
+ Element presentationElem = (Element) itemElem.appendChild(doc.createElement("presentation"));
+ if (!StringUtils.isBlank(question.getText())) {
+ Element materialElem = (Element) presentationElem.appendChild(doc.createElement("material"));
+ appendMaterialElements(materialElem, question.getText());
+ }
+
+ itemId++;
+ String responseLidIdentifier = "QUE_" + itemId + "_RL";
+ Element responseLidElem = (Element) presentationElem.appendChild(doc.createElement("response_lid"));
+ responseLidElem.setAttribute("ident", responseLidIdentifier);
+ responseLidElem.setAttribute("rcardinality", "Single");
+ responseLidElem.setAttribute("rtiming", "No");
+
+ // question feedback (displayed no matter what answer was choosed)
+ List feedbackList = new ArrayList();
+ String incorrectFeedbackLabel = null;
+ Element overallFeedbackElem = null;
+ if (!StringUtils.isBlank(question.getFeedback())) {
+ overallFeedbackElem = createFeedbackElem("_ALL", question.getFeedback());
+ feedbackList.add(overallFeedbackElem);
+ }
+
+ Element renderChoiceElem = (Element) responseLidElem.appendChild(doc.createElement("render_choice"));
+ short answerId = 0;
+ List respconditionList = new ArrayList(question.getAnswers().size());
+
+ // iterate through answers, collecting some info along the way
+ for (Answer answer : question.getAnswers()) {
+ Element responseLabelElem = (Element) renderChoiceElem.appendChild(doc.createElement("response_label"));
+ itemId++;
+ answerId++;
+ String answerLabel = "QUE_" + itemId + "_A" + answerId;
+ responseLabelElem.setAttribute("ident", answerLabel);
+
+ // answer text
+ if (!StringUtils.isBlank(answer.getText())) {
+ Element materialElem = (Element) responseLabelElem.appendChild(doc.createElement("material"));
+ appendMaterialElements(materialElem, answer.getText());
+ }
+
+ // just labels for feedback for correct/incorrect answer
+ boolean isCorrect = answer.getScore() > 0;
+ Element feedbackElem = null;
+ if (!StringUtils.isBlank(answer.getFeedback())) {
+ feedbackElem = createFeedbackElem((isCorrect ? "_C" : "_IC"), answer.getFeedback());
+ feedbackList.add(feedbackElem);
+
+ if (!isCorrect && (incorrectFeedbackLabel == null)) {
+ incorrectFeedbackLabel = feedbackElem.getAttribute("ident");
+ }
+ }
+
+ // mark which answer is correct by setting score for each of them
+ Element respconditionElem = doc.createElement("respcondition");
+ Element conditionvarElem = (Element) respconditionElem.appendChild(doc.createElement("conditionvar"));
+ Element varequalElem = (Element) conditionvarElem.appendChild(doc.createElement("varequal"));
+ varequalElem.setAttribute("respident", responseLidIdentifier);
+ varequalElem.setTextContent(answerLabel);
+
+ Element setvarElem = (Element) respconditionElem.appendChild(doc.createElement("setvar"));
+ setvarElem.setAttribute("varname", "que_score");
+ setvarElem.setAttribute("action", isCorrect ? "Set" : "Add");
+ setvarElem.setTextContent(String.valueOf(answer.getScore()));
+
+ // link feedback for correct/incorrect answer
+ if (feedbackElem != null) {
+ Element displayfeedbackElem = (Element) respconditionElem.appendChild(doc
+ .createElement("displayfeedback"));
+ displayfeedbackElem.setAttribute("feedbacktype", "Response");
+ displayfeedbackElem.setAttribute("linkrefid", feedbackElem.getAttribute("ident"));
+ } else if (!isCorrect && (incorrectFeedbackLabel != null)) {
+ Element displayfeedbackElem = (Element) respconditionElem.appendChild(doc
+ .createElement("displayfeedback"));
+ displayfeedbackElem.setAttribute("feedbacktype", "Response");
+ displayfeedbackElem.setAttribute("linkrefid", incorrectFeedbackLabel);
+ }
+
+ if (overallFeedbackElem != null) {
+ Element displayfeedbackElem = (Element) respconditionElem.appendChild(doc
+ .createElement("displayfeedback"));
+ displayfeedbackElem.setAttribute("feedbacktype", "Response");
+ displayfeedbackElem.setAttribute("linkrefid", overallFeedbackElem.getAttribute("ident"));
+ }
+
+ respconditionList.add(respconditionElem);
+ }
+
+ if (overallFeedbackElem != null) {
+ Element respconditionElem = doc.createElement("respcondition");
+ Element conditionvarElem = (Element) respconditionElem.appendChild(doc.createElement("conditionvar"));
+ conditionvarElem.appendChild(doc.createElement("elem"));
+ Element displayfeedbackElem = (Element) respconditionElem.appendChild(doc.createElement("displayfeedback"));
+ displayfeedbackElem.setAttribute("feedbacktype", "Response");
+ displayfeedbackElem.setAttribute("linkrefid", overallFeedbackElem.getAttribute("ident"));
+
+ respconditionList.add(respconditionElem);
+ }
+
+ Element resprocessingElem = (Element) itemElem.appendChild(doc.createElement("resprocessing"));
+ Element outcomesElem = (Element) resprocessingElem.appendChild(doc.createElement("outcomes"));
+ Element decvarElem = (Element) outcomesElem.appendChild(doc.createElement("decvar"));
+ decvarElem.setAttribute("vartype", "decimal");
+ decvarElem.setAttribute("defaultval", "0");
+ decvarElem.setAttribute("varname", "que_score");
+
+ // write out elements collected during answer iteration
+ for (Element respconditionElem : respconditionList) {
+ resprocessingElem.appendChild(respconditionElem);
+ }
+
+ for (Element feedbackElem : feedbackList) {
+ itemElem.appendChild(feedbackElem);
+ }
+
+ return itemElem;
+ }
+
+ /**
+ * Creates a feedback XML element.
+ */
+ private Element createFeedbackElem(String labelSuffix, String feedback) {
+ itemId++;
+ String label = "QUE_" + itemId + labelSuffix;
+ Element feedbackElem = doc.createElement("itemfeedback");
+ feedbackElem.setAttribute("ident", label);
+ Element materialElem = (Element) feedbackElem.appendChild(doc.createElement("material"));
+ appendMaterialElements(materialElem, feedback);
+
+ return feedbackElem;
+ }
+
+ /**
+ * Transforms a DOM object to String representation.
+ */
+ private String writeOutDoc() {
+ DOMSource domSource = new DOMSource(doc);
+ StringWriter writer = new StringWriter();
+ StreamResult streamResult = new StreamResult(writer);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ try {
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ // a bit of beautification
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+ transformer.transform(domSource, streamResult);
+ } catch (Exception e) {
+ QuestionExporter.log.error("Error while writing out XML document", e);
+ return null;
+ }
+
+ String result = writer.toString();
+ try {
+ writer.close();
+ } catch (IOException e) {
+ QuestionExporter.log.warn("Writer could not be closed", e);
+ }
+
+ return result;
+ }
+
+ /**
+ * Extracts images from HTML text (probably created by CKEditor) and substitutes them with markers, so further
+ * processing knows how to handle them.
+ */
+ private String[] parseImages(String text) {
+ List result = new ArrayList();
+ int index = 0;
+ // looks for images stored in LAMS WWW secure folder
+ Matcher matcher = QuestionExporter.IMAGE_PATTERN.matcher(text);
+
+ while (matcher.find()) {
+ // add HTML which is before the image
+ result.add(text.substring(index, matcher.start()));
+ index = matcher.end();
+
+ // find the image in file system
+ String relativePath = matcher.group(1);
+ File image = new File(QuestionExporter.EAR_IMAGE_FOLDER, relativePath);
+ if (!image.isFile() || !image.canRead()) {
+ QuestionExporter.log.warn("Image could not be parsed: " + matcher.group());
+ continue;
+ }
+
+ // was it already added?
+ String imageName = null;
+ for (String key : images.keySet()) {
+ if (image.equals(images.get(key))) {
+ imageName = key;
+ break;
+ }
+ }
+
+ // if it wasn't added, store the reference so ZIP packing knows where to copy from and how to name the file
+ if (imageName == null) {
+ String baseImageName = FileUtil.getFileName(relativePath);
+ imageName = baseImageName;
+ short prefix = 1;
+ while (images.containsKey(imageName)) {
+ // if the name is the same, use an arbitrary prefix
+ imageName = prefix + "_" + baseImageName;
+ prefix++;
+ }
+
+ images.put(imageName, image);
+ }
+
+ result.add(QuestionExporter.IMAGE_MARKER + imageName);
+ }
+
+ // write out the rest of HTML text
+ if (index < text.length()) {
+ result.add(text.substring(index));
+ }
+
+ return result.toArray(new String[] {});
+ }
+
+ /**
+ * Appends material XML element, i.e. list of HTML & image parts
+ */
+ private void appendMaterialElements(Element materialElem, String text) {
+ String[] answerParts = parseImages(text);
+ for (String answerPart : answerParts) {
+ if (answerPart.startsWith(QuestionExporter.IMAGE_MARKER)) {
+ String imageName = answerPart.substring(QuestionExporter.IMAGE_MARKER.length());
+ String imageType = "image/" + FileUtil.getFileExtension(imageName);
+ Element matimageElem = (Element) materialElem.appendChild(doc.createElement("matimage"));
+ matimageElem.setAttribute("imagtype", imageType);
+ matimageElem.setAttribute("uri", imageName);
+ } else {
+ Element mattextElem = (Element) materialElem.appendChild(doc.createElement("mattext"));
+ mattextElem.setAttribute("texttype", "text/html");
+ mattextElem.appendChild(doc.createCDATASection(answerPart));
+ }
+ }
+ }
+
+ /**
+ * Fill the existing template file with current data.
+ * @return contents of XML template file
+ */
+ private String createManifest() throws IOException {
+ String id = UUID.randomUUID().toString();
+ String fileName = packageTitle + ".xml";
+
+ StringBuilder resourceEntries = new StringBuilder("\n");
+ for (String imageName : images.keySet()) {
+ resourceEntries.append("").append("\n");
+ }
+
+ String manifest = FileUtils.readFileToString(QuestionExporter.MANIFEST_TEMPLATE_FILE);
+ manifest = manifest.replace("[ID]", id).replace("[TITLE]", packageTitle).replace("[FILE_NAME]", fileName)
+ .replace("[FILE_LIST]", resourceEntries);
+ return manifest;
+ }
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/questions/QuestionParser.java
===================================================================
diff -u -r04a5075bd2cb89afd1f7b323d8e03ef4fa2f83e3 -rf26fb3937b73bfdefd25a6166863ea188d5f8cb9
--- lams_common/src/java/org/lamsfoundation/lams/questions/QuestionParser.java (.../QuestionParser.java) (revision 04a5075bd2cb89afd1f7b323d8e03ef4fa2f83e3)
+++ lams_common/src/java/org/lamsfoundation/lams/questions/QuestionParser.java (.../QuestionParser.java) (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -69,8 +69,6 @@
public class QuestionParser {
private static Logger log = Logger.getLogger(QuestionParser.class);
- // just for convenience when returning from methods
- private static final Question[] QUESTION_ARRAY_TYPE = new Question[] {};
// can be anything
private static final String TEMP_PACKAGE_NAME_PREFIX = "QTI_PACKAGE_";
private static final Pattern IMAGE_PATTERN = Pattern.compile("\\[IMAGE: (.*)\\]");
@@ -124,7 +122,7 @@
}
}
- return result.toArray(QuestionParser.QUESTION_ARRAY_TYPE);
+ return result.toArray(Question.QUESTION_ARRAY_TYPE);
}
/**
@@ -362,7 +360,7 @@
result.add(question);
}
- return result.toArray(QuestionParser.QUESTION_ARRAY_TYPE);
+ return result.toArray(Question.QUESTION_ARRAY_TYPE);
}
/**
@@ -463,7 +461,7 @@
}
}
- return result.toArray(QuestionParser.QUESTION_ARRAY_TYPE);
+ return result.toArray(Question.QUESTION_ARRAY_TYPE);
}
/**
Index: lams_tool_lamc/conf/language/lams/ApplicationResources.properties
===================================================================
diff -u -red84afe5b317b35b156848df66a1a9332af0f4b1 -rf26fb3937b73bfdefd25a6166863ea188d5f8cb9
--- lams_tool_lamc/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision ed84afe5b317b35b156848df66a1a9332af0f4b1)
+++ lams_tool_lamc/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -261,6 +261,7 @@
monitor.summary.date.restriction.set =Deadline has been set
monitor.summary.date.restriction.removed =Deadline has been removed
label.authoring.import.qti =Import IMS QTI
+label.authoring.export.qti =Export IMS QTI
error.correct.answer.blank =Please correct this: Correct answer cannot be blank.
label.submit =Finish
label.report.by.question =Report by question
Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/McAction.java
===================================================================
diff -u -r9375ad2403d36eb4af0ddd61ad395765f15e2e67 -rf26fb3937b73bfdefd25a6166863ea188d5f8cb9
--- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/McAction.java (.../McAction.java) (revision 9375ad2403d36eb4af0ddd61ad395765f15e2e67)
+++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/McAction.java (.../McAction.java) (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -52,6 +52,7 @@
import org.lamsfoundation.lams.contentrepository.client.IToolContentHandler;
import org.lamsfoundation.lams.questions.Answer;
import org.lamsfoundation.lams.questions.Question;
+import org.lamsfoundation.lams.questions.QuestionExporter;
import org.lamsfoundation.lams.questions.QuestionParser;
import org.lamsfoundation.lams.tool.exception.ToolException;
import org.lamsfoundation.lams.tool.mc.EditActivityDTO;
@@ -469,7 +470,7 @@
for (Answer answer : question.getAnswers()) {
McCandidateAnswersDTO mcCandidateAnswersDTO = new McCandidateAnswersDTO();
String answerText = QuestionParser.processHTMLField(answer.getText(), false, contentFolderID,
- question.getResourcesFolderPath());
+ question.getResourcesFolderPath());
if (answerText == null) {
LamsDispatchAction.log.warn("Skipping a blank answer");
continue;
@@ -531,9 +532,52 @@
defaultContentIdStr, mcService, httpSessionID, listQuestionContentDTO);
request.setAttribute(McAppConstants.TOTAL_QUESTION_COUNT, new Integer(listQuestionContentDTO.size()));
- return (mapping.findForward(McAppConstants.LOAD));
+ return mapping.findForward(McAppConstants.LOAD);
}
+ /**
+ * Prepares MC questions for QTI packing
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public ActionForward exportQTI(ActionMapping mapping, ActionForm form, HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ String httpSessionID = request.getParameter("httpSessionID");
+ SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(httpSessionID);
+
+ List listQuestionContentDTO = (List) sessionMap
+ .get(McAppConstants.LIST_QUESTION_CONTENT_DTO_KEY);
+ List questions = new LinkedList();
+
+ for (McQuestionContentDTO mcQuestion : listQuestionContentDTO) {
+ Question question = new Question();
+
+ question.setType(Question.QUESTION_TYPE_MULTIPLE_CHOICE);
+ question.setTitle("Question " + mcQuestion.getDisplayOrder());
+ question.setText(mcQuestion.getQuestion());
+ question.setFeedback(mcQuestion.getFeedback());
+ List answers = new ArrayList();
+
+ for (McCandidateAnswersDTO mcAnswer : (List) mcQuestion.getListCandidateAnswersDTO()) {
+ Answer answer = new Answer();
+ answer.setText(mcAnswer.getCandidateAnswer());
+ answer.setScore("Correct".equalsIgnoreCase(mcAnswer.getCorrect()) ? Float.parseFloat(mcQuestion
+ .getMark()) : 0);
+
+ answers.add(answer);
+ question.setAnswers(answers);
+ }
+
+ // put the question in the right place
+ questions.add(Integer.parseInt(mcQuestion.getDisplayOrder()) - 1, question);
+ }
+
+ String title = request.getParameter("title");
+ QuestionExporter exporter = new QuestionExporter(title, questions.toArray(Question.QUESTION_ARRAY_TYPE));
+ exporter.exportQTIPackage(request, response);
+
+ return null;
+ }
+
protected void commonSaveCode(HttpServletRequest request, McGeneralAuthoringDTO mcGeneralAuthoringDTO,
McAuthoringForm mcAuthoringForm, SessionMap sessionMap, String activeModule, String strToolContentID,
String defaultContentIdStr, IMcService mcService, String httpSessionID, List listQuestionContentDTO) {
@@ -1235,7 +1279,6 @@
return (mapping.findForward("itemList"));
}
-
/**
*
Index: lams_tool_lamc/web/authoring/BasicContent.jsp
===================================================================
diff -u -r87ff0b33fa2d006a084b2ae9b7ff14d4c4be0f6d -rf26fb3937b73bfdefd25a6166863ea188d5f8cb9
--- lams_tool_lamc/web/authoring/BasicContent.jsp (.../BasicContent.jsp) (revision 87ff0b33fa2d006a084b2ae9b7ff14d4c4be0f6d)
+++ lams_tool_lamc/web/authoring/BasicContent.jsp (.../BasicContent.jsp) (revision f26fb3937b73bfdefd25a6166863ea188d5f8cb9)
@@ -44,29 +44,23 @@
};
function importQTI(){
- window.open('questionFile.jsp?limitType=mc',
+ window.open('questions/questionFile.jsp?limitType=mc',
'QuestionFile','width=500,height=200,scrollbars=yes');
}
function saveQTI(formHTML, formName) {
- var form = $($.parseHTML(formHTML));
- $.ajax({
- type: "POST",
- url: '',
- data: form.serializeArray(),
- success: function(response) {
- $(questionListTargetDiv).html(response);
- refreshThickbox();
- }
- });
- }
-
- function saveQTI(formHTML, formName) {
document.body.innerHTML += formHTML;
var form = document.getElementById(formName);
form.action = '';
form.submit();
}
+
+ function exportQTI(){
+ var frame = document.getElementById("downloadFileDummyIframe"),
+ title = encodeURIComponent(document.getElementsByName("title")[0].value);
+ frame.src = ''
+ + '&title=' + title;
+ }
@@ -111,6 +105,9 @@
+
+
+
@@ -126,4 +123,7 @@
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file