/********************************************************************************** * $URL: $ * $Id: $ *********************************************************************************** * * Author: Charles Hedrick, hedrick@rutgers.edu * * Copyright (c) 2013 Rutgers, the State University of New Jersey * * 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.lessonbuildertool.ccexport; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.HashSet; import java.util.HashMap; import java.util.Collections; import java.util.SortedSet; import java.util.SortedMap; import java.util.TreeSet; import java.util.TreeMap; import java.util.Comparator; import java.util.Date; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.lang.StringEscapeUtils; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.lessonbuildertool.service.LessonSubmission; import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean.UrlItem; import org.sakaiproject.lessonbuildertool.service.LessonEntity; import org.sakaiproject.tool.assessment.data.dao.assessment.AssessmentAccessControl; import org.sakaiproject.tool.assessment.data.dao.assessment.AssessmentData; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedAssessmentData; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedMetaData; import org.sakaiproject.tool.assessment.data.dao.authz.AuthorizationData; import org.sakaiproject.tool.assessment.data.dao.grading.AssessmentGradingData; import org.sakaiproject.tool.assessment.data.ifc.assessment.AnswerIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.EvaluationModelIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemAttachmentIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemDataIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemTextIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.PublishedAssessmentIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.SectionDataIfc; import org.sakaiproject.tool.assessment.data.ifc.questionpool.QuestionPoolDataIfc; import org.sakaiproject.tool.assessment.data.ifc.shared.TypeIfc; import org.sakaiproject.tool.assessment.facade.AssessmentFacade; import org.sakaiproject.tool.assessment.facade.AssessmentFacadeQueries; import org.sakaiproject.tool.assessment.facade.QuestionPoolFacadeQueriesAPI; import org.sakaiproject.tool.assessment.facade.AuthzQueriesFacadeAPI; import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade; import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacadeQueriesAPI; import org.sakaiproject.tool.assessment.facade.QuestionPoolFacade; import org.sakaiproject.tool.assessment.shared.api.grading.GradingServiceAPI; import org.sakaiproject.tool.assessment.services.GradingService; import org.sakaiproject.tool.assessment.services.ItemService; import org.sakaiproject.tool.assessment.services.QuestionPoolService; import org.sakaiproject.tool.assessment.services.PersistenceService; import org.sakaiproject.tool.assessment.services.assessment.AssessmentService; import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService; import org.w3c.dom.Document; import org.sakaiproject.user.cover.UserDirectoryService; import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.tool.api.Session; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.memory.api.Cache; import org.sakaiproject.memory.api.CacheRefresher; import org.sakaiproject.memory.api.MemoryService; import uk.org.ponder.messageutil.MessageLocator; import org.sakaiproject.lessonbuildertool.SimplePageItem; import org.sakaiproject.lessonbuildertool.model.SimplePageToolDao; import org.sakaiproject.db.cover.SqlService; import org.sakaiproject.db.api.SqlReader; import java.sql.Connection; import java.sql.ResultSet; import org.sakaiproject.lessonbuildertool.ccexport.ZipPrintStream; /* * set up as a singleton. But CCexport is not. */ public class SamigoExport { private static Log log = LogFactory.getLog(SamigoExport.class); PublishedAssessmentService pubService = new PublishedAssessmentService(); AssessmentService assessmentService = new AssessmentService(); private static SimplePageToolDao simplePageToolDao; public void setSimplePageToolDao(Object dao) { simplePageToolDao = (SimplePageToolDao) dao; } static PublishedAssessmentFacadeQueriesAPI publishedAssessmentFacadeQueries; public void setPublishedAssessmentFacadeQueries( PublishedAssessmentFacadeQueriesAPI p) { this.publishedAssessmentFacadeQueries = p; } static QuestionPoolFacadeQueriesAPI questionPoolFacadeQueries; public void setQuestionPoolFacadeQueries( QuestionPoolFacadeQueriesAPI p) { this.questionPoolFacadeQueries = p; } static MessageLocator messageLocator = null; public void setMessageLocator(MessageLocator m) { messageLocator = m; } public void init () { // currently nothing to do log.info("init()"); } public void destroy() { log.info("destroy()"); } // to create bean. the bean is used only to call the pseudo-static // methods such as getEntitiesInSite. So type, id, etc are left uninitialized protected SamigoExport() { } // find topics in site, but organized by forum public List<String> getEntitiesInSite(String siteId) { ArrayList<PublishedAssessmentFacade> plist = pubService.getBasicInfoOfAllPublishedAssessments2("title", true, siteId); List<String> ret = new ArrayList<String>(); // security. assume this is only used in places where it's OK, so skip security checks for (PublishedAssessmentFacade assessment: plist) { if (assessment.getStatus().equals(AssessmentIfc.ACTIVE_STATUS)) { ret.add(LessonEntity.SAM_PUB + "/" + assessment.getPublishedAssessmentId().toString()); } } return ret; } // are there any items in a pool? public boolean havePoolItems() { List<QuestionPoolDataIfc>pools = questionPoolFacadeQueries.getBasicInfoOfAllPools(UserDirectoryService.getCurrentUser().getId()); if (pools != null && pools.size() > 0) { int poolno = 1; for (QuestionPoolDataIfc pool: pools) { List<ItemDataIfc> itemList = questionPoolFacadeQueries.getAllItems(pool.getQuestionPoolId()); if (itemList != null && itemList.size() > 0) return true; } } return false; } public boolean outputEntity(String samigoId, ZipPrintStream out, PrintStream errStream, CCExport ccExport, CCExport.Resource resource, int version) { int i = samigoId.indexOf("/"); String publishedAssessmentString = samigoId.substring(i+1); Long publishedAssessmentId = new Long(publishedAssessmentString); PublishedAssessmentFacade assessment = pubService.getPublishedAssessment(publishedAssessmentString); List<ItemDataIfc> publishedItemList = preparePublishedItemList(assessment); // boolean anonymousGrading = assessment.getEvaluationModel().getAnonymousGrading().equals(EvaluationModelIfc.ANONYMOUS_GRADING); String assessmentTitle = assessment.getTitle(); // SortedMap<Long,String> questions = new TreeMap<Long,String>(); out.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"); switch (version) { case CCExport.V11: out.println("<questestinterop xmlns=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2\""); out.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/profile/cc/ccv1p1/ccv1p1_qtiasiv1p2p1_v1p0.xsd\">"); break; default: out.println("<questestinterop xmlns=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2\""); out.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/profile/cc/ccv1p2/ccv1p2_qtiasiv1p2p1_v1p0.xsd\">"); } out.println(" <assessment ident=\"QDB_1\" title=\"" + StringEscapeUtils.escapeXml(assessmentTitle) + "\">"); out.println(" <section ident=\"S_1\">"); outputQuestions(publishedItemList, null, assessmentTitle, out, errStream, ccExport, resource, version); out.println(" </section>"); out.println(" </assessment>"); out.println("</questestinterop>"); return true; } public boolean outputBank(ZipPrintStream out, PrintStream errStream, CCExport ccExport, CCExport.Resource resource, int version) { out.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"); switch (version) { case CCExport.V11: out.println("<questestinterop xmlns=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2\""); out.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/profile/cc/ccv1p1/ccv1p1_qtiasiv1p2p1_v1p0.xsd\">"); break; default: out.println("<questestinterop xmlns=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2\""); out.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/profile/cc/ccv1p2/ccv1p2_qtiasiv1p2p1_v1p0.xsd\">"); } out.println(" <objectbank ident=\"QDB_1\">"); List<QuestionPoolDataIfc>pools = questionPoolFacadeQueries.getBasicInfoOfAllPools(UserDirectoryService.getCurrentUser().getId()); if (pools != null && pools.size() > 0) { int poolno = 1; for (QuestionPoolDataIfc pool: pools) { List<ItemDataIfc> itemList = questionPoolFacadeQueries.getAllItems(pool.getQuestionPoolId()); if (itemList != null && itemList.size() > 0) outputQuestions(itemList, ("pool" + (poolno++)), pool.getTitle(), out, errStream, ccExport, resource, version); } } out.println(" </objectbank>"); out.println("</questestinterop>"); return true; } class Feedback { String id; String text; } void outputQuestions(List<ItemDataIfc> itemList, String assessmentSeq, String assessmentTitle, ZipPrintStream out, PrintStream errStream, CCExport ccExport, CCExport.Resource resource, int version) { int seq = 1; // feedback: // item: Map<String, String> where keys are org.sakaiproject.tool.assessment.data.ifc.assessment.ItemFeedbackIfc. // CORRECT_FEEDBACK = "Correct Feedback"; // INCORRECT_FEEDBACK = "InCorrect Feedback"; // GENERAL_FEEDBACK = "General Feedback"; // but may be easiest to use item.getItemFeedback(type) // or getCorrectItemFeedback, getInCorrectItemFeedback, getGeneralItemFeedback // for individual answers, // answer: Set, // org.sakaiproject.tool.assessment.data.ifc.assessment.AnswerFeedbackIfc. // CORRECT_FEEDBACK = "Correct Feedback"; // INCORRECT_FEEDBACK = "InCorrect Feedback"; // GENERAL_FEEDBACK = "General Feedback"; // ANSWER_FEEDBACK = "answerfeedback"; // matching has correct and/or incorrect for each answer // multiple choice has general feedback for each answer // answer_feedback doesn't seem to be used // probably easier to use answer.getAnswerFeedback(type) // or answer.getCorrectAnswerFeedback, getInCorrectAnswerFeedback, getGeneralAnswerFeedback, getTheAnswerFeedback for (ItemDataIfc item: itemList) { SectionDataIfc section = item.getSection(); String itemId = null; String title = null; List<Feedback> feedbacks = new ArrayList<Feedback>(); if (section != null) { itemId = item.getSection().getSequence() + "_" + item.getSequence(); title = item.getSection().getSequence() + "." + item.getSequence(); } else { itemId = assessmentSeq + "_" + seq; title = assessmentTitle + " " + (seq++); } Set<ItemTextIfc> texts = item.getItemTextSet(); List<ItemTextIfc> textlist = new ArrayList<ItemTextIfc>(); textlist.addAll(texts); // for FIB we have a textlist rather than just one text if (textlist.size() > 1) { Collections.sort(textlist, new Comparator() { public int compare (Object o1, Object o2) { Long v1 = ((ItemTextIfc)o1).getSequence(); Long v2 = ((ItemTextIfc)o2).getSequence(); return v1.compareTo(v2); } }); } Set<AnswerIfc> answers = textlist.get(0).getAnswerSet(); List<AnswerIfc> answerlist = new ArrayList<AnswerIfc>(); answerlist.addAll(answers); if (answerlist.size() > 1) { Collections.sort(answerlist, new Comparator() { public int compare (Object o1, Object o2) { Long v1 = ((AnswerIfc)o1).getSequence(); Long v2 = ((AnswerIfc)o2).getSequence(); return v1.compareTo(v2); } }); } String profile = "cc.multiple_choice.v0p1"; Long type = item.getTypeId(); boolean survey = false; if (type.equals(TypeIfc.MULTIPLE_CHOICE) || type.equals(TypeIfc.MULTIPLE_CHOICE_SURVEY) || type.equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION)) { if (type.equals(TypeIfc.MULTIPLE_CHOICE_SURVEY)) survey = true; type = TypeIfc.MULTIPLE_CHOICE; // normalize it profile = "cc.multiple_choice.v0p1"; } else if (type.equals(TypeIfc.MULTIPLE_CORRECT)) { profile = "cc.multiple_response.v0p1"; } else if (type.equals(TypeIfc.TRUE_FALSE)) { profile = "cc.true_false.v0p1"; } else if (type.equals(TypeIfc.ESSAY_QUESTION)) { profile = "cc.essay.v0p1"; } else if (type.equals(TypeIfc.FILL_IN_BLANK) || type.equals(TypeIfc.FILL_IN_NUMERIC) ) { String answerString = answerlist.get(0).getText(); // only limited pattern match is supported. It has to be just one alternative, and // it can only be a substring. I classify anything starting or ending in *, and with one // alternative as pattern match, otherwise FIB, and give error except for the one proper case if (answerString.indexOf("*") >= 0 && answerString.indexOf("|") < 0) profile = "cc.pattern_match.v0p1"; else profile = "cc.fib.v0p1"; type = TypeIfc.FILL_IN_BLANK; // normalize } else { errStream.println(messageLocator.getMessage("simplepage.exportcc-sam-undefinedtype").replace("{1}", title).replace("{2}",assessmentTitle)); } //ignore // MATCHING // FILE_UPLOAD: // AUDIO_RECORDING: // MATRIX_CHOICES_SURVEY: String cardinality = "Single"; if (type.equals(TypeIfc.MULTIPLE_CORRECT) || type.equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION)) cardinality = "Multiple"; Set<Long> correctSet = new HashSet<Long>(); String correctItem = ""; // CC doesn't have survey questoins. We treat them as multiple correct single selection if (answerlist.size() > 0) { for (AnswerIfc answer: answerlist) { if (survey || answer.getIsCorrect()) { if (type.equals(TypeIfc.TRUE_FALSE)) correctItem = answer.getText().toLowerCase(); else correctItem = "QUE_" + itemId + "_" + answer.getSequence(); correctSet.add(answer.getSequence()); } } } out.println(" <item ident=\"QUE_" + itemId + "\" title=\"" + StringEscapeUtils.escapeXml(title) + "\">"); out.println(" <itemmetadata>"); out.println(" <qtimetadata>"); out.println(" <qtimetadatafield>"); out.println(" <fieldlabel>cc_profile</fieldlabel>"); out.println(" <fieldentry>" + profile + "</fieldentry>"); out.println(" </qtimetadatafield>"); out.println(" </qtimetadata>"); out.println(" </itemmetadata>"); out.println(" <presentation>"); out.println(" <material>"); String text = ""; if (type.equals(TypeIfc.FILL_IN_BLANK) || type.equals(TypeIfc.FILL_IN_NUMERIC) ) { // gettext replaces {} with ____. The problem is that some of the CC samples tests have // an actual ____ in the text. Thus it's best to work with the original {}. for (ItemTextIfc it: textlist) { text += "" + it.getText(); } text = text.trim(); // If there's more than one {} we'll get a weird result, but there's not a lot we can do about that. int index = 0; int blanks = 0; while (true) { index = text.indexOf("{}", index+2); if (index >= 0) blanks ++; else break; } if (blanks > 1) { errStream.println(messageLocator.getMessage("simplepage.exportcc-sam-too-many-blanks").replace("{1}", title).replace("{2}",assessmentTitle).replace("{3}", ""+blanks)); } // now we have the whole string with {}. If the {} isn't at the end, replace // it with [____] so the student can see where the replacement actually is. The // CC subset won't allow the actual blank to be there. if (text.endsWith("{}")) text = text.substring(0, text.length()-2); text = text.replaceAll("\\{\\}", "[____]"); } else text = item.getText(); out.println(" <mattext texttype=\"text/html\">" + ccExport.fixup(text, resource) + "</mattext>"); out.println(" </material>"); if (type.equals(TypeIfc.MULTIPLE_CHOICE) ||type.equals(TypeIfc.MULTIPLE_CORRECT) || type.equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION) || type.equals(TypeIfc.TRUE_FALSE)) { // mc has general for each item, correct and incorrect, survey has general out.println(" <response_lid ident=\"QUE_" + itemId + "_RL\" rcardinality=\"" + cardinality + "\">"); out.println(" <render_choice>"); for (AnswerIfc answer: answerlist) { String answerId = "QUE_" + itemId + "_" + answer.getSequence(); if (type.equals(TypeIfc.TRUE_FALSE)) answerId = answer.getText().toLowerCase(); String atext = answer.getText(); if (atext == null || atext.trim().equals("")) continue; out.println(" <response_label ident=\"" + answerId + "\">"); out.println(" <material>"); out.println(" <mattext texttype=\"text/html\">" + ccExport.fixup(atext, resource) + "</mattext>"); out.println(" </material>"); out.println(" </response_label>"); } out.println(" </render_choice>"); out.println(" </response_lid>"); out.println(" </presentation>"); out.println(" <resprocessing>"); out.println(" <outcomes>"); out.println(" <decvar maxvalue=\"100\" minvalue=\"0\" varname=\"SCORE\" vartype=\"Decimal\"/>"); out.println(" </outcomes>"); if (item.getGeneralItemFeedback() != null) { out.println(" <respcondition continue=\"Yes\">"); out.println(" <conditionvar><other/></conditionvar>"); out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"general_fb\" />"); out.println(" </respcondition>"); Feedback feedback = new Feedback(); feedback.id = "general_fb"; feedback.text = item.getGeneralItemFeedback(); feedbacks.add(feedback); } for (AnswerIfc answer: answerlist) { if (answer.getGeneralAnswerFeedback() != null) { String atext = answer.getText(); if (atext == null || atext.trim().equals("")) continue; String answerId = "QUE_" + itemId + "_" + answer.getSequence(); if (type.equals(TypeIfc.TRUE_FALSE)) answerId = answer.getText().toLowerCase(); out.println(" <respcondition continue=\"Yes\">"); out.println(" <conditionvar>"); out.println(" <varequal respident=\"QUE_" + itemId + "_RL\">" + answerId + "</varequal>"); out.println(" </conditionvar>"); out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"" + answerId + "_fb\" />"); out.println(" </respcondition>"); Feedback feedback = new Feedback(); feedback.id = answerId + "_fb"; feedback.text = answer.getGeneralAnswerFeedback(); feedbacks.add(feedback); } } out.println(" <respcondition continue=\"No\">"); out.println(" <conditionvar>"); if (type.equals(TypeIfc.MULTIPLE_CHOICE) || type.equals(TypeIfc.TRUE_FALSE)) { int remaining = -1; // default to allow all correct answers if (correctSet.size() > 1) { if (version < CCExport.V12) { errStream.println(messageLocator.getMessage("simplepage.exportcc-sam-mcss").replace("{1}", title).replace("{2}",assessmentTitle)); remaining = 1; } else out.println(" <or>"); } for (AnswerIfc answer: answerlist) { String answerId = "QUE_" + itemId + "_" + answer.getSequence(); if (type.equals(TypeIfc.TRUE_FALSE)) answerId = answer.getText().toLowerCase(); if (correctSet.contains(answer.getSequence()) && remaining != 0) { out.println(" <varequal case=\"Yes\" respident=\"QUE_" + itemId + "_RL\">" + answerId + "</varequal>"); remaining--; } } if (correctSet.size() > 1 && remaining < 0) out.println(" </or>"); } else if (type.equals(TypeIfc.MULTIPLE_CORRECT) || type.equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION)) { if (type.equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION)) errStream.println(messageLocator.getMessage("simplepage.exportcc-sam-mcss").replace("{1}", title).replace("{2}",assessmentTitle)); out.println(" <and>"); for (AnswerIfc answer: answerlist) { String answerId = itemId + "_" + answer.getSequence(); String atext = answer.getText(); if (atext == null || atext.trim().equals("")) continue; if (correctSet.contains(answer.getSequence())) { out.println(" <varequal case=\"Yes\" respident=\"QUE_" + itemId + "_RL\">QUE_" + itemId + "_" + answer.getSequence() + "</varequal>"); } else { out.println(" <not>"); out.println(" <varequal case=\"Yes\" respident=\"QUE_" + itemId + "_RL\">QUE_" + itemId + "_" + answer.getSequence() + "</varequal>"); out.println(" </not>"); } } out.println(" </and>"); } out.println(" </conditionvar>"); out.println(" <setvar action=\"Set\" varname=\"SCORE\">100</setvar>"); if (item.getCorrectItemFeedback() != null) { out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"correct_fb\"/>"); Feedback feedback = new Feedback(); feedback.id = "correct_fb"; feedback.text = item.getCorrectItemFeedback(); feedbacks.add(feedback); } out.println(" </respcondition>"); if (item.getInCorrectItemFeedback() != null) { out.println(" <respcondition>"); out.println(" <conditionvar><other/></conditionvar>"); out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"general_incorrect_fb\" />"); out.println(" </respcondition>"); Feedback feedback = new Feedback(); feedback.id = "general_incorrect_fb"; feedback.text = item.getInCorrectItemFeedback(); feedbacks.add(feedback); } out.println(" </resprocessing>"); } if (type.equals(TypeIfc.FILL_IN_BLANK) || type.equals(TypeIfc.ESSAY_QUESTION)) { // FIB has correct or incorrect, essay has general out.println(" <response_str ident=\"QUE_" + itemId + "_RL\">"); out.println(" <render_fib columns=\"30\" rows=\"1\"/>"); out.println(" </response_str>"); out.println(" </presentation>"); if (type.equals(TypeIfc.FILL_IN_BLANK) && answerlist.size() > 0) { out.println(" <resprocessing>"); out.println(" <outcomes>"); out.println(" <decvar maxvalue=\"100\" minvalue=\"0\" varname=\"SCORE\" vartype=\"Decimal\"/>"); out.println(" </outcomes>"); if (item.getGeneralItemFeedback() != null) { out.println(" <respcondition continue=\"Yes\">"); out.println(" <conditionvar><other/></conditionvar>"); out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"general_fb\" />"); out.println(" </respcondition>"); Feedback feedback = new Feedback(); feedback.id = "general_fb"; feedback.text = item.getGeneralItemFeedback(); feedbacks.add(feedback); } out.println(" <respcondition continue=\"No\">"); out.println(" <conditionvar>"); String answerId = "QUE_" + itemId + "_RL"; String answerString = answerlist.get(0).getText(); String[] answerArray = answerString.split("\\|"); boolean toomanystars = false; if (answerString.indexOf("*") >= 0 && answerString.indexOf("|") >= 0) { errStream.println(messageLocator.getMessage("simplepage.exportcc-sam-fib-too-many-star").replace("{1}", title).replace("{2}",assessmentTitle).replace("{3}", answerString)); toomanystars = true; } for (String answer: answerArray) { boolean substr = false; boolean hasStar = answer.indexOf("*") >= 0; String orig = answer; // this isn't a perfect test. Not much we can do with * in the middle of a string // and just at the end or just at the beginning isn't a perfect match to this. // if more than one alternative, don't treat as matching, since that format isn't legal if (!toomanystars) { if (answer.startsWith("*")) { answer = answer.substring(1); substr = true; } if (answer.endsWith("*")) { answer = answer.substring(0, answer.length()-1); substr = true; } } if (hasStar) { if (substr) errStream.println(messageLocator.getMessage("simplepage.exportcc-sam-fib-star").replace("{1}", title).replace("{2}",assessmentTitle).replace("{3}", orig).replace("{4}", answer)); else errStream.println(messageLocator.getMessage("simplepage.exportcc-sam-fib-bad-star").replace("{1}", title).replace("{2}",assessmentTitle).replace("{3}", orig)); } if (substr) out.println(" <varsubstring case=\"No\" respident=\"" + answerId + "\">" + StringEscapeUtils.escapeXml(answer) + "</varsubstring>"); else out.println(" <varequal case=\"No\" respident=\"" + answerId + "\">" + StringEscapeUtils.escapeXml(answer) + "</varequal>"); } out.println(" </conditionvar>"); out.println(" <setvar action=\"Set\" varname=\"SCORE\">100</setvar>"); if (item.getCorrectItemFeedback() != null) { out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"correct_fb\"/>"); Feedback feedback = new Feedback(); feedback.id = "correct_fb"; feedback.text = item.getCorrectItemFeedback(); feedbacks.add(feedback); } out.println(" </respcondition>"); if (item.getInCorrectItemFeedback() != null) { out.println(" <respcondition>"); out.println(" <conditionvar><other/></conditionvar>"); out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"general_incorrect_fb\" />"); out.println(" </respcondition>"); Feedback feedback = new Feedback(); feedback.id = "general_incorrect_fb"; feedback.text = item.getInCorrectItemFeedback(); feedbacks.add(feedback); } out.println(" </resprocessing>"); } } if (type.equals(TypeIfc.ESSAY_QUESTION)) { // essay has no resprocessing except if there is general feedback if (item.getGeneralItemFeedback() != null) { out.println(" <resprocessing>"); out.println(" <outcomes>"); out.println(" <decvar maxvalue=\"100\" minvalue=\"0\" varname=\"SCORE\" vartype=\"Decimal\"/>"); out.println(" </outcomes>"); out.println(" <respcondition continue=\"No\">"); out.println(" <conditionvar><other/></conditionvar>"); out.println(" <displayfeedback feedbacktype=\"Response\" linkrefid=\"general_fb\" />"); out.println(" </respcondition>"); out.println(" </resprocessing>"); Feedback feedback = new Feedback(); feedback.id = "general_fb"; feedback.text = item.getGeneralItemFeedback(); feedbacks.add(feedback); } } if (feedbacks.size() > 0) { for (Feedback feedback: feedbacks) { out.println(" <itemfeedback ident=\"" + feedback.id + "\">"); out.println(" <material>"); out.println(" <mattext texttype=\"text/html\">" + ccExport.fixup(feedback.text, resource) + "</mattext>"); out.println(" </material>"); out.println(" </itemfeedback>"); } } out.println(" </item>"); } } public List<ItemDataIfc> preparePublishedItemList(PublishedAssessmentIfc publishedAssessment){ List<ItemDataIfc> ret = new ArrayList(); ArrayList sectionArray = publishedAssessment.getSectionArray(); Collections.sort(sectionArray, new Comparator() { public int compare(Object o1, Object o2) { return ((SectionDataIfc)o1).getSequence() - ((SectionDataIfc)o2).getSequence(); }}); for (int i=0;i<sectionArray.size(); i++){ SectionDataIfc section = (SectionDataIfc)sectionArray.get(i); ArrayList itemArray = section.getItemArray(); Collections.sort(itemArray, new Comparator() { public int compare(Object o1, Object o2) { return ((ItemDataIfc)o1).getSequence() - ((ItemDataIfc)o2).getSequence(); }}); for (int j=0;j<itemArray.size(); j++){ ItemDataIfc item = (ItemDataIfc)itemArray.get(j); ret.add(item); } } return ret; } }