/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/sam/trunk/samigo-qti/src/java/org/sakaiproject/tool/assessment/qti/helper/item/ItemHelper12Impl.java $ * $Id: ItemHelper12Impl.java 123521 2013-05-02 17:44:22Z ktsao@stanford.edu $ *********************************************************************************** * * Copyright (c) 2003, 2004, 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.item; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.sakaiproject.tool.assessment.data.ifc.assessment.AnswerIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemTextIfc; import org.sakaiproject.tool.assessment.qti.asi.Item; import org.sakaiproject.tool.assessment.qti.constants.AuthoringConstantStrings; import org.sakaiproject.tool.assessment.qti.constants.QTIVersion; import org.sakaiproject.tool.assessment.qti.helper.AuthoringXml; import org.sakaiproject.tool.assessment.qti.util.XmlUtil; import org.sakaiproject.tool.assessment.services.GradingService; import org.sakaiproject.tool.assessment.data.ifc.assessment.AnswerFeedbackIfc; /** * <p>Version for QTI 1.2 item XML, significant differences between 1.2 and 2.0</p> * * @version $Id: ItemHelper12Impl.java 123521 2013-05-02 17:44:22Z ktsao@stanford.edu $ * * Many methods in Fill in Blank and Numerical Responses(FIN) are identical for now. * This might change if we want to add random variable, parameterized calculation.... * */ public class ItemHelper12Impl extends ItemHelperBase implements ItemHelperIfc { private static Log log = LogFactory.getLog(ItemHelper12Impl.class); private static final String MATCH_XPATH = "item/presentation/flow/response_grp/render_choice"; private static final String NBSP = " "; //private Document doc; protected String[] itemTypes = AuthoringConstantStrings.itemTypes; private AuthoringXml authoringXml; private List allIdents; private Double currentMaxScore = Double.valueOf(0); private Double currentMinScore = Double.valueOf(0); private double currentPerItemScore = 0; private double currentPerItemDiscount = 0; /** * */ public ItemHelper12Impl() { super(); authoringXml = new AuthoringXml(getQtiVersion()); allIdents = new ArrayList(); log.debug("ItemHelper12Impl"); } protected AuthoringXml getAuthoringXml() { return authoringXml; } /** * get the qti version * @return */ protected int getQtiVersion() { return QTIVersion.VERSION_1_2; } /** * Add maximum score to item XML. * @param score * @param itemXml */ public void addMaxScore(Double score, Item itemXml) { String xPath = "item/resprocessing/outcomes/decvar/@maxvalue"; if (score == null) { score = Double.valueOf(0); } currentMaxScore = score; updateItemXml(itemXml, xPath, "" + score.toString()); } /** * Add minimum score to item XML * @param score * @param itemXml */ public void addMinScore(Double discount, Item itemXml) { String xPath = "item/resprocessing/outcomes/decvar/@minvalue"; if (discount == null) { discount = Double.valueOf(0); } currentMinScore = discount; updateItemXml(itemXml, xPath, "" + discount.toString()); } /** * Flags an answer as correct. * @param correctAnswerLabel the answer that is correct * @param itemXml the encapsulation of the item xml */ public void addCorrectAnswer(String correctAnswerLabel, Item itemXml) { this.flagAnswerCorrect(correctAnswerLabel, itemXml, true); } /** * Flags an answer as INCORRECT. * Currently, only used for true false questions. * @param incorrectAnswerLabel the answer that is NOT correct * @param itemXml the encapsulation of the item xml */ public void addIncorrectAnswer(String incorrectAnswerLabel, Item itemXml) { this.flagAnswerCorrect(incorrectAnswerLabel, itemXml, false); } /** * Flags an answer as correct/incorrect. * @param correctAnswerLabel the answer that is correct * @param itemXml the encapsulation of the item xml * @param correct true, or false if not correct */ public void flagAnswerCorrect(String answerLabel, Item itemXml, boolean correct) { if (answerLabel == null) { answerLabel = ""; } String flag; if (correct) { flag = "Correct"; } else { flag = "InCorrect"; } String respProcBaseXPath = "item/resprocessing/respcondition"; String respProcCondXPath = "/conditionvar/varequal"; String respProcFeedbackXPath = "/displayfeedback/@linkrefid"; int respSize = 0; // now get each response and flag correct answer List resp = itemXml.selectNodes(respProcBaseXPath); if ( (resp != null) && (resp.size() > 0)) { respSize = resp.size(); } for (int i = 1; i <= respSize; i++) { String index = ("[" + i) + "]"; String answerVar = itemXml.selectSingleValue(respProcBaseXPath + index + respProcCondXPath, "element"); if (answerLabel.equals(answerVar)) //found right displayfeedback { String xPath = respProcBaseXPath + index + "/@title"; String xfPath = respProcBaseXPath + index + respProcFeedbackXPath; updateItemXml(itemXml, xPath, flag); updateItemXml(itemXml, xfPath, flag); break; //done } } } /** * Add/update a response label entry * @param itemXml * @param xpath * @param itemText * @param isInsert * @param responseNo * @param responseLabelIdent */ public void addResponseEntry( Item itemXml, String xpath, String value, boolean isInsert, String responseNo, String responseLabel) { if (isInsert) { String nextNode = "response_label[" + responseNo + "]"; itemXml.insertElement(nextNode, xpath, "response_label"); itemXml.add( xpath + "/response_label[" + responseNo + "]", "material/mattext"); } else { itemXml.add(xpath, "response_label/material/mattext"); } try { // put CDATA around answers log.debug("putting CDATA around : " + value); if (value == null) { value = ""; } value = XmlUtil.convertStrforCDATA(value); itemXml.update(xpath + "/response_label[" + responseNo + "]/material/mattext", value); } catch (Exception ex) { log.error("Cannot update value in addResponselEntry(): " + ex); } String newPath = xpath + "/response_label[" + responseNo + "]"; itemXml.addAttribute(newPath, "ident"); newPath = xpath + "/response_label[" + responseNo + "]/@ident"; updateItemXml(itemXml, newPath, responseLabel); } /** * Add Item Feedback for a given response number. * @param itemXml the item xml * @param responseNo the numnber */ private void addItemfeedback(Item itemXml, String value, boolean isInsert, String responseNo, String responseLabel) { String xpath = "item"; String nextNode = "itemfeedback[" + responseNo + "]"; if (isInsert) { itemXml.insertElement(nextNode, xpath, "itemfeedback"); itemXml.add( xpath + "/itemfeedback[" + responseNo + "]", "flow_mat/material/mattext"); /* itemXml.add( xpath, "itemfeedback/flow_mat/material/mattext"); */ } else { } try { if (value == null) { value = ""; } value = XmlUtil.convertStrforCDATA(value); itemXml.update(xpath + "/itemfeedback[" + responseNo + "]/flow_mat/material/mattext", value); } catch (Exception ex) { log.error("Cannot update value in addItemfeedback(): " + ex); } String newPath = xpath + "/itemfeedback[" + responseNo + "]"; itemXml.addAttribute(newPath, "ident"); newPath = xpath + "/itemfeedback[" + responseNo + "]/@ident"; String feedbackIdent = responseLabel; updateItemXml(itemXml, newPath, feedbackIdent); } /** * Get the metadata field entry XPath * @return the XPath */ public String getMetaXPath() { String xpath = "item/itemmetadata/qtimetadata"; return xpath; } /** * Get the metadata field entry XPath for a given label * @param fieldlabel * @return the XPath */ public String getMetaLabelXPath(String fieldlabel) { String xpath = "item/itemmetadata/qtimetadata/qtimetadatafield/fieldlabel[text()='" + fieldlabel + "']/following-sibling::fieldentry"; return xpath; } /** * Get the text for the item * @param itemXml * @return the text */ public String getText(Item itemXml) { String xpath = "item/presentation/flow/material/mattext"; String itemType = itemXml.getItemType(); if (itemType.equals(AuthoringConstantStrings.MATCHING)) { xpath = "item/presentation/flow//mattext"; } return makeItemNodeText(itemXml, xpath); } /** * Matching only, sets each source to be matched to. * It also sets the the matching target. * @param itemTextList lvalue of matches * @param itemXml */ private void setItemTextMatching(List itemTextList, Item itemXml) { String xpath = MATCH_XPATH; Map allTargets = new HashMap(); itemXml.add(xpath, "response_label"); String randomNumber = ("" + Math.random()).substring(2); Iterator iter = itemTextList.iterator(); double itSize = itemTextList.size(); // just in case we screw up if (itSize > 0) { currentPerItemScore = currentMaxScore.doubleValue() / itSize; currentPerItemDiscount = currentMinScore.doubleValue(); } int respCondCount = 0; //used to count the respconditions while (iter.hasNext()) { ItemTextIfc itemText = (ItemTextIfc) iter.next(); String text = itemText.getText(); Long sequence = itemText.getSequence(); String responseLabelIdent = "MS-" + randomNumber + "-" + sequence; List answerList = itemText.getAnswerArray(); Iterator aiter = answerList.iterator(); int noSources = answerList.size(); while (aiter.hasNext()) { respCondCount++; AnswerIfc answer = (AnswerIfc) aiter.next(); String answerText = answer.getText(); String label = answer.getLabel(); Long answerSequence = answer.getSequence(); Boolean correct = answer.getIsCorrect(); String responseFeedback = ""; if (correct.booleanValue()) { responseFeedback = answer.getAnswerFeedback(AnswerFeedbackIfc.CORRECT_FEEDBACK); } else { responseFeedback = answer.getAnswerFeedback(AnswerFeedbackIfc.INCORRECT_FEEDBACK); } if (responseFeedback == null) { responseFeedback = ""; } String responseNo = "" + (answerSequence.longValue() - noSources + 1); String respIdent = "MT-" + randomNumber + "-" + label; String respCondNo = "" + respCondCount; responseFeedback = XmlUtil.convertStrforCDATA(responseFeedback); // add source (addMatchingRespcondition()) if (Boolean.TRUE.equals(correct)) { log.debug("Matching: matched."); if (!allIdents.contains(respIdent)) { allIdents.add(respIdent); // put in global (ewww) ident list } allTargets.put(respIdent, answerText); addMatchingRespcondition(true, itemXml, respCondNo, respIdent, responseLabelIdent, responseFeedback); } else { log.debug("Matching: NOT matched."); addMatchingRespcondition(false, itemXml, respCondNo, respIdent, responseLabelIdent, responseFeedback); continue; // we skip adding the response label when false } } String responseNo = "" + sequence; addMatchingResponseLabelSource(itemXml, responseNo, responseLabelIdent, text, 1); } // add targets (addMatchingResponseLabelTarget()) for (int i = 0; i < allIdents.size(); i++) { String respIdent = (String) allIdents.get(i); String answerText = (String) allTargets.get(respIdent); String responseNo = "" + (i + 1); addMatchingResponseLabelTarget(itemXml, responseNo, respIdent, answerText); } } private void setItemTextMatrix(List itemTextList, Item itemXml) { String xpath = MATCH_XPATH; itemXml.add(xpath, "response_label"); String randomNumber = ("" + Math.random()).substring(2); Iterator iter = itemTextList.iterator(); double itSize = itemTextList.size(); while (iter.hasNext()) { ItemTextIfc itemText = (ItemTextIfc) iter.next(); String text = itemText.getText(); Long sequence = itemText.getSequence(); String responseIdent = "MT-" + randomNumber + "-" + sequence; String responseNo = "" + sequence; addMatchingResponseLabelTarget(itemXml, responseNo, responseIdent, text); } // add targets (addMatchingResponseLabelTarget()) if (itemTextList.size() > 0) { ItemTextIfc itemText = (ItemTextIfc)itemTextList.get(0); List answerList = itemText.getAnswerArray(); int numTexts = itemTextList.size(); int matchmax = itemTextList.size(); // this is a kludge. On input the only difference in the matrix format is that // match_max > 1. It's valid to have a matrix with only one row, but in that case // use match_max of 2 to force the input side to recognize this as a matrix. I // hope this approach doesn't confuse any other CMS that may try to read the XML file. if (matchmax < 2) matchmax = 2; for (int i = 0; i < answerList.size(); i++) { AnswerIfc answer = (AnswerIfc)answerList.get(i); String answerText = answer.getText(); String responseNo = "" + (answer.getSequence() + numTexts); String responseIdent = "MS-" + randomNumber + "-" + responseNo; addMatchingResponseLabelSource(itemXml, responseNo, responseIdent, answerText, matchmax); } } updateAllSourceMatchGroup(itemXml); } /** * setItemTextCalculatedQuestion() adds the variables and formulas associated with * the Calculated Question. Variables and Formulas are both stored in sam_itemtext_t and * sam_answer_t table. This function adds those variable and formula definitions to * the item/presentation/flow path * @param itemTextList list of all variables and formulas (stored as ItemTextIfc and AnswerIfc * objects) * @param itemXml XML document to be updated. New data will be appended under "item/presentation/flow" */ private void setItemTextCalculatedQuestion(List<ItemTextIfc> itemTextList, Item itemXml) { String xpath = "item/presentation/flow"; itemXml.add(xpath, "variables"); itemXml.add(xpath, "formulas"); GradingService gs = new GradingService(); String instructions = itemXml.getItemText(); List<String> formulaNames = gs.extractFormulas(instructions); List<String> variableNames = gs.extractVariables(instructions); for (ItemTextIfc itemText : itemTextList) { if (variableNames.contains(itemText.getText())) { this.addCalculatedQuestionVariable(itemText, itemXml, xpath + "/variables"); } else if (formulaNames.contains(itemText.getText())){ this.addCalculatedQuestionFormula(itemText, itemXml, xpath + "/formulas"); } else { log.error("Calculated Question export failed, '" + itemText.getText() + "'" + "was not identified as either a variable or formula, so there must be " + "an error with the Calculated Question definition, " + "question id: " + itemText.getItem().getItemIdString()); } } } /** * addCalculatedQuestionVariable() adds a new formula node with required subnodes * into xpath location defined by the calling function * @param itemText - ItemText object, persisted in sam_itemtext_t, which contains * the data needed for the node * @param itemXml - XML object being created, with will be the result of the export * @param xpath - where in the XML object the formula should be added * always edit the last node in the array. */ private void addCalculatedQuestionVariable(ItemTextIfc itemText, Item itemXml, String xpath) { itemXml.add(xpath, "variable"); String updatedXpath = xpath + "/variable[last()]"; try { List<AnswerIfc> answers = itemText.getAnswerArray(); // find the matching answer, since the answer list will have multiple answer objects // for each ItemTextIfc object for (AnswerIfc answer : answers) { if (answer.getIsCorrect()) { String text = answer.getText(); String min = text.substring(0, text.indexOf("|")); String max = text.substring(text.indexOf("|") + 1, text.indexOf(",")); String decimalPlaces = text.substring(text.indexOf(",") + 1); // add nodes itemXml.add(updatedXpath, "name"); itemXml.update(updatedXpath + "/name", itemText.getText()); itemXml.add(updatedXpath, "min"); itemXml.update(updatedXpath + "/min", min); itemXml.add(updatedXpath, "max"); itemXml.update(updatedXpath + "/max", max); itemXml.add(updatedXpath, "decimalPlaces"); itemXml.update(updatedXpath + "/decimalPlaces", decimalPlaces); break; } } } catch (Exception e) { log.error(e.getMessage(), e); } } /** * addCalculatedQuestionFormula() adds a new formula node with required subnodes * into xpath location defined by the calling function * @param itemText - ItemText object, persisted in sam_itemtext_t, which contains * the data needed for the node * @param itemXml - XML object being created, with will be the result of the export * @param xpath - where in the XML object the formula should be added * always edit the last node in the array. */ private void addCalculatedQuestionFormula(ItemTextIfc itemText, Item itemXml, String xpath) { itemXml.add(xpath, "formula"); String updatedXpath = xpath + "/formula[last()]"; try { List<AnswerIfc> answers = itemText.getAnswerArray(); // find the matching answer, since the answer list will have multiple answer objects // for each ItemTextIfc object for (AnswerIfc answer : answers) { if (answer.getIsCorrect()) { String text = answer.getText(); String formula = text.substring(0, text.indexOf("|")); String tolerance = text.substring(text.indexOf("|") + 1, text.indexOf(",")); String decimalPlaces = text.substring(text.indexOf(",") + 1); // add nodes itemXml.add(updatedXpath, "name"); itemXml.update(updatedXpath + "/name", itemText.getText()); itemXml.add(updatedXpath, "formula"); itemXml.update(updatedXpath + "/formula", formula); itemXml.add(updatedXpath, "tolerance"); itemXml.update(updatedXpath + "/tolerance", tolerance); itemXml.add(updatedXpath, "decimalPlaces"); itemXml.update(updatedXpath + "/decimalPlaces", decimalPlaces); break; } } } catch (Exception e) { log.error(e.getMessage(), e); } } ////////////////////////////////////////////////////////////////////////////// // FILL IN THE BLANK ////////////////////////////////////////////////////////////////////////////// /** * Set the item text. * This is only valid for FIB,a single item text separated by '{}'. * @param itemText text to be updated, the syntax is in the form: * 'roses are {} and violets are {}.' -> 'roses are ',' and violets are ','.' * @param itemXml */ private void setItemTextFIB(String fibAns, Item itemXml) { if ( (fibAns != null) && (fibAns.trim().length() > 0)) { List fibList = parseFillInBlank(fibAns); Map valueMap = null; //Set newSet = null; String mattext = null; String respStr = null; String xpath = "item/presentation/flow/flow"; //String position = null; //String[] responses = null; if ( (fibList != null) && (fibList.size() > 0)) { //List idsAndResponses = new ArrayList(); //1. add Mattext And Responses for (int i = 0; i < fibList.size(); i++) { valueMap = (Map) fibList.get(i); if ( (valueMap != null) && (valueMap.size() > 0)) { mattext = (String) valueMap.get("text"); //wrap mattext with cdata mattext = XmlUtil.convertStrforCDATA(mattext); if (mattext != null) { //add mattext itemXml.add(xpath, "material/mattext"); String newXpath = xpath + "/material[" + (Integer.toString(i + 1) + "]/mattext"); updateItemXml(itemXml, newXpath, mattext); } respStr = (String) valueMap.get("ans"); if (respStr != null) { //add response_str itemXml.add(xpath, "response_str/render_fib"); String newXpath = xpath + "/response_str[" + ( Integer.toString(i + 1) + "]"); itemXml.addAttribute(newXpath, "ident"); String ident = "FIB0" + i; updateItemXml( itemXml, newXpath + "/@ident", ident); itemXml.addAttribute(newXpath, "rcardinality"); updateItemXml( itemXml, newXpath + "/@rcardinality", "Ordered"); newXpath = newXpath + "/render_fib"; itemXml.addAttribute(newXpath, "fibtype"); updateItemXml( itemXml, newXpath + "/@fibtype", "String"); itemXml.addAttribute(newXpath, "prompt"); updateItemXml( itemXml, newXpath + "/@prompt", "Box"); itemXml.addAttribute(newXpath, "columns"); updateItemXml( itemXml, newXpath + "/@columns", ( Integer.toString(respStr.length() + 5))); itemXml.addAttribute(newXpath, "rows"); updateItemXml(itemXml, newXpath + "/@rows", "1"); // we throw this into our global (ugh) list of idents allIdents.add(ident); } } } } } } /** * we ensure that answer text between brackets is always nonempty, also that * starting text is nonempty, we use a non-breaking space for this purpose * @param fib * @return */ private static String padFibWithNonbreakSpacesText(String fib) { if (fib.startsWith("{")) { fib = NBSP + fib; } return fib.replaceAll("\\}\\{", "}" + NBSP + "{"); } /** * Special FIB processing. * @param itemXml * @param responseCondNo * @param respIdent * @param points * @param responses * @return */ private Item addFIBRespconditionNotMutuallyExclusive( Item itemXml, String responseCondNo, String respIdent, String points, String[] responses) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "Yes"); String or = ""; itemXml.add(respCond, "conditionvar/or"); or = respCond + "/conditionvar/or"; for (int i = 0; i < responses.length; i++) { itemXml.add(or, "varequal"); int iString = i + 1; String varequal = or + "/varequal[" + iString + "]"; itemXml.addAttribute(varequal, "case"); itemXml.addAttribute(varequal, "respident"); updateItemXml(itemXml, varequal + "/@case", "No"); updateItemXml( itemXml, varequal + "/@respident", respIdent); // need to wrap CDATA for responses[i] . String wrapcdata_response = XmlUtil.convertStrforCDATA(responses[i]); updateItemXml(itemXml, varequal, wrapcdata_response); } //Add setvar itemXml.add(respCond, "setvar"); itemXml.addAttribute(respCond + "/setvar", "action"); updateItemXml( itemXml, respCond + "/setvar/@action", "Add"); itemXml.addAttribute(respCond + "/setvar", "varname"); updateItemXml( itemXml, respCond + "/setvar/@varname", "SCORE"); updateItemXml(itemXml, respCond + "/setvar", points); // this should be minimum value return itemXml; } /** * Special FIB processing. * @param itemXml * @param responseCondNo * @param respIdents * @param points * @param response * @return */ /* private Item addFIBRespconditionMutuallyExclusive(Item itemXml, String responseCondNo, ArrayList respIdents, String points, String response) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "Yes"); String or = ""; itemXml.add(respCond, "conditionvar/or"); or = respCond + "/conditionvar/or"; for (int i = 0; i < respIdents.size(); i++) { int iString = i + 1; itemXml.add(or, "varequal"); String varequal = or + "/varequal[" + (i + 1) + "]"; itemXml.addAttribute(varequal, "case"); itemXml.addAttribute(varequal, "respident"); updateItemXml(itemXml, varequal + "/@case", "No"); updateItemXml( itemXml, varequal + "/@respident", (String) respIdents.get(i)); updateItemXml(itemXml, varequal, response); } //Add setvar itemXml.add(respCond, "setvar"); itemXml.addAttribute(respCond + "/setvar", "action"); updateItemXml( itemXml, respCond + "/setvar/@action", "Add"); itemXml.addAttribute(respCond + "/setvar", "varname"); updateItemXml( itemXml, respCond + "/setvar/@varname", "SCORE"); updateItemXml(itemXml, respCond + "/setvar", points); // this should be minimum value return itemXml; } */ /** * Special FIB processing. * @param itemXml * @param responseCondNo * @return */ /* private Item addFIBRespconditionCorrectFeedback( Item itemXml, String responseCondNo) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "Yes"); String and = ""; itemXml.add(respCond, "conditionvar/and"); and = respCond + "/conditionvar/and"; for (int i = 1; i < (new Integer(responseCondNo)).intValue(); i++) { List or = itemXml.selectNodes("item/resprocessing/respcondition[" + i + "]/conditionvar/or"); if (or != null) { itemXml.addElement(and, ( (Element) or.get(0))); } } //Add display feedback itemXml.add(respCond, "displayfeedback"); itemXml.addAttribute(respCond + "/displayfeedback", "feedbacktype"); updateItemXml( itemXml, respCond + "/displayfeedback/@feedbacktype", "Response"); itemXml.addAttribute(respCond + "/displayfeedback", "linkrefid"); updateItemXml( itemXml, respCond + "/displayfeedback/@linkrefid", "Correct"); return itemXml; } */ /** * Special FIB processing. * @param itemXml * @param responseCondNo * @return */ /* private Item addFIBRespconditionInCorrectFeedback( Item itemXml, String responseCondNo) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "No"); itemXml.add(respCond, "conditionvar/other"); // Add display feedback itemXml.add(respCond, "displayfeedback"); itemXml.addAttribute(respCond + "/displayfeedback", "feedbacktype"); updateItemXml( itemXml, respCond + "/displayfeedback/@feedbacktype", "Response"); itemXml.addAttribute(respCond + "/displayfeedback", "linkrefid"); updateItemXml( itemXml, respCond + "/displayfeedback/@linkrefid", "InCorrect"); return itemXml; } */ ////////////////////////////////////////////////////////////////////////////// // Numeric Response ////////////////////////////////////////////////////////////////////////////// /** * Set the item text. * This is only valid for FIN,a single item text separated by '{}'. * @param itemText text to be updated, the syntax is in the form: * 'roses are {} and violets are {}.' -> 'roses are ',' and violets are ','.' * @param itemXml */ private void setItemTextFIN(String finAns, Item itemXml) { if ( (finAns != null) && (finAns.trim().length() > 0)) { List finList = parseFillInNumeric(finAns); Map valueMap = null; Set newSet = null; String mattext = null; String respStr = null; String xpath = "item/presentation/flow/flow"; String position = null; String[] responses = null; if ( (finList != null) && (finList.size() > 0)) { //1. add Mattext And Responses for (int i = 0; i < finList.size(); i++) { valueMap = (Map) finList.get(i); if ( (valueMap != null) && (valueMap.size() > 0)) { mattext = (String) valueMap.get("text"); // wrap mattext with cdata mattext = XmlUtil.convertStrforCDATA(mattext); if (mattext != null) { //add mattext itemXml.add(xpath, "material/mattext"); String newXpath = xpath + "/material[" + ( Integer.toString(i + 1) + "]/mattext"); updateItemXml(itemXml, newXpath, mattext); } respStr = (String) valueMap.get("ans"); if (respStr != null) { //add response_str itemXml.add(xpath, "response_str/render_fin"); String newXpath = xpath + "/response_str[" + ( Integer.toString(i + 1) + "]"); itemXml.addAttribute(newXpath, "ident"); String ident = "FIN0" + i; updateItemXml( itemXml, newXpath + "/@ident", ident); itemXml.addAttribute(newXpath, "rcardinality"); updateItemXml( itemXml, newXpath + "/@rcardinality", "Ordered"); newXpath = newXpath + "/render_fin"; itemXml.addAttribute(newXpath, "fintype"); updateItemXml( itemXml, newXpath + "/@fintype", "String"); itemXml.addAttribute(newXpath, "prompt"); updateItemXml( itemXml, newXpath + "/@prompt", "Box"); itemXml.addAttribute(newXpath, "columns"); updateItemXml( itemXml, newXpath + "/@columns", ( Integer.toString(respStr.length() + 5))); itemXml.addAttribute(newXpath, "rows"); updateItemXml(itemXml, newXpath + "/@rows", "1"); // we throw this into our global (ugh) list of idents allIdents.add(ident); } } } } } } /** * we ensure that answer text between brackets is always nonempty, also that * starting text is nonempty, we use a non-breaking space for this purpose * @param fin * @return */ private static String padFinWithNonbreakSpacesText(String fin) { if (fin.startsWith("{")) { fin = NBSP + fin; } return fin.replaceAll("\\}\\{", "}" + NBSP + "{"); } /** * Special FIN processing. * @param itemXml * @param responseCondNo * @param respIdent * @param points * @param responses * @return */ private Item addFINRespconditionNotMutuallyExclusive( Item itemXml, String responseCondNo, String respIdent, String points, String[] responses) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "Yes"); String or = ""; itemXml.add(respCond, "conditionvar/or"); or = respCond + "/conditionvar/or"; for (int i = 0; i < responses.length; i++) { itemXml.add(or, "varequal"); int iString = i + 1; String varequal = or + "/varequal[" + iString + "]"; itemXml.addAttribute(varequal, "case"); itemXml.addAttribute(varequal, "respident"); updateItemXml(itemXml, varequal + "/@case", "No"); updateItemXml( itemXml, varequal + "/@respident", respIdent); // need to wrap CDATA for responses[i] . String wrapcdata_response = XmlUtil.convertStrforCDATA(responses[i]); updateItemXml(itemXml, varequal, wrapcdata_response); } //Add setvar itemXml.add(respCond, "setvar"); itemXml.addAttribute(respCond + "/setvar", "action"); updateItemXml( itemXml, respCond + "/setvar/@action", "Add"); itemXml.addAttribute(respCond + "/setvar", "varname"); updateItemXml( itemXml, respCond + "/setvar/@varname", "SCORE"); updateItemXml(itemXml, respCond + "/setvar", points); // this should be minimum value return itemXml; } /** * Special FIN processing. * @param itemXml * @param responseCondNo * @param respIdents * @param points * @param response * @return */ /* private Item addFINRespconditionMutuallyExclusive(Item itemXml, String responseCondNo, ArrayList respIdents, String points, String response) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "Yes"); String or = ""; itemXml.add(respCond, "conditionvar/or"); or = respCond + "/conditionvar/or"; for (int i = 0; i < respIdents.size(); i++) { int iString = i + 1; itemXml.add(or, "varequal"); String varequal = or + "/varequal[" + (i + 1) + "]"; itemXml.addAttribute(varequal, "case"); itemXml.addAttribute(varequal, "respident"); updateItemXml(itemXml, varequal + "/@case", "No"); updateItemXml( itemXml, varequal + "/@respident", (String) respIdents.get(i)); updateItemXml(itemXml, varequal, response); } //Add setvar itemXml.add(respCond, "setvar"); itemXml.addAttribute(respCond + "/setvar", "action"); updateItemXml( itemXml, respCond + "/setvar/@action", "Add"); itemXml.addAttribute(respCond + "/setvar", "varname"); updateItemXml( itemXml, respCond + "/setvar/@varname", "SCORE"); updateItemXml(itemXml, respCond + "/setvar", points); // this should be minimum value return itemXml; } */ /** * Special FIN processing. * @param itemXml * @param responseCondNo * @return */ /* private Item addFINRespconditionCorrectFeedback( Item itemXml, String responseCondNo) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "Yes"); String and = ""; itemXml.add(respCond, "conditionvar/and"); and = respCond + "/conditionvar/and"; for (int i = 1; i < (new Integer(responseCondNo)).intValue(); i++) { List or = itemXml.selectNodes("item/resprocessing/respcondition[" + i + "]/conditionvar/or"); if (or != null) { itemXml.addElement(and, ( (Element) or.get(0))); } } //Add display feedback itemXml.add(respCond, "displayfeedback"); itemXml.addAttribute(respCond + "/displayfeedback", "feedbacktype"); updateItemXml( itemXml, respCond + "/displayfeedback/@feedbacktype", "Response"); itemXml.addAttribute(respCond + "/displayfeedback", "linkrefid"); updateItemXml( itemXml, respCond + "/displayfeedback/@linkrefid", "Correct"); return itemXml; } */ /** * Special FIN processing. * @param itemXml * @param responseCondNo * @return */ /* private Item addFINRespconditionInCorrectFeedback( Item itemXml, String responseCondNo) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition"); String respCond = "item/resprocessing/respcondition[" + responseCondNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "No"); itemXml.add(respCond, "conditionvar/other"); // Add display feedback itemXml.add(respCond, "displayfeedback"); itemXml.addAttribute(respCond + "/displayfeedback", "feedbacktype"); updateItemXml( itemXml, respCond + "/displayfeedback/@feedbacktype", "Response"); itemXml.addAttribute(respCond + "/displayfeedback", "linkrefid"); updateItemXml( itemXml, respCond + "/displayfeedback/@linkrefid", "InCorrect"); return itemXml; } */ /** * * @param idsAndResponses * @return */ /* private ArrayList getSimilarCorrectAnswerIDs(List idsAndResponses) { String[] compareResponse = null; ArrayList finalArray = new ArrayList(); // this is list of maps which contains arrayList correct responses and ids ArrayList idList = new ArrayList(); //ArrayList responseList = new ArrayList(); Map intermediaryMap = new HashMap(); for (int l = 0; l < idsAndResponses.size(); l++) { Map idsAndResponsesMap = (Map) idsAndResponses.get(l); for (Iterator it = idsAndResponsesMap.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); String respIdent = (String)entry.getKey(); String[] responses = null ; if ( (respIdent != null) && (respIdent.length() > 0)) { responses = (String[])entry.getValue(); } boolean newElement = true; for (int i = 0; i < finalArray.size(); i++) { Map currentEntry = (Map) finalArray.get(i); for (Iterator it2 = currentEntry.entrySet().iterator(); it2.hasNext();) { Map.Entry entry2 = (Map.Entry) it2.next(); compareResponse = (String[]) entry2.getKey(); //Set entrySet = currentEntry.keySet(); //Iterator entrykeys = entrySet.iterator(); //while (entrykeys.hasNext()) //{ //compareResponse = (String[]) entrykeys.next(); if (Arrays.equals(responses, compareResponse)) { idList = (ArrayList) entry2.getValue(); idList.add(respIdent); newElement = false; } } } if ( (finalArray.size() == 0) || (newElement)) { idList = new ArrayList(); idList.add(respIdent); intermediaryMap = new HashMap(); intermediaryMap.put(responses, idList); finalArray.add(intermediaryMap); } } } return finalArray; } */ /** * Special FIB processing. * @param itemXml * @param idsAndResponses * @param allIdents * @param isMutuallyExclusive * @param points * @return */ /* private Item addFIBRespconditions(Item itemXml, List idsAndResponses, List allIdents, boolean isMutuallyExclusive, String points) { if (idsAndResponses.size() > 0) { ArrayList combinationResponses = getSimilarCorrectAnswerIDs( idsAndResponses); if (combinationResponses != null && combinationResponses.size() > 0) { int respConditionNo = 1; for (int i = 0; i < combinationResponses.size(); i++) { Map currentEntry = (Map) combinationResponses.get(i); for (Iterator it = currentEntry.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); String[] responses = (String[]) entry.getKey(); ArrayList idList = (ArrayList) entry.getValue(); if (idList != null && idList.size() > 0) { if (idList.size() == 1) { addFIBRespconditionNotMutuallyExclusive( itemXml, new Integer(respConditionNo).toString(), (String) idList.get(0), points, responses); respConditionNo = respConditionNo + 1; } else { for (int k = 0; k < responses.length; k++) { addFIBRespconditionMutuallyExclusive(itemXml, new Integer(respConditionNo).toString(), idList, points, responses[k]); respConditionNo = respConditionNo + 1; } } } } } // add respcondition for all correct answers addFIBRespconditionCorrectFeedback(itemXml, new Integer(respConditionNo). toString()); respConditionNo = respConditionNo + 1; //add respcondition for all incorrect answers addFIBRespconditionInCorrectFeedback(itemXml, new Integer(respConditionNo). toString()); } } return itemXml; } */ /** * Special FIN processing. * @param itemXml * @param idsAndResponses * @param allIdents * @param isMutuallyExclusive * @param points * @return */ /* private Item addFINRespconditions(Item itemXml, List idsAndResponses, List allIdents, boolean isMutuallyExclusive, String points) { if (idsAndResponses.size() > 0) { ArrayList combinationResponses = getSimilarCorrectAnswerIDs(idsAndResponses); if (combinationResponses != null && combinationResponses.size() > 0) { int respConditionNo = 1; for (int i = 0; i < combinationResponses.size(); i++) { Map currentEntry = (Map) combinationResponses.get(i); for (Iterator it = currentEntry.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); String[] responses = (String[])entry.getKey(); ArrayList idList = (ArrayList) entry.getValue(); if (idList != null && idList.size() > 0) { if (idList.size() == 1) { addFINRespconditionNotMutuallyExclusive( itemXml, new Integer(respConditionNo) .toString(), (String) idList .get(0), points, responses); respConditionNo = respConditionNo + 1; } else { for (int k = 0; k < responses.length; k++) { addFINRespconditionMutuallyExclusive( itemXml, new Integer(respConditionNo) .toString(), idList, points, responses[k]); respConditionNo = respConditionNo + 1; } } } } } // add respcondition for all correct answers addFINRespconditionCorrectFeedback(itemXml, new Integer( respConditionNo).toString()); respConditionNo = respConditionNo + 1; // add respcondition for all incorrect answers addFINRespconditionInCorrectFeedback(itemXml, new Integer( respConditionNo).toString()); } } return itemXml; } */ /** * Get list of form: * {ans=red, text=Roses are}, * {ans=blue, text=and violets are}, * {ans=null, text=.} * From String of form "Roses are {red} and violets are {blue}." * * @param input * @return list of Maps */ private static List parseFillInBlank(String input) { input = padFibWithNonbreakSpacesText(input); Map tempMap = null; List storeParts = new ArrayList(); if (input == null) { return storeParts; } StringTokenizer st = new StringTokenizer(input, "}"); String tempToken = ""; String[] splitArray = null; while (st.hasMoreTokens()) { tempToken = st.nextToken(); tempMap = new HashMap(); //split out text and answer parts from token splitArray = tempToken.trim().split("\\{", 2); tempMap.put("text", splitArray[0].trim()); if (splitArray.length > 1) { tempMap.put("ans", (splitArray[1])); } else { tempMap.put("ans", null); } storeParts.add(tempMap); } return storeParts; } private static List parseFillInNumeric(String input) { input = padFinWithNonbreakSpacesText(input); Map tempMap = null; List storeParts = new ArrayList(); if (input == null) { return storeParts; } StringTokenizer st = new StringTokenizer(input, "}"); String tempToken = ""; String[] splitArray = null; while (st.hasMoreTokens()) { tempToken = st.nextToken(); tempMap = new HashMap(); //split out text and answer parts from token splitArray = tempToken.trim().split("\\{", 2); tempMap.put("text", splitArray[0].trim()); if (splitArray.length > 1) { tempMap.put("ans", (splitArray[1])); } else { tempMap.put("ans", null); } storeParts.add(tempMap); } return storeParts; } /* private static String[] getPossibleCorrectResponses(String inputStr) { String patternStr = ","; String[] responses = inputStr.split(patternStr); for (int i = 0; i < responses.length; i++) { responses[i] = responses[i].trim(); } Arrays.sort(responses); return responses; } */ /** * Set the item text. * This is valid for all undelimited single item texts. * Not valid for matching or fill in the blank, but OK for instructional text * @param itemText text to be updated * @param itemXml */ public void setItemText(String itemText, Item itemXml) { String xpath = "item/presentation/flow/material/mattext"; List list = itemXml.selectNodes(xpath); log.debug("in ItemHelper12Impl.java: setItemText() text = " + itemText); itemText = XmlUtil.convertStrforCDATA(itemText); log.debug("in ItemHelperBase.java: setItemText() wrapped CDATA text is = " + itemText); try { itemXml.update(xpath, itemText); } catch (Exception ex) { log.error(ex.getMessage(), ex); } } /** * Set the (one or more) item texts. * Valid for single and multiple texts. * @param itemXml * @param itemText text to be updated */ public void setItemTexts(ArrayList itemTextList, Item itemXml) { if (itemTextList.size() < 1) { return; } if (itemXml.isMatching()) { setItemTextMatching(itemTextList, itemXml); return; } else if (itemXml.isMXSURVEY()) { setItemTextMatrix(itemTextList, itemXml); return; } else if (itemXml.isCalculatedQuestion()) { setItemTextCalculatedQuestion(itemTextList, itemXml); return; } String text = ( (ItemTextIfc) itemTextList.get(0)).getText(); if (itemXml.isFIB()) { setItemTextFIB(text, itemXml); return; } else if (itemXml.isFIN()) { setItemTextFIN(text, itemXml); return; } else { setItemText(text, itemXml); return; } } /** * get item type string * @param itemXml * @return type as string */ public String getItemType(Item itemXml) { String type = itemXml.getFieldentry("qmd_itemtype"); return type; } /** * Set the answer texts for item. * @param itemTextList the text(s) for item */ public void setAnswers(ArrayList itemTextList, Item itemXml) { log.debug("entered setAnswers()"); log.debug("size=" + itemTextList.size()); // other types either have no answer or include them in their template, or, // in matching, generate all in setItemTextMatching() if (!itemXml.isFIB() && !itemXml.isMCSC() && !itemXml.isFIN() && !itemXml.isMCMC() && !itemXml.isMCMCSS() && !itemXml.isEssay() && !itemXml.isSurvey()) { return; } // OK, so now we are in business. String xpath = "item/presentation/flow/response_lid/render_choice"; List list = itemXml.selectNodes(xpath); Iterator nodeIter = list.iterator(); Iterator iter = itemTextList.iterator(); Set answerSet = new HashSet(); char label = 'A'; int xpathIndex = 1; int respIdentCount = 0; while (iter.hasNext()) { answerSet = ( (ItemTextIfc) iter.next()).getAnswerSet(); log.debug("answersize=" + answerSet.size()); //System.out.println("answersize=" + answerSet.size()); Iterator aiter = answerSet.iterator(); while (aiter.hasNext()) { AnswerIfc answer = (AnswerIfc) aiter.next(); if (Boolean.TRUE.equals(answer.getIsCorrect())) { this.addCorrectAnswer("" + label, itemXml); } String value = answer.getText(); log.debug("\n\n***The answer is: " + value); //System.out.println("\n\n***The answer is: " + value); // if and only if FIB we do special processing if (itemXml.isFIB()) { String[] responses = { value}; // one possible for now String respIdent = (String) allIdents.get(respIdentCount++); addFIBRespconditionNotMutuallyExclusive( itemXml, "" + xpathIndex, respIdent, "0", responses); label++; xpathIndex++; continue; // } if (itemXml.isFIN()) { String[] responses = { value}; // one possible for now String respIdent = (String) allIdents.get(respIdentCount++); addFINRespconditionNotMutuallyExclusive( itemXml, "" + xpathIndex, respIdent, "0", responses); label++; xpathIndex++; continue; // } // process into XML // we assume that we have equal to or more than the requisite elements // if we have more than the existing elements we manufacture more // with labels 'A', 'B'....etc. Node node = null; try { boolean isInsert = true; if (nodeIter.hasNext()) { isInsert = false; } this.addResponseEntry( itemXml, xpath, value, isInsert, "" + xpathIndex, "" + label); } catch (Exception ex) { log.error("Cannot process source document.", ex); } label++; xpathIndex++; } } } /** * Set the feedback texts for item. * @param itemTextList the text(s) for item * @param itemXml */ public void setFeedback(ArrayList itemTextList, Item itemXml) { //log.info("setFeedback()"); boolean hasAnswerLevelFeedback = itemXml.isMCMC() || itemXml.isMCSC()|| itemXml.isMCMCSS(); //log.info("itemXml.getItemType(): " + itemXml.getItemType()); //log.info("hasAnswerLevelFeedback: " + hasAnswerLevelFeedback); // for any answers that are now in the template, create a feedback String xpath = "item/itemfeedback/flow/response_lid/render_choice"; int xpathIndex = 1; List list = itemXml.selectNodes(xpath); Iterator nodeIter = list.iterator(); Iterator iter = itemTextList.iterator(); Set answerSet = new HashSet(); char label = 'A'; boolean first = true; while (iter.hasNext()) { ItemTextIfc itemTextIfc = (ItemTextIfc) iter.next(); if (first) // then do once { addCorrectAndIncorrectFeedback(itemXml, itemTextIfc); xpathIndex = 1; first = false; } if (hasAnswerLevelFeedback) { log.debug("Setting answer level feedback"); answerSet = itemTextIfc.getAnswerSet(); log.debug("answerSet.size(): " + answerSet.size()); Iterator aiter = answerSet.iterator(); while (aiter.hasNext()) { AnswerIfc answer = (AnswerIfc) aiter.next(); //log.info("Setting answer feedback for: " + answer.getText()); //log.info("xpathIndex: " + xpathIndex); //log.info("label: " + label); String value = answer.getGeneralAnswerFeedback(); boolean isInsert = true; if (nodeIter.hasNext()) { isInsert = false; } if(itemXml.isMCSC()){ //MC Single Correct if(answer.getIsCorrect().booleanValue()){ answer.setPartialCredit(100d); } if (answer.getItem().getPartialCreditFlag()) { Double partialCredit = 100d; try { partialCredit = Double.valueOf(((answer.getItem().getScore().doubleValue())*answer.getPartialCredit().doubleValue())/100d); } catch (Exception e) { log.error("Could not compute partial value for id: " + answer.getId()); } addAnswerFeedbackPartialCredit(itemXml, value, isInsert, xpathIndex, "" + label, partialCredit); //--mustansar } else { addAnswerFeedback(itemXml, value, isInsert, xpathIndex, "" + label ); } } else { // for MC Mulitiple Correct addAnswerFeedback(itemXml, value, isInsert, xpathIndex, "" + label ); } label++; xpathIndex++; } } addGeneralFeedback(itemXml, xpathIndex, itemTextIfc); } } /** * Adds feedback with idents of Correct and InCorrect * @param itemXml * @param itemTextIfc */ private void addCorrectAndIncorrectFeedback(Item itemXml, ItemTextIfc itemTextIfc) { String correctFeedback = itemTextIfc.getItem().getCorrectItemFeedback(); String incorrectFeedback = itemTextIfc.getItem(). getInCorrectItemFeedback(); log.debug("CORRECT FEEDBACK: " + correctFeedback); if (correctFeedback != null) { this.addItemfeedback( itemXml, correctFeedback, false, "1", "" + "Correct"); } log.debug("INCORRECT FEEDBACK: " + incorrectFeedback); if (incorrectFeedback != null) { this.addItemfeedback( itemXml, incorrectFeedback, false, "2", "" + "InCorrect"); } } /** * Adds feedback with ident referencing item ident * @param itemXml * @param xpathIndex * @param itemTextIfc */ private void addGeneralFeedback(Item itemXml, int xpathIndex, ItemTextIfc itemTextIfc) { log.debug("\nDebug add in General Feedback"); String generalFeedback = itemTextIfc.getItem().getGeneralItemFeedback(); String itemId = itemTextIfc.getItem().getItemIdString(); if (generalFeedback != null) { addItemfeedback( itemXml, generalFeedback, true, "" + xpathIndex++, itemId); } } /** * Adds feedback with ident referencing answer ident. * * @param itemXml * @param value * @param isInsert * @param responseNo * @param responseLabel */ private void addAnswerFeedback(Item itemXml, String value, boolean isInsert, int responseNo, String responseLabel) { log.debug("addAnswerFeedback()"); log.debug("answer feedback value: " + value); if (value == null) { value = "<![CDATA[]]>"; } else { value = XmlUtil.convertStrforCDATA(value); } String respCond = "item/resprocessing/respcondition[" + responseNo + "]"; updateItemXml(itemXml, respCond + "/setvar", "" + currentPerItemScore); updateItemXml(itemXml, respCond + "/displayfeedback[2]/@linkrefid", "AnswerFeedback"); updateItemXml(itemXml, respCond + "/displayfeedback[2]", value); } //////////////////////////////////////////////////////////////// // MATCHING //////////////////////////////////////////////////////////////// /** * Add the matching response label entry source. * @param itemXml * @param responseNo * @param responseLabelIdent * @param value */ private void addMatchingResponseLabelTarget( Item itemXml, String responseNo, String respIdent, String value) { String xpath = MATCH_XPATH; insertResponseLabelMattext(itemXml, responseNo, value, xpath); String newPath = xpath + "/response_label[" + responseNo + "]"; itemXml.addAttribute(newPath, "ident"); newPath = xpath + "/response_label[" + responseNo + "]/@ident"; updateItemXml(itemXml, newPath, respIdent); } /** * Add the matching response label entry source. * @param itemXml * @param responseNo * @param responseLabelIdent * @param value */ private void addMatchingResponseLabelSource( Item itemXml, String responseNo, String responseLabelIdent, String value, int matchMax) { String xpath = MATCH_XPATH; insertResponseLabelMattext(itemXml, responseNo, value, xpath); itemXml.addAttribute( xpath + "/response_label[" + responseNo + "]", "match_max"); itemXml.addAttribute( xpath + "/response_label[" + responseNo + "]", "match_group"); updateItemXml( itemXml, xpath + "/response_label[" + responseNo + "]" + "/@match_max", Integer.toString(matchMax)); String newPath = xpath + "/response_label[" + responseNo + "]"; itemXml.addAttribute(newPath, "ident"); newPath = xpath + "/response_label[" + responseNo + "]/@ident"; updateItemXml(itemXml, newPath, responseLabelIdent); } /** * utility method for addMatchingResponseLabelTarget(), addMatchingResponseLabelSource() * @param itemXml * @param responseNo * @param value * @param xpath */ private void insertResponseLabelMattext(Item itemXml, String responseNo, String value, String xpath) { String nextNode = "response_label[" + responseNo + "]"; itemXml.insertElement(nextNode, xpath, "response_label"); itemXml.add( xpath + "/response_label[" + responseNo + "]", "material/mattext"); try { log.debug("in ItemHelper12Impl.java: insertResponseLabelMattext() text = " + value); value = XmlUtil.convertStrforCDATA(value); log.debug("in ItemHelperBase.java: insertResponseLabelMattext() wrapped CDATA text is = " + value); itemXml.update( xpath + "/response_label[" + responseNo + "]/material/mattext", value); } catch (Exception ex) { log.warn("Unable to set mattext in '" + xpath + "/response_label[" + responseNo + "]' to '" + value + "'"); } } /** * Add the matching item feedback. * * @param itemXml * @param feedbackIdent * @param responseNo */ /* private void addMatchingItemfeedback( Item itemXml, String feedbackIdent, String responseNo) { String xpath = "item"; String nextNode = "itemfeedback[" + responseNo + "]"; itemXml.insertElement(nextNode, xpath, "itemfeedback"); itemXml.add( xpath + "/itemfeedback[" + responseNo + "]", "flow_mat/material/mattext"); String newPath = xpath + "/itemfeedback[" + responseNo + "]"; itemXml.addAttribute(newPath, "ident"); newPath = xpath + "/itemfeedback[" + responseNo + "]/@ident"; updateItemXml(itemXml, newPath, feedbackIdent); //Add placeholder for image xpath = xpath + "/itemfeedback[" + responseNo + "]/flow_mat"; itemXml.add(xpath, "material/matimage"); xpath = xpath + "/flow_mat/material[2]/matimage"; //Image attributes itemXml.addAttribute(xpath, "uri"); itemXml.addAttribute(xpath, "imagetype"); xpath = xpath + "/@imagetype"; updateItemXml(itemXml, xpath, "text/html"); } */ /** * Add matching response condition. * @param itemXml * @param responseNo * @param respident * @param responseLabelIdent */ private void addMatchingRespcondition(boolean correct, Item itemXml, String responseNo, String respident, String responseLabelIdent, String responseFeedback) { String xpath = "item/resprocessing"; itemXml.add(xpath, "respcondition/conditionvar/varequal"); String respCond = "item/resprocessing/respcondition[" + responseNo + "]"; itemXml.addAttribute(respCond, "continue"); updateItemXml(itemXml, respCond + "/@continue", "No"); itemXml.addAttribute(respCond + "/conditionvar/varequal", "case"); updateItemXml(itemXml, respCond + "/conditionvar/varequal/@case", "Yes"); itemXml.addAttribute(respCond + "/conditionvar/varequal", "respident"); itemXml.addAttribute(respCond + "/conditionvar/varequal", "index"); updateItemXml( itemXml, respCond + "/conditionvar/varequal/@index", responseNo); if (respident != null) { updateItemXml( itemXml, respCond + "/conditionvar/varequal/@respident", respident); } updateItemXml( itemXml, respCond + "/conditionvar/varequal", responseLabelIdent); //Add setvar itemXml.add(respCond, "setvar"); itemXml.addAttribute(respCond + "/setvar", "action"); updateItemXml(itemXml, respCond + "/setvar/@action", "Add"); itemXml.addAttribute(respCond + "/setvar", "varname"); updateItemXml(itemXml, respCond + "/setvar/@varname", "SCORE"); //Add display feedback itemXml.add(respCond, "displayfeedback"); itemXml.addAttribute(respCond + "/displayfeedback", "feedbacktype"); updateItemXml( itemXml, respCond + "/displayfeedback/@feedbacktype", "Response"); itemXml.addAttribute(respCond + "/displayfeedback", "linkrefid"); if (correct) { updateItemXml(itemXml, respCond + "/setvar", "" + currentPerItemScore); updateItemXml(itemXml, respCond + "/displayfeedback/@linkrefid", "CorrectMatch"); } else { updateItemXml(itemXml, respCond + "/setvar", "0"); updateItemXml(itemXml, respCond + "/displayfeedback/@linkrefid", "InCorrectMatch"); } updateItemXml(itemXml, respCond + "/displayfeedback", responseFeedback); } /** * Update match group. * Uses global internal list of all target idents. * (... response_label[not(@match_group)]/@ident) * DO NOT CALL before we have all the target idents ready * @param itemXml */ private void updateAllSourceMatchGroup(Item itemXml) { String matchGroupsXpath = "item/presentation/flow/response_grp/render_choice/response_label[(@match_group)]"; if (allIdents.size() > 0) { Iterator iter = allIdents.iterator(); String targetIdent = null; String match_group = null; while (iter.hasNext()) { targetIdent = (String) iter.next(); if (match_group == null) { match_group = targetIdent; } else { match_group = match_group + "," + targetIdent; } } if (match_group != null) { int noOfSources = (itemXml.selectNodes(matchGroupsXpath)).size(); for (int i = 1; i <= noOfSources; i++) { String xpath = "item/presentation/flow/response_grp/render_choice/response_label[" + i + "]/@match_group"; updateItemXml(itemXml, xpath, match_group); } } } } /** * Adds feedback with ident referencing answer ident. * * @param itemXml * @param value * @param isInsert * @param responseNo * @param responseLabel */ private void addAnswerFeedbackPartialCredit(Item itemXml, String value, boolean isInsert, int responseNo, String responseLabel, Double partialCredit) { log.debug("addAnswerFeedback()"); log.debug("answer feedback value: " + value); if (value == null) { value = "<![CDATA[]]>"; } else { value = XmlUtil.convertStrforCDATA(value); } String respCond = "item/resprocessing/respcondition[" + responseNo + "]"; // updateItemXml(itemXml, respCond + "/setvar", "" + currentPerItemScore); updateItemXml(itemXml, respCond + "/setvar", "" + partialCredit); updateItemXml(itemXml, respCond + "/displayfeedback[2]/@linkrefid", "AnswerFeedback"); updateItemXml(itemXml, respCond + "/displayfeedback[2]", value); } }