001: package org.sakaiproject.tool.assessment.samlite.impl;
002:
003: import java.io.File;
004: import java.io.IOException;
005: import java.io.InputStream;
006: import java.util.HashMap;
007: import java.util.Iterator;
008: import java.util.Map;
009: import java.util.regex.Matcher;
010: import java.util.regex.Pattern;
011:
012: import javax.xml.parsers.DocumentBuilder;
013: import javax.xml.parsers.DocumentBuilderFactory;
014: import javax.xml.parsers.ParserConfigurationException;
015:
016: import org.apache.commons.logging.Log;
017: import org.apache.commons.logging.LogFactory;
018: import org.apache.xmlbeans.XmlOptions;
019: import org.imsglobal.xsd.imsQtiasiv1P2.AssessfeedbackType;
020: import org.imsglobal.xsd.imsQtiasiv1P2.AssessmentType;
021: import org.imsglobal.xsd.imsQtiasiv1P2.AssessmentcontrolType;
022: import org.imsglobal.xsd.imsQtiasiv1P2.ConditionvarType;
023: import org.imsglobal.xsd.imsQtiasiv1P2.DecvarType;
024: import org.imsglobal.xsd.imsQtiasiv1P2.DisplayfeedbackType;
025: import org.imsglobal.xsd.imsQtiasiv1P2.FlowMatType;
026: import org.imsglobal.xsd.imsQtiasiv1P2.FlowType;
027: import org.imsglobal.xsd.imsQtiasiv1P2.ItemType;
028: import org.imsglobal.xsd.imsQtiasiv1P2.ItemfeedbackType;
029: import org.imsglobal.xsd.imsQtiasiv1P2.ItemmetadataType;
030: import org.imsglobal.xsd.imsQtiasiv1P2.ItemrubricType;
031: import org.imsglobal.xsd.imsQtiasiv1P2.MaterialType;
032: import org.imsglobal.xsd.imsQtiasiv1P2.MatimageType;
033: import org.imsglobal.xsd.imsQtiasiv1P2.MattextType;
034: import org.imsglobal.xsd.imsQtiasiv1P2.OrType;
035: import org.imsglobal.xsd.imsQtiasiv1P2.OrderType;
036: import org.imsglobal.xsd.imsQtiasiv1P2.OutcomesType;
037: import org.imsglobal.xsd.imsQtiasiv1P2.PresentationMaterialType;
038: import org.imsglobal.xsd.imsQtiasiv1P2.PresentationType;
039: import org.imsglobal.xsd.imsQtiasiv1P2.QtimetadataType;
040: import org.imsglobal.xsd.imsQtiasiv1P2.QtimetadatafieldType;
041: import org.imsglobal.xsd.imsQtiasiv1P2.QuestestinteropDocument;
042: import org.imsglobal.xsd.imsQtiasiv1P2.RenderChoiceType;
043: import org.imsglobal.xsd.imsQtiasiv1P2.RenderFibType;
044: import org.imsglobal.xsd.imsQtiasiv1P2.RespconditionType;
045: import org.imsglobal.xsd.imsQtiasiv1P2.ResponseLabelType;
046: import org.imsglobal.xsd.imsQtiasiv1P2.ResponseLidType;
047: import org.imsglobal.xsd.imsQtiasiv1P2.ResponseStrType;
048: import org.imsglobal.xsd.imsQtiasiv1P2.ResprocessingType;
049: import org.imsglobal.xsd.imsQtiasiv1P2.RubricType;
050: import org.imsglobal.xsd.imsQtiasiv1P2.SectionType;
051: import org.imsglobal.xsd.imsQtiasiv1P2.SelectionOrderingType;
052: import org.imsglobal.xsd.imsQtiasiv1P2.SetvarType;
053: import org.imsglobal.xsd.imsQtiasiv1P2.VarequalType;
054: import org.sakaiproject.tool.assessment.samlite.api.Answer;
055: import org.sakaiproject.tool.assessment.samlite.api.Question;
056: import org.sakaiproject.tool.assessment.samlite.api.QuestionGroup;
057: import org.sakaiproject.tool.assessment.samlite.api.SamLiteService;
058: import org.w3c.dom.Document;
059:
060: public class SamLiteServiceImpl implements SamLiteService {
061: private static Log log = LogFactory
062: .getLog(SamLiteServiceImpl.class);
063: public static final String DEFAULT_CHARSET = "ascii-us";
064:
065: private Pattern justQuestionPattern, startOfQuestionPattern,
066: correctAnswerPattern;
067: private Pattern correctFillInPattern, answerPattern,
068: endQuestionPattern, correctMultipleChoicePattern;
069: private Pattern shortEssayPattern, correctTruePattern,
070: correctFalsePattern, unnecessaryTruePattern,
071: unnecessaryFalsePattern;
072:
073: private Pattern startOfQuestionNumericPattern;
074: private Pattern pointsPattern;
075:
076: public void init() {
077: // Initialization
078: startOfQuestionPattern = Pattern.compile(
079: "^(Question\\s*\\d*\\s*)", Pattern.CASE_INSENSITIVE);
080: startOfQuestionNumericPattern = Pattern.compile(
081: "^(\\d+\\.|\\)|\\]\\s*)", Pattern.CASE_INSENSITIVE);
082: pointsPattern = Pattern.compile(
083: "\\((\\d*\\.?\\d*)\\s+point(s?)\\)",
084: Pattern.CASE_INSENSITIVE);
085:
086: correctAnswerPattern = Pattern.compile("^\\*");
087: correctMultipleChoicePattern = Pattern.compile(
088: "^\\*\\s*([a-z])\\.\\s*(.*)", Pattern.CASE_INSENSITIVE);
089: correctFillInPattern = Pattern.compile("^\\*\\s*(.*)");
090: answerPattern = Pattern.compile("^([a-z])\\.\\s*(.*)",
091: Pattern.CASE_INSENSITIVE);
092: endQuestionPattern = Pattern.compile("^Save answer",
093: Pattern.CASE_INSENSITIVE);
094: shortEssayPattern = Pattern.compile("^\\[Essay\\]$",
095: Pattern.CASE_INSENSITIVE);
096: correctTruePattern = Pattern.compile("^\\*\\s*True$");
097: correctFalsePattern = Pattern.compile("^\\*\\s*False$");
098: unnecessaryTruePattern = Pattern.compile("^True$");
099: unnecessaryFalsePattern = Pattern.compile("^False$");
100: }
101:
102: public Question saveLast(QuestionGroup questionGroup,
103: Question question) {
104:
105: if (null != question) {
106: // Short essay questions don't have any answer, so it's the default type
107: if (Question.UNDEFINED_QUESTION == question
108: .getQuestionType())
109: question.setQuestionType(Question.SHORT_ESSAY_QUESTION);
110: // If it doesn't have points yet, it's not going to get any
111: if (!question.hasPoints())
112: question.setQuestionPoints("0");
113: questionGroup.addQuestion(question);
114: }
115:
116: return new Question();
117: }
118:
119: private String getPoints(String line) {
120: Matcher pointsMatcher = pointsPattern.matcher(line);
121:
122: String points = "";
123:
124: if (pointsMatcher.find())
125: points = pointsMatcher.group(1);
126:
127: return points;
128: }
129:
130: private String stripPoints(String line) {
131: Matcher pointsMatcher = pointsPattern.matcher(line);
132:
133: StringBuffer sb = new StringBuffer();
134: while (pointsMatcher.find()) {
135: pointsMatcher.appendReplacement(sb, "");
136: }
137: pointsMatcher.appendTail(sb);
138:
139: return sb.toString();
140: }
141:
142: private String removeMatchedPattern(Matcher m) {
143: StringBuffer buffer = new StringBuffer();
144:
145: m.appendReplacement(buffer, "");
146: m.appendTail(buffer);
147:
148: return buffer.toString();
149: }
150:
151: public QuestionGroup parse(String name, String description,
152: String data) {
153: QuestionGroup questionGroup = new QuestionGroup(name,
154: description);
155:
156: String cleanData = data;
157:
158: String[] lines = cleanData.split("\\n");
159: Question question = null;
160:
161: int questionNumber = 1;
162:
163: for (int i = 0; i < lines.length; i++) {
164: if (lines[i].endsWith("\\r"))
165: lines[i] = lines[i].replace('\r', ' ');
166: String line = lines[i].trim();
167:
168: if (null != line && !"".equals(line)) {
169: Matcher startOfQuestionMatcher = startOfQuestionPattern
170: .matcher(line);
171: Matcher startOfQuestionNumericMatcher = startOfQuestionNumericPattern
172: .matcher(line);
173: Matcher pointsMatcher = pointsPattern.matcher(line);
174:
175: // The question can begin with the word 'Question'
176: boolean isQuestionStart = startOfQuestionMatcher.find();
177: // Or it can begin with a number followed by a delimitor
178: boolean isQuestionNumericStart = startOfQuestionNumericMatcher
179: .find();
180: // Some users may prefer to delineate questions with just the points line
181: boolean isJustPoints = pointsMatcher.find();
182:
183: if (isQuestionStart || isQuestionNumericStart
184: || isJustPoints) {
185: question = saveLast(questionGroup, question);
186:
187: if (isQuestionStart)
188: line = removeMatchedPattern(startOfQuestionMatcher);
189: else if (isQuestionNumericStart)
190: line = removeMatchedPattern(startOfQuestionNumericMatcher);
191:
192: String points = getPoints(line);
193:
194: question.setQuestionPoints(points);
195:
196: String questionText = stripPoints(line);
197:
198: question.append(questionText.trim());
199:
200: // Don't bother looking at what the user enters for question numbers, since they
201: // could introduce errors -- we're going to number by order of questions
202: question.setQuestionNumber(questionNumber);
203:
204: questionNumber++;
205: } else if (null != question)
206: parseLine(question, line);
207: }
208: }
209: // Make sure we get that last question
210: if (null != question)
211: saveLast(questionGroup, question);
212:
213: return questionGroup;
214: }
215:
216: private void parseLine(Question question, String line) {
217: boolean isEndOfQuestion = endQuestionPattern.matcher(line)
218: .find();
219: boolean isCorrectAnswer = correctAnswerPattern.matcher(line)
220: .lookingAt();
221: Matcher answerMatcher = answerPattern.matcher(line);
222: boolean isAnswer = answerMatcher.find();
223: boolean isEmptyTrue = unnecessaryTruePattern.matcher(line)
224: .find();
225: boolean isEmptyFalse = unnecessaryFalsePattern.matcher(line)
226: .find();
227:
228: if (isEndOfQuestion) {
229: // Do nothing, we just want to ignore this line
230: } else if (isAnswer) {
231: question.addAnswer(answerMatcher.group(1), answerMatcher
232: .group(2), false);
233: } else if (isCorrectAnswer) {
234: Matcher multipleChoiceMatcher = correctMultipleChoicePattern
235: .matcher(line);
236: boolean isMC = multipleChoiceMatcher.find();
237: Matcher fillInMatcher = correctFillInPattern.matcher(line);
238: boolean isFI = fillInMatcher.find();
239: boolean isTrue = correctTruePattern.matcher(line).find();
240: boolean isFalse = correctFalsePattern.matcher(line).find();
241:
242: if (isMC) {
243: String earlierCorrectAnswer = question
244: .getCorrectAnswer();
245: boolean hasOneCorrectAnswerAlready = (null != earlierCorrectAnswer && !""
246: .equals(earlierCorrectAnswer.trim()));
247:
248: question.setCorrectAnswer(multipleChoiceMatcher
249: .group(1));
250: question.addAnswer(multipleChoiceMatcher.group(1),
251: multipleChoiceMatcher.group(2), true);
252:
253: if (hasOneCorrectAnswerAlready)
254: question
255: .setQuestionType(Question.MULTIPLE_CHOICE_MULTIPLE_ANSWER_QUESTION);
256: else
257: question
258: .setQuestionType(Question.MULTIPLE_CHOICE_QUESTION);
259: } else if (isTrue) {
260: question.addAnswer("A", "True", true);
261: question.addAnswer("B", "False", false);
262: question.setQuestionType(Question.TRUE_FALSE_QUESTION);
263: question.setCorrectAnswer("True");
264: } else if (isFalse) {
265: question.addAnswer("A", "True", false);
266: question.addAnswer("B", "False", true);
267: question.setQuestionType(Question.TRUE_FALSE_QUESTION);
268: question.setCorrectAnswer("False");
269: } else if (isFI) {
270: question.setCorrectAnswer(fillInMatcher.group(1));
271: question
272: .setQuestionType(Question.FILL_IN_THE_BLANK_QUESTION);
273: }
274: } else if (isEmptyTrue || isEmptyFalse) {
275: // Do nothing, since the 'correct' true or false answer is all we need.
276: } else {
277: // If we didn't match anything, then assume it's just part of the question text
278: question.append(line);
279: }
280: }
281:
282: public Document createDocument(QuestionGroup questionGroup) {
283: log
284: .debug("Creating a new qti document with the following name: "
285: + questionGroup.getName());
286: QuestestinteropDocument document = createQTIDocument(questionGroup);
287: XmlOptions options = new XmlOptions();
288:
289: options.setSavePrettyPrintIndent(4);
290: options.setSavePrettyPrint();
291: options.setUseDefaultNamespace();
292: Map prefixes = new HashMap();
293: prefixes.put("", "http://www.imsglobal.org/xsd/ims_qtiasiv1p2");
294: options.setSaveImplicitNamespaces(prefixes);
295: /*
296: try {
297: document.save(new File("/home/jrenfro/TestQuiz.xml"), options);
298: } catch (Exception e) {
299: e.printStackTrace();
300: }
301: */
302: InputStream inputStream = null;
303: try {
304: inputStream = document.newInputStream(options);
305:
306: DocumentBuilderFactory builderFactory = DocumentBuilderFactory
307: .newInstance();
308: builderFactory.setNamespaceAware(true);
309: DocumentBuilder documentBuilder = builderFactory
310: .newDocumentBuilder();
311: Document doc = documentBuilder.parse(inputStream);
312:
313: return doc;
314: } catch (ParserConfigurationException pce) {
315: log.error("Unable to parse quiz document ", pce);
316: } catch (IOException ioe) {
317: log
318: .error(
319: "Low-level IOException caught reading quiz document ",
320: ioe);
321: } catch (Exception qse) {
322: log
323: .error(
324: "Unable to save this quiz as an imported assessment ",
325: qse);
326: } finally {
327: if (null != inputStream) {
328: try {
329: inputStream.close();
330: } catch (IOException ioe) {
331: log.warn("Unimportant ioe");
332: }
333: }
334: }
335:
336: return null;
337: }
338:
339: public QuestestinteropDocument createQTIDocument(
340: QuestionGroup questionGroup) {
341: // Create a new instance of the document object
342: QuestestinteropDocument doc = QuestestinteropDocument.Factory
343: .newInstance();
344: // Add a Questestinterop object
345: doc.addNewQuestestinterop();
346: // Add an assessment object
347: doc.getQuestestinterop().addNewAssessment();
348: // Grab the assessment in a local var
349: AssessmentType assessment = doc.getQuestestinterop()
350: .getAssessment();
351: assessment.setTitle(questionGroup.getName());
352: // There's a bunch of metadata that we pre-populate here
353: addAssessmentMetadata(assessment);
354: // Now we're getting into the necessary xml fragments that make up the QTI schema
355: AssessmentcontrolType assessmentControl = assessment
356: .addNewAssessmentcontrol();
357: assessmentControl
358: .setFeedbackswitch(AssessmentcontrolType.Feedbackswitch.YES);
359: assessmentControl
360: .setHintswitch(AssessmentcontrolType.Hintswitch.YES);
361: assessmentControl
362: .setSolutionswitch(AssessmentcontrolType.Solutionswitch.YES);
363: assessmentControl.setView(AssessmentcontrolType.View.ALL);
364:
365: RubricType rubric = assessment.addNewRubric();
366: buildMattext(rubric.addNewMaterial().addNewMattext());
367:
368: PresentationMaterialType presentationMaterial = assessment
369: .addNewPresentationMaterial();
370: FlowMatType pFlowMat = presentationMaterial.addNewFlowMat();
371: pFlowMat.setClass1("Block");
372: buildMattext(pFlowMat.addNewMaterial().addNewMattext(),
373: questionGroup.getDescription());
374:
375: AssessfeedbackType assessFeedback = assessment
376: .addNewAssessfeedback();
377: assessFeedback.setIdent("Feedback");
378: assessFeedback.setTitle("Feedback");
379: assessFeedback.setView(AssessfeedbackType.View.ALL);
380:
381: FlowMatType afFlowMat = assessFeedback.addNewFlowMat();
382: buildMattext(afFlowMat.addNewMaterial().addNewMattext());
383:
384: // Add the section
385: SectionType section = assessment.addNewSection();
386: section.setIdent("Multiple Choice Questions");
387:
388: QtimetadataType sectionMetaData = section.addNewQtimetadata();
389: buildMetaDataField(sectionMetaData, "SECTION_OBJECTIVE", "");
390: buildMetaDataField(sectionMetaData, "SECTION_KEYWORD", "");
391: buildMetaDataField(sectionMetaData, "SECTION_RUBRIC", "");
392:
393: PresentationMaterialType sectionPresentationMaterial = section
394: .addNewPresentationMaterial();
395: FlowMatType spFlowMat = sectionPresentationMaterial
396: .addNewFlowMat();
397: spFlowMat.setClass1("Block");
398: buildMattext(spFlowMat.addNewMaterial().addNewMattext());
399: buildEmptyMaterialImage(spFlowMat.addNewMaterial()
400: .addNewMatimage());
401:
402: SelectionOrderingType selectionOrdering = section
403: .addNewSelectionOrdering();
404: selectionOrdering.setSequenceType("Normal");
405:
406: OrderType order = selectionOrdering.addNewOrder();
407: order.setOrderType("Sequential");
408:
409: // Now, loop through the questions and add to the qti section object
410: if (null != questionGroup.getQuestions()) {
411: for (Iterator it = questionGroup.getQuestions().iterator(); it
412: .hasNext();) {
413: Question question = (Question) it.next();
414: processQuestion(section, question);
415: }
416: }
417:
418: return doc;
419: }
420:
421: private void buildMetaDataField(QtimetadataType metadata,
422: String label, String entry) {
423: QtimetadatafieldType field = metadata.addNewQtimetadatafield();
424: field.setFieldlabel(label);
425: field.setFieldentry(entry);
426: }
427:
428: private void addAssessmentMetadata(AssessmentType assessment) {
429: QtimetadataType metadata = assessment.addNewQtimetadata();
430:
431: buildMetaDataField(metadata, "SHOW_CREATOR", "True");
432: buildMetaDataField(metadata, "SCALENAME", "STRONGLY_AGREE");
433: buildMetaDataField(metadata, "EDIT_AUTHORS", "True");
434: buildMetaDataField(metadata, "EDIT_DESCRIPTION", "True");
435: buildMetaDataField(metadata, "DISPLAY_TEMPLATE", "True");
436: buildMetaDataField(metadata, "CONSIDER_START_DATE", "False");
437: buildMetaDataField(metadata, "CONSIDER_END_DATE", "False");
438: buildMetaDataField(metadata, "CONSIDER_RETRACT_DATE", "False");
439: buildMetaDataField(metadata, "EDIT_END_DATE", "True");
440: buildMetaDataField(metadata, "EDIT_RETRACT_DATE", "True");
441: buildMetaDataField(metadata, "EDIT_PUBLISH_ANONYMOUS", "True");
442: buildMetaDataField(metadata, "EDIT_AUTHENTICATED_USERS", "True");
443: buildMetaDataField(metadata, "CONSIDER_ALLOW_IP", "False");
444: buildMetaDataField(metadata, "EDIT_ALLOW_IP", "True");
445: buildMetaDataField(metadata, "EDIT_USERID", "True");
446: buildMetaDataField(metadata, "CONSIDER_DURATION", "False");
447: buildMetaDataField(metadata, "AUTO_SUBMIT", "True");
448: buildMetaDataField(metadata, "EDIT_DURATION", "True");
449: buildMetaDataField(metadata, "EDIT_AUTO_SUBMIT", "True");
450: buildMetaDataField(metadata, "NAVIGATION", "Linear");
451: buildMetaDataField(metadata, "QUESTION_LAYOUT", "I");
452: buildMetaDataField(metadata, "QUESTION_NUMBERING", "CONTINUOUS");
453: buildMetaDataField(metadata, "EDIT_NAVIGATION", "True");
454: buildMetaDataField(metadata, "EDIT_QUESTION_LAYOUT", "True");
455: buildMetaDataField(metadata, "EDIT_QUESTION_NUMBERING", "True");
456: buildMetaDataField(metadata, "LATE_HANDLING", "True");
457: buildMetaDataField(metadata, "MAX_ATTEMPTS", "1");
458: buildMetaDataField(metadata, "EDIT_LATE_HANDLING", "True");
459: buildMetaDataField(metadata, "EDIT_MAX_ATTEMPTS", "True");
460: buildMetaDataField(metadata, "AUTO_SAVE", "False");
461: buildMetaDataField(metadata, "EDIT_AUTO_SAVE", "True");
462: buildMetaDataField(metadata, "EDIT_ASSESSFEEDBACK", "True");
463: buildMetaDataField(metadata, "EDIT_FINISH_URL", "True");
464: buildMetaDataField(metadata, "FEEDBACK_DELIVERY", "IMMEDIATE");
465: buildMetaDataField(metadata, "FEEDBACK_AUTHORING", "QUESTION");
466: buildMetaDataField(metadata, "EDIT_FEEDBACK_DELIVERY", "True");
467: buildMetaDataField(metadata, "EDIT_FEEDBACK_COMPONENTS", "True");
468: buildMetaDataField(metadata, "FEEDBACK_SHOW_CORRECT_RESPONSE",
469: "True");
470: buildMetaDataField(metadata, "FEEDBACK_SHOW_STUDENT_SCORE",
471: "True");
472: buildMetaDataField(metadata, "FEEDBACK_SHOW_ITEM_LEVEL", "True");
473: buildMetaDataField(metadata, "FEEDBACK_SHOW_SELECTION_LEVEL",
474: "False");
475: buildMetaDataField(metadata, "FEEDBACK_SHOW_GRADER_COMMENT",
476: "True");
477: buildMetaDataField(metadata, "FEEDBACK_SHOW_STATS", "True");
478: buildMetaDataField(metadata, "FEEDBACK_SHOW_QUESTION", "True");
479: buildMetaDataField(metadata, "FEEDBACK_SHOW_RESPONSE", "True");
480: buildMetaDataField(metadata, "ANONYMOUS_GRADING", "True");
481: buildMetaDataField(metadata, "GRADE_SCORE", "HIGHEST_SCORE");
482: buildMetaDataField(metadata, "GRADEBOOK_OPTIONS", "SELECTED");
483: buildMetaDataField(metadata, "EDIT_GRADEBOOK_OPTIONS", "True");
484: buildMetaDataField(metadata, "EDIT_ANONYMOUS_GRADING", "True");
485: buildMetaDataField(metadata, "EDIT_GRADE_SCORE", "True");
486: buildMetaDataField(metadata, "EDIT_BGCOLOR", "True");
487: buildMetaDataField(metadata, "EDIT_BGIMG", "True");
488: buildMetaDataField(metadata, "EDIT_ASSESSMENT_METADATA", "True");
489: buildMetaDataField(metadata, "EDIT_COLLECT_SECTION_METADATA",
490: "True");
491: buildMetaDataField(metadata, "EDIT_COLLECT_ITEM_METADATA",
492: "True");
493: buildMetaDataField(metadata, "COLLECT_SECTION_METADATA",
494: "False");
495: buildMetaDataField(metadata, "COLLECT_ITEM_METADATA", "False");
496: buildMetaDataField(metadata,
497: "templateInfo_isInstructorEditable", "true");
498: buildMetaDataField(metadata,
499: "assessmentAuthor_isInstructorEditable", "true");
500: buildMetaDataField(metadata,
501: "assessmentCreator_isInstructorEditable", "true");
502: buildMetaDataField(metadata,
503: "description_isInstructorEditable", "true");
504: buildMetaDataField(metadata, "dueDate_isInstructorEditable",
505: "true");
506: buildMetaDataField(metadata,
507: "retractDate_isInstructorEditable", "true");
508: buildMetaDataField(metadata,
509: "anonymousRelease_isInstructorEditable", "true");
510: buildMetaDataField(metadata,
511: "authenticatedRelease_isInstructorEditable", "true");
512: buildMetaDataField(metadata,
513: "ipAccessType_isInstructorEditable", "true");
514: buildMetaDataField(metadata,
515: "passwordRequired_isInstructorEditable", "true");
516: buildMetaDataField(metadata,
517: "timedAssessment_isInstructorEditable", "true");
518: buildMetaDataField(metadata,
519: "timedAssessmentAutoSubmit_isInstructorEditable",
520: "true");
521: buildMetaDataField(metadata,
522: "itemAccessType_isInstructorEditable", "true");
523: buildMetaDataField(metadata,
524: "displayChunking_isInstructorEditable", "true");
525: buildMetaDataField(metadata,
526: "displayNumbering_isInstructorEditable", "true");
527: buildMetaDataField(metadata,
528: "submissionModel_isInstructorEditable", "true");
529: buildMetaDataField(metadata,
530: "lateHandling_isInstructorEditable", "true");
531: buildMetaDataField(metadata, "autoSave_isInstructorEditable",
532: "true");
533: buildMetaDataField(metadata,
534: "submissionMessage_isInstructorEditable", "true");
535: buildMetaDataField(metadata,
536: "feedbackType_isInstructorEditable", "true");
537: buildMetaDataField(metadata,
538: "feedbackComponents_isInstructorEditable", "true");
539: buildMetaDataField(metadata,
540: "testeeIdentity_isInstructorEditable", "true");
541: buildMetaDataField(metadata,
542: "toGradebook_isInstructorEditable", "true");
543: buildMetaDataField(metadata,
544: "recordedScore_isInstructorEditable", "true");
545: buildMetaDataField(metadata, "bgColor_isInstructorEditable",
546: "true");
547: buildMetaDataField(metadata, "bgImage_isInstructorEditable",
548: "true");
549: buildMetaDataField(metadata,
550: "metadataAssess_isInstructorEditable", "true");
551: buildMetaDataField(metadata,
552: "metadataParts_isInstructorEditable", "true");
553: buildMetaDataField(metadata,
554: "metadataQuestions_isInstructorEditable", "true");
555: }
556:
557: private void processQuestion(SectionType section, Question question) {
558: log.debug("Processing a question");
559: switch (question.getQuestionType()) {
560: case Question.MULTIPLE_CHOICE_QUESTION:
561: processMultipleChoiceQuestion(section, question);
562: break;
563: case Question.MULTIPLE_CHOICE_MULTIPLE_ANSWER_QUESTION:
564: processMultipleChoiceMultipleAnswerQuestion(section,
565: question);
566: break;
567: case Question.FILL_IN_THE_BLANK_QUESTION:
568: processFillInQuestion(section, question);
569: break;
570: case Question.SHORT_ESSAY_QUESTION:
571: processShortEssayQuestion(section, question);
572: break;
573: case Question.TRUE_FALSE_QUESTION:
574: processTrueFalseQuestion(section, question);
575: break;
576: default:
577: // TODO: Notify the user that this question didn't work...
578: }
579: ;
580: }
581:
582: private void buildMattext(MattextType mattext) {
583: mattext.setCharset(DEFAULT_CHARSET);
584: mattext.setTexttype("text/plain");
585: }
586:
587: private void buildMattext(MattextType mattext, String value) {
588: buildMattext(mattext);
589: mattext.setStringValue(cdata(value));
590: }
591:
592: private String cdata(String value) {
593: return value;
594: //new StringBuffer().append("<![CDATA[").append(value).append("]]>").toString();
595: }
596:
597: private void buildEmptyMaterialImage(MatimageType matImage) {
598: matImage.setEmbedded("base64");
599: matImage.setImagtype("text/html");
600: }
601:
602: private void buildItemFeedback(ItemType item, String identity) {
603: ItemfeedbackType ifeedback = item.addNewItemfeedback();
604: ifeedback.setIdent(identity);
605: ifeedback.setView(ItemfeedbackType.View.ALL);
606: FlowMatType ifflow = ifeedback.addNewFlowMat();
607: ifflow.setClass1("Block");
608: buildMattext(ifflow.addNewMaterial().addNewMattext());
609: buildEmptyMaterialImage(ifflow.addNewMaterial()
610: .addNewMatimage());
611: }
612:
613: private void buildPresentationAndResponseLid(ItemType item,
614: Question question, String label, String respLidId,
615: ResponseLidType.Rcardinality.Enum rCardinality) {
616:
617: PresentationType presentation = item.addNewPresentation();
618: presentation.setLabel(label);
619: FlowType flow = presentation.addNewFlow();
620: flow.setClass1("Block");
621: MaterialType material = flow.addNewMaterial();
622: buildMattext(material.addNewMattext(), cdata(question
623: .getQuestion()));
624:
625: MaterialType material2 = flow.addNewMaterial();
626: buildMattext(material2.addNewMattext());
627:
628: // Only for multiple choice
629: if (Question.MULTIPLE_CHOICE_QUESTION == question
630: .getQuestionType()
631: || Question.MULTIPLE_CHOICE_MULTIPLE_ANSWER_QUESTION == question
632: .getQuestionType()) {
633: MatimageType matImage = material2.addNewMatimage();
634: matImage.setEmbedded("base64");
635: matImage.setImagtype("text/html");
636: matImage.setUri("");
637: }
638:
639: ResponseLidType responseLid = flow.addNewResponseLid();
640: responseLid.setIdent(respLidId);
641: responseLid.setRcardinality(rCardinality);
642: responseLid.setRtiming(ResponseLidType.Rtiming.NO);
643: RenderChoiceType renderChoice = responseLid
644: .addNewRenderChoice();
645: renderChoice.setShuffle(RenderChoiceType.Shuffle.NO);
646:
647: char c = 'A';
648: for (Iterator it = question.getAnswers().iterator(); it
649: .hasNext();) {
650: Answer answer = (Answer) it.next();
651: ResponseLabelType responseLabel = renderChoice
652: .addNewResponseLabel();
653: responseLabel.setIdent(String.valueOf(c));
654: responseLabel.setRarea(ResponseLabelType.Rarea.ELLIPSE);
655: responseLabel.setRrange(ResponseLabelType.Rrange.EXACT);
656: responseLabel.setRshuffle(ResponseLabelType.Rshuffle.YES);
657: MaterialType mat = responseLabel.addNewMaterial();
658: MattextType text = mat.addNewMattext();
659: text.setCharset(DEFAULT_CHARSET);
660: text.setTexttype("text/plain");
661: text.setStringValue(cdata(answer.getText()));
662: MaterialType m2 = responseLabel.addNewMaterial();
663: MatimageType mi = m2.addNewMatimage();
664: mi.setEmbedded("base64");
665: mi.setImagtype("text/html");
666: mi.setUri("");
667:
668: c++;
669: }
670: }
671:
672: private void addResponseLid(FlowType flow, Question question,
673: boolean isMultipleAnswer) {
674: ResponseLidType responseLid = flow.addNewResponseLid();
675: responseLid.setIdent("MCSC");
676: if (isMultipleAnswer)
677: responseLid
678: .setRcardinality(ResponseLidType.Rcardinality.MULTIPLE);
679: else
680: responseLid
681: .setRcardinality(ResponseLidType.Rcardinality.SINGLE);
682: responseLid.setRtiming(ResponseLidType.Rtiming.NO);
683: RenderChoiceType renderChoice = responseLid
684: .addNewRenderChoice();
685: renderChoice.setShuffle(RenderChoiceType.Shuffle.NO);
686:
687: char c = 'A';
688: for (Iterator it = question.getAnswers().iterator(); it
689: .hasNext();) {
690: Answer answer = (Answer) it.next();
691: ResponseLabelType responseLabel = renderChoice
692: .addNewResponseLabel();
693: responseLabel.setIdent(String.valueOf(c));
694: responseLabel.setRarea(ResponseLabelType.Rarea.ELLIPSE);
695: responseLabel.setRrange(ResponseLabelType.Rrange.EXACT);
696: responseLabel.setRshuffle(ResponseLabelType.Rshuffle.YES);
697: MaterialType mat = responseLabel.addNewMaterial();
698: MattextType text = mat.addNewMattext();
699: text.setCharset(DEFAULT_CHARSET);
700: text.setTexttype("text/plain");
701: text.setStringValue(cdata(answer.getText()));
702: MaterialType m2 = responseLabel.addNewMaterial();
703: MatimageType mi = m2.addNewMatimage();
704: mi.setEmbedded("base64");
705: mi.setImagtype("text/html");
706: mi.setUri("");
707:
708: c++;
709: }
710: }
711:
712: private void addRespProcessing(ItemType item, Question question,
713: String respident) {
714: ResprocessingType resProcessing = item.addNewResprocessing();
715: DecvarType decvar = resProcessing.addNewOutcomes()
716: .addNewDecvar();
717: decvar.setDefaultval("0");
718: decvar.setMaxvalue(question.getQuestionPoints());
719: decvar.setMinvalue("0");
720: decvar.setVarname("SCORE");
721: decvar.setVartype(DecvarType.Vartype.INTEGER);
722:
723: char alpha = 'A';
724:
725: if (null != question.getAnswers()) {
726: for (Iterator it = question.getAnswers().iterator(); it
727: .hasNext();) {
728: Answer answer = (Answer) it.next();
729: String response = answer.isCorrect() ? "Correct"
730: : "InCorrect";
731:
732: RespconditionType respCondition = resProcessing
733: .addNewRespcondition();
734: respCondition
735: .setContinue(RespconditionType.Continue.NO);
736: ConditionvarType conditionVar = respCondition
737: .addNewConditionvar();
738: VarequalType varEqual = conditionVar.addNewVarequal();
739: varEqual.setCase(VarequalType.Case.YES);
740: varEqual.setRespident(respident);
741: varEqual.setStringValue(String.valueOf(alpha));
742: SetvarType setVar = respCondition.addNewSetvar();
743: setVar.setAction(SetvarType.Action.ADD);
744: setVar.setVarname("SCORE");
745: setVar.setStringValue("0.0");
746: DisplayfeedbackType feedback1 = respCondition
747: .addNewDisplayfeedback();
748: feedback1
749: .setFeedbacktype(DisplayfeedbackType.Feedbacktype.RESPONSE);
750: feedback1.setLinkrefid(response);
751: DisplayfeedbackType feedback2 = respCondition
752: .addNewDisplayfeedback();
753: feedback2
754: .setFeedbacktype(DisplayfeedbackType.Feedbacktype.RESPONSE);
755: feedback2.setLinkrefid("AnswerFeedback");
756: feedback2.setStringValue(cdata("null"));
757:
758: alpha++;
759: }
760: }
761: }
762:
763: private void processTrueFalseQuestion(SectionType section,
764: Question question) {
765: ItemType item = section.addNewItem();
766: item.setTitle("True-False");
767:
768: ItemmetadataType itemMetaData = item.addNewItemmetadata();
769: QtimetadataType qtiMetaData = itemMetaData.addNewQtimetadata();
770:
771: buildMetaDataField(qtiMetaData, "qmd_itemtype", "True False");
772: buildMetaDataField(qtiMetaData, "TEXT_FORMAT", "HTML");
773:
774: ItemrubricType itemRubric = item.addNewItemrubric();
775: itemRubric.setView(ItemrubricType.View.ALL);
776:
777: MattextType mattext = itemRubric.addNewMaterial()
778: .addNewMattext();
779: mattext.setCharset(DEFAULT_CHARSET);
780: mattext.setTexttype("text/plain");
781:
782: buildPresentationAndResponseLid(item, question, "Resp001",
783: "TF02", ResponseLidType.Rcardinality.SINGLE);
784:
785: addRespProcessing(item, question, "TF02");
786:
787: buildItemFeedback(item, "Correct");
788: buildItemFeedback(item, "InCorrect");
789: }
790:
791: private void processMultipleChoiceQuestion(SectionType section,
792: Question question) {
793:
794: ItemType item = section.addNewItem();
795: item.setTitle("Multiple Choice");
796:
797: ItemmetadataType itemMetaData = item.addNewItemmetadata();
798: QtimetadataType qtiMetaData = itemMetaData.addNewQtimetadata();
799:
800: buildMetaDataField(qtiMetaData, "qmd_itemtype",
801: "Multiple Choice");
802: buildMetaDataField(qtiMetaData, "TEXT_FORMAT", "HTML");
803: buildMetaDataField(qtiMetaData, "hasRationale", "False");
804:
805: ItemrubricType itemRubric = item.addNewItemrubric();
806: MattextType mattext = itemRubric.addNewMaterial()
807: .addNewMattext();
808: mattext.setCharset("ascii-us");
809: mattext.setTexttype("text/plain");
810:
811: buildPresentationAndResponseLid(item, question, "Resp001",
812: "MCSC", ResponseLidType.Rcardinality.SINGLE);
813:
814: addRespProcessing(item, question, "MCSC");
815:
816: int numberOfAnswers = question.getAnswers().size();
817: char c = 'A';
818: for (int i = 0; i < numberOfAnswers; i++) {
819: buildItemFeedback(item, String.valueOf(c) + "1");
820: c++;
821: }
822:
823: buildItemFeedback(item, "Correct");
824: buildItemFeedback(item, "InCorrect");
825: }
826:
827: private void processMultipleChoiceMultipleAnswerQuestion(
828: SectionType section, Question question) {
829:
830: ItemType item = section.addNewItem();
831: item.setTitle("Multiple Correct");
832:
833: ItemmetadataType itemMetaData = item.addNewItemmetadata();
834: QtimetadataType qtiMetaData = itemMetaData.addNewQtimetadata();
835:
836: buildMetaDataField(qtiMetaData, "qmd_itemtype",
837: "Multiple Correct Answer");
838: buildMetaDataField(qtiMetaData, "TEXT_FORMAT", "HTML");
839: buildMetaDataField(qtiMetaData, "hasRationale", "False");
840:
841: ItemrubricType itemRubric = item.addNewItemrubric();
842: MattextType mattext = itemRubric.addNewMaterial()
843: .addNewMattext();
844: mattext.setCharset("ascii-us");
845: mattext.setTexttype("text/plain");
846:
847: buildPresentationAndResponseLid(item, question, "Resp001",
848: "MCMC", ResponseLidType.Rcardinality.MULTIPLE);
849:
850: addRespProcessing(item, question, "MCMC");
851:
852: int numberOfAnswers = question.getAnswers().size();
853: char c = 'A';
854: for (int i = 0; i < numberOfAnswers; i++) {
855: buildItemFeedback(item, String.valueOf(c) + "1");
856: c++;
857: }
858:
859: buildItemFeedback(item, "Correct");
860: buildItemFeedback(item, "InCorrect");
861: }
862:
863: private void processFillInQuestion(SectionType section,
864: Question question) {
865: ItemType item = section.addNewItem();
866: item.setTitle("Fill in the Blank");
867:
868: ItemmetadataType itemMetaData = item.addNewItemmetadata();
869: QtimetadataType qtiMetaData = itemMetaData.addNewQtimetadata();
870:
871: buildMetaDataField(qtiMetaData, "qmd_itemtype",
872: "Fill In the Blank");
873: buildMetaDataField(qtiMetaData, "TEXT_FORMAT", "HTML");
874: buildMetaDataField(qtiMetaData, "MUTUALLY_EXCLUSIVE", "True");
875:
876: ItemrubricType itemRubric = item.addNewItemrubric();
877: MattextType mattext = itemRubric.addNewMaterial()
878: .addNewMattext();
879: mattext.setCharset(DEFAULT_CHARSET);
880: mattext.setTexttype("text/plain");
881:
882: PresentationType presentation = item.addNewPresentation();
883: presentation.setLabel("FIB");
884: FlowType flow = presentation.addNewFlow();
885: flow.setClass1("Block");
886: MaterialType material = flow.addNewMaterial();
887: buildMattext(material.addNewMattext(), cdata(question
888: .getQuestion()));
889:
890: MaterialType material2 = flow.addNewMaterial();
891: buildMattext(material2.addNewMattext());
892:
893: ResponseStrType responseStr = flow.addNewResponseStr();
894: responseStr
895: .setRcardinality(ResponseStrType.Rcardinality.ORDERED);
896: responseStr.setRtiming(ResponseStrType.Rtiming.NO);
897:
898: RenderFibType renderFib = responseStr.addNewRenderFib();
899: renderFib.setCharset(DEFAULT_CHARSET);
900: renderFib.setColumns("5");
901: renderFib.setEncoding("UTF_8");
902: renderFib.setFibtype(RenderFibType.Fibtype.STRING);
903: renderFib.setPrompt(RenderFibType.Prompt.BOX);
904: renderFib.setRows("1");
905:
906: MaterialType material3 = flow.addNewMaterial();
907: buildMattext(material3.addNewMattext());
908:
909: ResprocessingType resProcessing = item.addNewResprocessing();
910: OutcomesType outcomes = resProcessing.addNewOutcomes();
911: DecvarType decvar = outcomes.addNewDecvar();
912: decvar.setDefaultval("0");
913: decvar.setMaxvalue(question.getQuestionPoints());
914: decvar.setMinvalue("0");
915: decvar.setVarname("SCORE");
916: decvar.setVartype(DecvarType.Vartype.INTEGER);
917:
918: RespconditionType respCondition = resProcessing
919: .addNewRespcondition();
920: respCondition.setContinue(RespconditionType.Continue.YES);
921: ConditionvarType condition = respCondition.addNewConditionvar();
922: OrType or = condition.addNewOr();
923: VarequalType varequal = or.addNewVarequal();
924: varequal.setCase(VarequalType.Case.NO);
925: varequal.setStringValue(cdata(question.getCorrectAnswer()));
926: SetvarType setvar = respCondition.addNewSetvar();
927: setvar.setAction(SetvarType.Action.ADD);
928: setvar.setVarname("SCORE");
929: setvar.setStringValue("0");
930:
931: buildItemFeedback(item, "Correct");
932: buildItemFeedback(item, "InCorrect");
933: }
934:
935: private void processShortEssayQuestion(SectionType section,
936: Question question) {
937: ItemType item = section.addNewItem();
938: item.setTitle("Essay Question");
939:
940: ItemmetadataType itemMetaData = item.addNewItemmetadata();
941: QtimetadataType qtiMetaData = itemMetaData.addNewQtimetadata();
942:
943: buildMetaDataField(qtiMetaData, "qmd_itemtype", "Essay");
944: buildMetaDataField(qtiMetaData, "TEXT_FORMAT", "HTML");
945:
946: ItemrubricType itemRubric = item.addNewItemrubric();
947: itemRubric.setView(ItemrubricType.View.ALL);
948:
949: buildMattext(itemRubric.addNewMaterial().addNewMattext());
950:
951: PresentationType presentation = item.addNewPresentation();
952: presentation.setLabel("Model Short Answer");
953: FlowType flow = presentation.addNewFlow();
954: flow.setClass1("Block");
955:
956: MaterialType material = flow.addNewMaterial();
957: buildMattext(material.addNewMattext(), cdata(question
958: .getQuestion()));
959:
960: MaterialType material2 = flow.addNewMaterial();
961: buildMattext(material2.addNewMattext());
962:
963: addResponseLid(flow, question, false);
964:
965: ResprocessingType resProcessing = item.addNewResprocessing();
966: OutcomesType outcomes = resProcessing.addNewOutcomes();
967: DecvarType decvar = outcomes.addNewDecvar();
968: decvar.setDefaultval("0");
969: decvar.setMaxvalue(question.getQuestionPoints());
970: decvar.setMinvalue("0");
971: decvar.setVarname("SCORE");
972: decvar.setVartype(DecvarType.Vartype.INTEGER);
973:
974: buildItemFeedback(item, "Correct");
975: buildItemFeedback(item, "InCorrect");
976: }
977:
978: }
|