/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/sam/trunk/samigo-qti/src/java/org/sakaiproject/tool/assessment/qti/helper/item/ItemTypeExtractionStrategy.java $ * $Id: ItemTypeExtractionStrategy.java 106463 2012-04-02 12:20:09Z david.horwitz@uct.ac.za $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 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.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.tool.assessment.facade.ItemFacade; import org.sakaiproject.tool.assessment.qti.constants.AuthoringConstantStrings; /** * Encapsulates the work of figuring out what type the item is. * Right now, uses static methods, later, we might want to change, add to factory. * * We use the QTI qmd_itemtype in itemmetadata as the preferred way to ascertain type. * We fall back on title and then item structure, also attempring keyword matching. * Note, this may be deprecated in favor of a vocabulary based approach. * Need to investigate. Anyway, this is the form that is backwardly compatible. * * @author Ed Smiley */ public class ItemTypeExtractionStrategy { private static Log log = LogFactory.getLog(ItemTypeExtractionStrategy.class); private static final Long DEFAULT_TYPE = Long.valueOf(2); /** * Obtain Long item type id from strings extracted from item. * @param title the item title * @param itemIntrospect hte structure based guess from XSL * @param qmdItemtype hte item type meta information * @return Long item type id */ public static Long calculate(String title, String itemIntrospect, String qmdItemtype) { String itemType = obtainTypeString(title, itemIntrospect, qmdItemtype); Long typeId = getType(itemType); return typeId; } public static Long calculate(Map itemMap) { String itemType = null; if (itemMap.get("type") != null && !itemMap.get("type").equals("")) { if (itemMap.get("type").equals("FIB")) { itemType = AuthoringConstantStrings.FIB; } else if (itemMap.get("type").equals("Matching")) itemType = AuthoringConstantStrings.MATCHING; } else if (itemMap.get("itemRcardinality") != null && !itemMap.get("itemRcardinality").equals("")) { String itemRcardinality = (String) itemMap.get("itemRcardinality"); if ("Single".equalsIgnoreCase(itemRcardinality)) { List answerList = (List) itemMap.get("itemAnswer"); if (answerList.size() == 2) { String firstAnswer = ((String) answerList.get(0)).split(":::")[1]; String secondAnswer = ((String) answerList.get(1)).split(":::")[1]; if ((firstAnswer.equalsIgnoreCase("true") && secondAnswer.equalsIgnoreCase("false")) || (firstAnswer.equalsIgnoreCase("false") && secondAnswer.equalsIgnoreCase("true"))) { itemType = AuthoringConstantStrings.TF; } else { itemType = AuthoringConstantStrings.MCSC; } } else { itemType = AuthoringConstantStrings.MCSC; } } else { itemType = AuthoringConstantStrings.MCMC; } } else { itemType = AuthoringConstantStrings.ESSAY; } Long typeId = getType(itemType); return typeId; } /** * simple unit test * * @param args not used */ public static void main(String[] args) { // ItemFacade item = new ItemFacade(); String title; String intro; String qmd; String[] typeArray = AuthoringConstantStrings.itemTypes; for (int i = 0; i < typeArray.length; i++) { qmd = typeArray[i]; title = "title"; intro = "introspect"; Long typeId = calculate(title, intro, qmd); } for (int i = 0; i < typeArray.length; i++) { title = typeArray[i]; intro = "introspect"; qmd = "qmd"; Long typeId = calculate(title, intro, qmd); } for (int i = 0; i < typeArray.length; i++) { title = "title"; intro = typeArray[i]; qmd = "qmd"; Long typeId = calculate(title, intro, qmd); } } /** * Figure out the best string describing type to use. * @param titleItemType the item's title * @param itemIntrospectItemType best guess based on structure * @param qmdItemType the type declared in metadata, if any * @return the string describing the item. */ private static String obtainTypeString( String titleItemType, String itemIntrospectItemType, String qmdItemType) { log.debug("qmdItemType: " + qmdItemType); log.debug("titleItemType: " + titleItemType); log.debug("itemIntrospectItemType: " + itemIntrospectItemType); // if we can't find any other approach String itemType = itemIntrospectItemType; // start with item title if (titleItemType != null) { if (isExactType(titleItemType)) itemType = titleItemType; titleItemType = guessType(titleItemType); if (titleItemType != null) itemType = titleItemType; } // next try to figure out from qmd_itemtype metadata if (qmdItemType != null) { if (isExactType(qmdItemType)) itemType = qmdItemType; qmdItemType = guessType(qmdItemType); if (qmdItemType != null) itemType = qmdItemType; } log.debug("returning itemType: " + itemType); return itemType; } /** * Try to infer the type of imported question from string, such as title. * @param candidate string to guess type from * @return AuthoringConstantStrings.{type} */ private static String guessType(String candidate) { String itemType; String lower = candidate.toLowerCase(); itemType = matchGuess(lower); return itemType; } /** * helper method for guessType() with its type guess strategy. * @param toGuess string to test * @return the guessed canonical type */ private static String matchGuess(String toGuess) { // not sure how well this works for i18n. String itemType = null; if (toGuess.indexOf("multiple") != -1 && toGuess.indexOf("response") != -1) { itemType = AuthoringConstantStrings.MCMC; } else if (toGuess.indexOf("true") != -1 || toGuess.indexOf("tf") != -1) { itemType = AuthoringConstantStrings.TF; } else if (toGuess.indexOf("matrix") != -1) { itemType = AuthoringConstantStrings.MATRIX; } else if (toGuess.indexOf("survey") != -1) { itemType = AuthoringConstantStrings.SURVEY; } else if (toGuess.indexOf("single") != -1 && toGuess.indexOf("correct") != -1) { if (toGuess.indexOf("selection") != -1) { itemType = AuthoringConstantStrings.MCMCSS; } else { itemType = AuthoringConstantStrings.MCSC; } } else if (toGuess.indexOf("multiple") != -1 && toGuess.indexOf("correct") != -1) { itemType = AuthoringConstantStrings.MCMC; } else if (toGuess.indexOf("audio") != -1 || toGuess.indexOf("recording") != -1) { itemType = AuthoringConstantStrings.AUDIO; } else if (toGuess.indexOf("file") != -1 || toGuess.indexOf("upload") != -1) { itemType = AuthoringConstantStrings.FILE; } else if (toGuess.indexOf("match") != -1) { itemType = AuthoringConstantStrings.MATCHING; } else if (toGuess.indexOf("fib") != -1 || toGuess.indexOf("fill") != -1 || toGuess.indexOf("f.i.b.") != -1 ) { itemType = AuthoringConstantStrings.FIB; } // place holder for numerical responses questions else if (toGuess.indexOf("numerical") != -1 || (toGuess.indexOf("calculate") != -1 && toGuess.indexOf("question") == -1) || toGuess.indexOf("math") != -1 ) { itemType = AuthoringConstantStrings.FIN; } // CALCULATED_QUESTION else if (toGuess.indexOf("calcq") != -1 || toGuess.indexOf("c.q.") != -1 || toGuess.indexOf("cq") != -1 || (toGuess.indexOf("calculate") != -1 && toGuess.indexOf("question") != -1) ) { itemType = AuthoringConstantStrings.CALCQ; } else if (toGuess.indexOf("essay") != -1 || toGuess.indexOf("short") != -1) { itemType = AuthoringConstantStrings.ESSAY; } return itemType; } /** * * @param typeString get type string * @return Long item type */ private static Long getType(String typeString) { Long type = getValidType(typeString); if (type==null) { log.warn("Unable to set item type: '" + typeString + "'."); log.warn("guessing item type: '" + DEFAULT_TYPE + "'."); type = DEFAULT_TYPE; } return type; } /** * Is this one of our exact type strings, or not? * Ignores case and extra space. * @param typeString the string * @return true if it is */ private static boolean isExactType(String typeString) { return getValidType(typeString)!=null; } /** * Get valid type as Long matching typeString * Ignores case and extra space. * @param typeString the candidate string * @return valid type as Long matching typeString, or null, if no match. */ private static Long getValidType(String typeString) { Long type = null; String[] typeArray = AuthoringConstantStrings.itemTypes; for (int i = 0; i < typeArray.length; i++) { if (typeString.trim().equalsIgnoreCase(typeArray[i])) { type = Long.valueOf(i); break; } } return type; } }