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" %> + + + + <fmt:message key="title.lams" /> :: <fmt:message key="label.questions.choice.title" /> + + + + + + + +
+
+ +
+ +

+ +

+ +
+ + + + <%-- Question itself --%> + ${questionStatus.index + 1}. + + + () + + + () + + + () + + + () + + + () + + + () + + + () + + + + +
${question.text}

+ + <%-- Question feedback --%> + + <%-- If question contains images, where to take them from --%> + + <%-- Answers, if required and exist --%> + +
+ + + <%-- Answer itself --%> + + + ${answer.text}
+
+ + <%-- Do not display answers if management is too difficult or pointless --%> + + +
+ <%-- Answers score and feedback --%> + + +
+ + + + + + + + + +
+
+
+ +
+ + +
+
+
+ + +
\ 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"%> + + + + <fmt:message key="title.lams" /> :: <fmt:message key="label.questions.file.title" /> + + + + + + + + + +
+
+ +
+
+
+

+ +
+ + + + +
+ + +
+
+
+ + +
\ 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