org.etudes.mneme.impl.ImportQti2ServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.etudes.mneme.impl.ImportQti2ServiceImpl.java

Source

/**********************************************************************************
 * $URL: https://source.etudes.org/svn/apps/mneme/trunk/mneme-impl/impl/src/java/org/etudes/mneme/impl/ImportQti2ServiceImpl.java $
 * $Id: ImportQti2ServiceImpl.java 8562 2014-08-30 06:07:15Z mallikamt $
 ***********************************************************************************
 *
 * Copyright (c) 2013, 2014 Etudes, Inc.
 * 
 * Licensed under the Apache 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.apache.org/licenses/LICENSE-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.etudes.mneme.impl;

import java.io.File;
import java.io.FileInputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.etudes.mneme.api.Assessment;
import org.etudes.mneme.api.AssessmentPermissionException;
import org.etudes.mneme.api.AssessmentService;
import org.etudes.mneme.api.AssessmentType;
import org.etudes.mneme.api.AttachmentService;
import org.etudes.mneme.api.GradesService;
import org.etudes.mneme.api.ImportQti2Service;
import org.etudes.mneme.api.Part;
import org.etudes.mneme.api.Pool;
import org.etudes.mneme.api.PoolDraw;
import org.etudes.mneme.api.PoolService;
import org.etudes.mneme.api.Question;
import org.etudes.mneme.api.QuestionGrouping;
import org.etudes.mneme.api.QuestionPick;
import org.etudes.mneme.api.QuestionService;
import org.etudes.mneme.api.ReviewShowCorrect;
import org.etudes.mneme.api.ReviewTiming;
import org.etudes.mneme.api.SecurityService;
import org.etudes.mneme.impl.EssayQuestionImpl.SubmissionType;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.dom.DOMXPath;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.content.cover.ContentHostingService;
import org.sakaiproject.content.cover.ContentTypeImageService;
import org.sakaiproject.entity.api.EntityManager;
import org.sakaiproject.entity.api.Reference;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.entity.api.ResourcePropertiesEdit;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.exception.IdUsedException;
import org.sakaiproject.i18n.InternationalizedMessages;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.thread_local.api.ThreadLocalManager;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.util.ResourceLoader;
import org.sakaiproject.util.StringUtil;
import org.sakaiproject.util.Xml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;

/**
 * <p>
 * ImportQtiServiceImpl implements ImportQtiService
 * </p>
 */
public class ImportQti2ServiceImpl implements ImportQti2Service {
    protected class Average {
        protected int count = 0;

        protected float total = 0.0f;

        public void add(float value) {
            count++;
            total += value;
        }

        public float getAverage() {
            if (count > 0) {
                return total / count;
            }
            return 0.0f;
        }
    }

    public static final int MAX_NAME_LENGTH = 150;

    /** Our logger. */
    private static Log M_log = LogFactory.getLog(ImportQti2ServiceImpl.class);

    /** Dependency: AssessmentService */
    protected AssessmentService assessmentService = null;

    /** Dependency: AttachmentService */
    protected AttachmentService attachmentService = null;

    /** Messages bundle name. */
    protected String bundle = null;

    /** Dependency: EntityManager */
    protected EntityManager entityManager = null;

    /** Dependency: EventTrackingService */
    protected EventTrackingService eventTrackingService = null;

    /** Messages. */
    protected transient InternationalizedMessages messages = null;

    /** Dependency: PoolService */
    protected PoolService poolService = null;

    /** Dependency: QuestionService */
    protected QuestionService questionService = null;

    /** Dependency: SecurityService */
    protected SecurityService securityService = null;

    /** Dependency: SessionManager */
    protected SessionManager sessionManager = null;

    /** Dependency: ThreadLocalManager. */
    protected ThreadLocalManager threadLocalManager = null;

    /**
     * Returns to uninitialized state.
     */
    public void destroy() {
        M_log.info("destroy()");
    }

    /**
     * {@inheritDoc}
     */
    public void importPool(Document doc, String context, String unzipBackUpLocation)
            throws AssessmentPermissionException {
        if ((doc == null) || (!doc.hasChildNodes()))
            return;

        // get a name for the pool
        Average pointsAverage = new Average();
        HashMap<String, Question> allQuestions = new HashMap<String, Question>();
        HashMap<String, String> allQuestionPoints = new HashMap<String, String>();
        HashMap<String, Pool> allPools = new HashMap<String, Pool>();

        // 1. Create one defaultPool for all questions
        Pool pool = this.poolService.newPool(context);
        pool.setTitle(findPoolTitle(context, "", doc));

        String poolDescription = findPoolDescription(doc);
        pool.setDescription(poolDescription);
        allPools.put("defaultPool", pool);

        // 2. Read questions of type item from manifest file
        try {
            // if etudes export package has pool resources
            XPath poolPath = new DOMXPath("//resources/resource[starts-with(@identifier,'POOL')]");

            List<Element> poolItems = poolPath.selectNodes(doc);
            for (Element poolItem : poolItems) {
                String pId = poolItem.getAttribute("identifier");
                pId = pId.replace("POOL", "");

                if (!allPools.containsKey(pId)) {
                    Pool p = this.poolService.newPool(context);
                    p.setTitle(findPoolTitle(context, poolItem.getAttribute("title"), doc));
                    allPools.put(pId, p);
                }
            }

            XPath itemPath = new DOMXPath("//*[contains(local-name(),'resource')]");
            List<Element> items = itemPath.selectNodes(doc);

            for (Element item : items) {
                String type = item.getAttribute("type");
                if (type == null || !type.startsWith("imsqti_item_"))
                    continue;

                // read href value
                String fileLocation = item.getAttribute("href");
                if ("".equals(fileLocation))
                    continue;

                // read Xml file and create question
                Question question = null;
                try {
                    String baseName = "";
                    fileLocation = fileLocation.replace("\\", "/");
                    if (fileLocation.lastIndexOf("/") != -1)
                        baseName = "/" + fileLocation.substring(0, fileLocation.lastIndexOf("/"));

                    question = processQuestionItemFile(allPools, allQuestions, context, doc, item,
                            allQuestionPoints, pointsAverage, unzipBackUpLocation, fileLocation, baseName);
                } catch (Exception e) {
                    M_log.debug(e.getMessage());
                    continue;
                }
            }

            // 3. save pool
            // pool.setPointsEdit(pointsAverage.getAverage());
            Iterator<String> poolIterator = allPools.keySet().iterator();
            while (poolIterator.hasNext()) {
                String poolId = poolIterator.next();
                Pool p = allPools.get(poolId);
                if (p.getNumQuestions().intValue() == 0)
                    continue;
                this.poolService.savePool(p);
            }

            // 4. then later read assessments and integrate with gradebook and save
            String rights = findRightsInformation(doc);
            int testCount = 0;
            for (Element testItem : items) {
                String type = testItem.getAttribute("type");
                if (type == null || !type.startsWith("imsqti_test_"))
                    continue;

                // read href value
                String fileLocation = testItem.getAttribute("href");
                if ("".equals(fileLocation))
                    continue;

                String baseName = "";
                fileLocation = fileLocation.replace("\\", "/");
                if (fileLocation.lastIndexOf("/") != -1)
                    baseName = "/" + fileLocation.substring(0, fileLocation.lastIndexOf("/"));

                processAssessmentFiles(context, testItem, unzipBackUpLocation, fileLocation, baseName, rights,
                        allQuestions, allQuestionPoints, allPools);
                testCount++;
            }

            // create one default test for the pool just created if manifest has no resource of imsqi_test type.
            if (testCount == 0)
                createDefaultAssessment(context, allPools, allQuestions, allQuestionPoints);

        } catch (JaxenException e) {
            M_log.warn(e.toString());
        } catch (Exception e) {
            M_log.warn(e.toString());
        }
    }

    /**
     * If there are no resource for test then create one default assessment
     * @param context
     * @param allPools
     * @param allQuestions
     * @param allQuestionPoints
     * @return
     * @throws Exception
     */
    private Assessment createDefaultAssessment(String context, HashMap<String, Pool> allPools,
            HashMap<String, Question> allQuestions, HashMap<String, String> allQuestionPoints) throws Exception {
        if (allPools == null || allPools.size() == 0)
            return null;

        // create assessment
        Assessment test = assessmentService.newAssessment(context);
        test.setType(AssessmentType.test);
        Part part = test.getParts().addPart();
        part.setTitle("");

        Boolean autoRelease = null;
        int qCount = 0;
        String title = "untitled (import QTI)";
        for (Iterator<String> i = allQuestions.keySet().iterator(); i.hasNext();) {
            String key = i.next();
            Question question = allQuestions.get(key);

            // score
            String points = "1";
            if (allQuestionPoints.containsKey(key))
                points = allQuestionPoints.get(key);
            QuestionPick questionPick = part.addPickDetail(question);
            questionPick.setPoints(Float.parseFloat(points));

            if ("mneme:Essay".equals(question.getType()))
                autoRelease = new Boolean(false);

            qCount++;
            if (qCount == allQuestions.size()) {
                title = question.getPool().getTitle();
            }
        }
        test.setTitle(title);

        // auto release of grading
        if (autoRelease != null)
            test.getGrading().setAutoRelease(autoRelease);

        test.getGrading().setGradebookIntegration(Boolean.TRUE);

        assessmentService.saveAssessment(test);
        return test;
    }

    /**
     * Read assessment.xml file and create an assessment with all of its settings.
     * 
     * @param context
     * @param testItem
     * @param unzipBackUpLocation
     * @param fileLocation
     * @param allQuestions
     * @return
     */
    private Assessment processAssessmentFiles(String context, Element testItem, String unzipBackUpLocation,
            String fileLocation, String baseName, String rights, HashMap<String, Question> allQuestions,
            HashMap<String, String> allQuestionPoints, HashMap<String, Pool> allPools) throws Exception {
        Assessment test = null;
        int partCount = 1;
        Boolean allLikert = null;
        Boolean autoRelease = null;
        boolean randomAccess = false;
        String layout = "question";
        boolean partNumbering = true;

        // Read Assessment.Xml file
        Document contentsDOM = Xml.readDocument(unzipBackUpLocation + File.separator + fileLocation);
        if (contentsDOM == null)
            return null;

        Element rootAssessment = contentsDOM.getDocumentElement();
        String title = rootAssessment.getAttribute("title");

        // create assessment
        test = assessmentService.newAssessment(context);
        test.setTitle(findAssessmentTitle(context, title));

        // read identifier to find survey, assignment or test
        AssessmentType testType = findAssessmentType(testItem);
        test.setType(testType);

        // read title and description from testItem
        String description = findDescription(testItem);
        if (description != null && description.length() > 0) {
            description = processInstructionsEmbedMedia(unzipBackUpLocation, context, description,
                    new ArrayList<String>());
            test.getPresentation().setText(description);
        }

        // time limit
        XPath timeLimitPath = new DOMXPath("/assessmentTest/timeLimits");
        Element timeElement = (Element) timeLimitPath.selectSingleNode(contentsDOM);
        String maxTime = (timeElement != null) ? timeElement.getAttribute("maxTime") : "";

        if (maxTime.length() > 0) {
            test.setHasTimeLimit(true);
            if (maxTime.indexOf(".") != -1)
                maxTime = maxTime.substring(0, maxTime.indexOf("."));
            test.setTimeLimit(new Long(maxTime));
        }

        // settings from outcome declaration
        HashMap<String, String> settings = processOutcomeDeclaration(contentsDOM);
        test = processSettingsfromOutcomeDeclaration(settings, test);

        //part numbering
        if (settings.containsKey("PartNumbering"))
            partNumbering = new Boolean(settings.get("PartNumbering")).booleanValue();

        // final message
        XPath finalMessagePath = new DOMXPath("/assessmentTest/testFeedback[@access='atEnd']");
        String finalMessage = finalMessagePath.stringValueOf(contentsDOM);
        if (finalMessage != null && finalMessage.length() > 0)
            test.getSubmitPresentation().setText(finalMessage);

        // add parts and questions
        List<Element> partElements = new ArrayList();
        XPath partsPath = new DOMXPath("/assessmentTest/testPart");
        partElements = partsPath.selectNodes(contentsDOM);

        // <itemSessionControl maxAttempts="2" allowReview="true" showFeedback="true">
        test = processSettingsfromItemSessionElement(partElements, test);

        if (partElements != null && partElements.size() > 0) {
            // partNumbering from outcome declaration
            test.getParts().setContinuousNumbering(partNumbering);

            for (Element partElement : partElements) {
                if (partElement == null)
                    continue;
                String navigationMode = partElement.getAttribute("navigationMode");
                if (!"linear".equalsIgnoreCase(navigationMode))
                    randomAccess = true;

                // section if more than one section bring as etudes parts
                List<Element> sectionElements = new ArrayList();
                XPath sectionPath = new DOMXPath("assessmentSection");
                sectionElements = sectionPath.selectNodes(partElement);

                if (sectionElements == null || sectionElements.size() == 0)
                    continue;
                for (Element sectionElement : sectionElements) {
                    String sectionIdentifier = sectionElement.getAttribute("identifier");
                    // rubric
                    XPath rubricPath = new DOMXPath("rubricBlock");
                    Element rubricElement = (Element) rubricPath.selectSingleNode(sectionElement);

                    // if part has no questions then skip
                    XPath ItemRefPath = new DOMXPath("assessmentItemRef");
                    List<Element> refElements = ItemRefPath.selectNodes(sectionElement);
                    if (refElements == null || refElements.size() == 0) {
                        if (rubricElement != null) {
                            String partInstructions = normalizeElementBody(contentsDOM, rubricElement);
                            partInstructions = processInstructionsEmbedMedia(unzipBackUpLocation.concat(baseName),
                                    context, partInstructions, new ArrayList<String>());
                            test.getPresentation().setText(partInstructions);
                        }
                        continue;
                    }

                    // create Part
                    Part part = test.getParts().addPart();
                    String partTitle = sectionElement.getAttribute("title");
                    partTitle = (partTitle != null && partTitle.length() > 0) ? partTitle : "";
                    part.setTitle(partTitle);

                    // rubric as Part Instructions
                    if (rubricElement != null) {
                        String partInstructions = normalizeElementBody(contentsDOM, rubricElement);
                        partInstructions = processInstructionsEmbedMedia(unzipBackUpLocation.concat(baseName),
                                context, partInstructions, new ArrayList<String>());
                        part.getPresentation().setText(partInstructions);
                    }

                    // orderingShuffle is true then randomize the order of questions
                    String orderingShuffle = findPartOrderingShuffle(sectionElement);
                    part.setRandomize(new Boolean(orderingShuffle));

                    boolean randomDraw = false;
                    int totalPartQuestions = 0;
                    List<Element> requiredQuestions = new ArrayList<Element>();

                    XPath randomSelectionPath = new DOMXPath(
                            ".//assessmentSection[@identifier='" + sectionIdentifier + "']/selection");
                    Element selection = (Element) randomSelectionPath.selectSingleNode(contentsDOM);

                    // look for selection if yes then there is random draw
                    if (selection != null) {
                        randomDraw = true;
                        randomAccess = randomAccess || randomDraw;

                        // <selection select="2"/>
                        String randomSelectionCount = selection.getAttribute("select");
                        totalPartQuestions = Integer.parseInt(randomSelectionCount);

                        // select assessmentItemRef with required clause and add as question pick
                        XPath assessmentItemRefPath = new DOMXPath(".//assessmentSection[@identifier='"
                                + sectionIdentifier + "']/assessmentItemRef[@required='true']");
                        requiredQuestions = assessmentItemRefPath.selectNodes(contentsDOM);
                    } else {
                        // add questions to assessment
                        XPath assessmentItemRefPath = new DOMXPath(
                                ".//assessmentSection[@identifier='" + sectionIdentifier + "']/assessmentItemRef");
                        requiredQuestions = assessmentItemRefPath.selectNodes(contentsDOM);
                    } // else end

                    for (Element questionElement : requiredQuestions) {
                        Question question = null;
                        String identifer = questionElement.getAttribute("identifier");
                        String href = questionElement.getAttribute("href");
                        question = getQuestionfromAllQuestions(identifer,
                                unzipBackUpLocation.concat(baseName) + File.separator + href, allQuestions);
                        if (question == null)
                            continue;

                        // all likert questions then survey
                        if ("mneme:LikertScale".equals(question.getType()) || question.getIsSurvey()) {
                            if (allLikert == null)
                                allLikert = new Boolean(true);
                            else
                                allLikert = new Boolean(allLikert.booleanValue() && true);
                        } else {
                            if (allLikert == null)
                                allLikert = new Boolean(false);
                            else
                                allLikert = new Boolean(allLikert.booleanValue() && false);
                        }

                        if ("mneme:Essay".equals(question.getType()))
                            autoRelease = new Boolean(false);

                        // score
                        String points = "1";
                        if (allQuestionPoints.containsKey(identifer))
                            points = allQuestionPoints.get(identifer);
                        QuestionPick questionPick = part.addPickDetail(question);
                        questionPick.setPoints(Float.parseFloat(points));
                    }

                    // add Random detail
                    int randomCount = totalPartQuestions - requiredQuestions.size();
                    part = buildRandomDrawPart(randomDraw, randomCount, context, sectionIdentifier,
                            unzipBackUpLocation.concat(baseName), contentsDOM, part, allPools, allQuestions,
                            allQuestionPoints);

                } // section element for
            } // part element for
        }

        // if all likert questions and not already a survey then mark it as survey
        if (testType != AssessmentType.survey && (allLikert != null && allLikert.booleanValue() == true))
            test.setType(AssessmentType.survey);

        // random access
        if (randomAccess)
            test.setRandomAccess(new Boolean(true));
        else
            test.setRandomAccess(new Boolean(false));

        // rights information
        //      if (rights != null) test.getPresentation().setText(test.getPresentation().getText().concat(rights));

        // auto release of grading
        if (autoRelease != null)
            test.getGrading().setAutoRelease(autoRelease);

        // send to gradebook
        if (test.getType() != AssessmentType.survey)
            test.getGrading().setGradebookIntegration(Boolean.TRUE);

        assessmentService.saveAssessment(test);
        return test;
    }

    /**
     * read all outcome declarations in a hashmap.
     * @param contentsDOM
     * @return
     */
    private HashMap<String, String> processOutcomeDeclaration(Document contentsDOM) {
        HashMap<String, String> settings = new HashMap<String, String>();
        try {
            XPath outcomes = new DOMXPath("/assessmentTest/outcomeDeclaration");
            List<Element> outcomeElements = outcomes.selectNodes(contentsDOM);

            for (Element e : outcomeElements) {
                String key = e.getAttribute("identifier");
                String value = e.getTextContent();
                if (key == null || key.length() == 0 || value == null || value.trim().length() == 0)
                    continue;
                settings.put(key, value.trim());
            }
        } catch (Exception ex) {
        }
        return settings;
    }

    /**
     * 
     * @param partElements
     * @param test
     * @return
     * @throws Exception
     */
    private Assessment processSettingsfromItemSessionElement(List<Element> partElements, Assessment test)
            throws Exception {
        String tries = "";

        if (partElements != null && partElements.size() > 0) {
            Element partElement = partElements.get(0);
            XPath itemSessionControlPath = new DOMXPath("itemSessionControl");
            Element itemSessionControlElement = (Element) itemSessionControlPath.selectSingleNode(partElement);

            if (itemSessionControlElement != null) {
                tries = (itemSessionControlElement.getAttribute("maxAttempts") != null)
                        ? itemSessionControlElement.getAttribute("maxAttempts")
                        : "";
                // tries
                if (!tries.equals("")) {
                    tries = tries.trim();
                    test.setHasTriesLimit(true);
                    test.setTries(new Integer(tries));
                }

                // review options
                if ("false".equalsIgnoreCase(itemSessionControlElement.getAttribute("allowReview").trim()))
                    test.getReview().setTiming(ReviewTiming.never);
                else
                    test.getReview().setTiming(ReviewTiming.submitted);

                // show hints
                if (!("").equals(itemSessionControlElement.getAttribute("showFeedback").trim()))
                    test.setShowHints(new Boolean(itemSessionControlElement.getAttribute("showFeedback")));

                // show model answer
                if (!("").equals(itemSessionControlElement.getAttribute("showSolution").trim()))
                    test.setShowModelAnswer(new Boolean(itemSessionControlElement.getAttribute("showSolution")));
            }
        }
        return test;
    }

    /**
     * Assign settings based on outcome declaration
     * @param settings
     * @param test
     * @return
     */
    private Assessment processSettingsfromOutcomeDeclaration(HashMap<String, String> settings, Assessment test) {
        if (settings.size() == 0)
            return test;
        // passing percentage
        if (settings.containsKey("PASSFACTOR")) {
            test.setMinScore(new Integer(settings.get("PASSFACTOR")));
            test.setMinScoreSet(new Boolean(true));
        }

        //review options
        if (settings.containsKey("ReviewShowSummary"))
            test.getReview().setShowSummary(new Boolean(true));

        if (settings.containsKey("ReviewShowFeedback")) {
            M_log.debug("review feedback true");
            test.getReview().setShowFeedback(new Boolean(true));
        }

        if (settings.containsKey("ReviewCorrectAnswer")) {
            String correctAnswer = settings.get("ReviewCorrectAnswer");
            if ("incorrect_only".equalsIgnoreCase(correctAnswer))
                test.getReview().setShowCorrectAnswer(ReviewShowCorrect.incorrect_only);
            else if ("correct_only".equalsIgnoreCase(correctAnswer))
                test.getReview().setShowCorrectAnswer(ReviewShowCorrect.correct_only);
            else if ("no".equalsIgnoreCase(correctAnswer))
                test.getReview().setShowCorrectAnswer(ReviewShowCorrect.no);
        }

        // AnonymousGrading
        if (settings.containsKey("AnonymousGrading"))
            test.getGrading().setAnonymous(new Boolean(settings.get("AnonymousGrading")));

        // layout
        if (settings.containsKey("QuestionLayout")) {
            String layout = settings.get("QuestionLayout");
            if ("part".equalsIgnoreCase(layout))
                test.setQuestionGrouping(QuestionGrouping.part);
            else if ("assessment".equalsIgnoreCase(layout))
                test.setQuestionGrouping(QuestionGrouping.assessment);
            else
                test.setQuestionGrouping(QuestionGrouping.question);
        } else
            test.setQuestionGrouping(QuestionGrouping.question);

        // pledge
        if (settings.containsKey("HonorPledge"))
            test.setRequireHonorPledge(new Boolean(settings.get("HonorPledge")));

        // ShuffleChoicesOverride
        if (settings.containsKey("ShuffleChoicesOverride"))
            test.setShuffleChoicesOverride(new Boolean(true));

        //open date
        if (settings.containsKey("OpenDate"))
            test.getDates().setOpenDate(getDateFromString(settings.get("OpenDate")));

        if (settings.containsKey("HideUntilOpen"))
            test.getDates().setHideUntilOpen(new Boolean(settings.get("HideUntilOpen")));

        if (settings.containsKey("DueDate"))
            test.getDates().setDueDate(getDateFromString(settings.get("DueDate")));

        if (settings.containsKey("AcceptUntil"))
            test.getDates().setAcceptUntilDate(getDateFromString(settings.get("AcceptUntil")));

        return test;
    }

    /**
     * Find Attachments and bring them in. Resource file elements which are not in embedMedia list are attachments.
     * 
     * @param resourceItem
     * @param context
     * @param question
     * @param embedMedia
     * @return
     */
    private Question processQuestionAttachments(Element resourceItem, String context, Question question,
            List<String> embedMedia, String unzipLocation) {
        if (question == null)
            return question;
        List<Reference> currAttachments = question.getPresentation().getAttachments();

        NodeList attachments = resourceItem.getElementsByTagName("file");
        for (int i = 0; i < attachments.getLength(); i++) {
            Element attach = (Element) attachments.item(i);
            String hrefLocation = attach.getAttribute("href");
            if (!findInEmbedMedia(hrefLocation, embedMedia)) {
                Reference attachRef = transferEmbeddedData(unzipLocation + File.separator + hrefLocation,
                        hrefLocation, context);
                if (attachRef == null)
                    return question;
                if (currAttachments == null)
                    currAttachments = new ArrayList<Reference>();
                currAttachments.add(attachRef);
            }
        }
        question.getPresentation().setAttachments(currAttachments);
        return question;
    }

    /**
     * Read Question.xml file and process it to create a question.
     * 
     * @param pool
     * @param context
     * @param doc
     * @param resourceItem
     * @param allQuestionPoints
     * @param pointsAvg
     * @param unzipBackUpLocation
     * @param fileName
     * @param baseName
     * @return
     * @throws Exception
     */
    private Question processQuestionItemFile(HashMap<String, Pool> allPools, HashMap<String, Question> allQuestions,
            String context, Document doc, Element resourceItem, HashMap<String, String> allQuestionPoints,
            Average pointsAvg, String unzipBackUpLocation, String fileName, String baseName) throws Exception {
        Document contentsDOM = Xml.readDocument(unzipBackUpLocation + File.separator + fileName);
        if (contentsDOM == null)
            return null;

        String interaction = null;

        XPath textPath = new DOMXPath("/assessmentItem/itemBody");
        Element itemBody = (Element) textPath.selectSingleNode(contentsDOM);
        boolean likertClass = (itemBody != null && "likert".equals(itemBody.getAttribute("class"))) ? true : false;
        boolean surveyType = findQuestionSurvey(resourceItem);

        // find question type...very important step
        if (resourceItem != null)
            interaction = findInteraction(contentsDOM);

        // create question text and collect all embed media and question.xml file
        ArrayList<String> embedMedia = new ArrayList<String>();
        String text = processQuestionText(context, contentsDOM, itemBody, embedMedia, interaction,
                unzipBackUpLocation.concat(baseName));
        embedMedia.add(fileName);

        String questionIdentifier = resourceItem.getAttribute("identifier");
        int poolIndex = questionIdentifier.indexOf(":");
        String poolId = "defaultPool";

        if (poolIndex > -1)
            poolId = questionIdentifier.substring(0, poolIndex);

        Pool pool = allPools.get(poolId);
        if (pool == null)
            pool = allPools.get("defaultPool");

        // create the question
        Question question = null;
        if (questionIdentifier != null && allQuestions.containsKey(questionIdentifier))
            question = allQuestions.get(questionIdentifier);

        if (likertClass)
            question = buildLikertScaleChoice(pool, text, interaction, contentsDOM);
        if ("".equals(interaction) && question == null)
            question = buildTask(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildEssay(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildMatchforManyMultipleChoice(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildTrueFalse(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildMultipleChoice(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildMatch(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildFillBlanks(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildFillBlankforGapText(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildFillBlankforOrdered(pool, text, interaction, contentsDOM);
        if (question == null)
            question = buildMultipleChoiceforUnsupported(pool, text, unzipBackUpLocation.concat(baseName), context,
                    contentsDOM);

        if (question == null)
            return null;

        // save
        if (question != null) {
            // read file elements and if other than embed media, bring in as attachments
            question = processQuestionAttachments(resourceItem, context, question, embedMedia, unzipBackUpLocation);

            // question description
            String description = findDescription(resourceItem);

            // explain reason
            Set<String> allInteractions = findAllInteraction(contentsDOM);
            if (allInteractions.size() > 1 && allInteractions.contains("textEntryInteraction")) {
                question.setExplainReason(new Boolean(true));
            }

            // hints mostly from identifier as correct otherwise from the correctresponse Identifier
            String hints = getQuestionHints(contentsDOM);
            if (question.getHints() != null)
                hints = question.getHints().concat(hints);
            question.setHints(hints);

            // feedback
            XPath modalFeedbackPath = new DOMXPath("/assessmentItem/modalFeedback");
            String feedback = modalFeedbackPath.stringValueOf(contentsDOM);
            question.setFeedback(feedback);

            // points ...some packages have que_score or score1 or SCORE or MAXSCORE or multiple of these records
            XPath outcomePath = new DOMXPath(
                    "/assessmentItem/outcomeDeclaration[contains(@identifier,'SCORE')] | /assessmentItem/outcomeDeclaration[contains(@identifier,'score')]");
            List<Element> scores = outcomePath.selectNodes(contentsDOM);

            String pointsValue = "";
            for (Element score : scores) {
                String p = score.getTextContent().trim();
                if (!"".equals(p) && !"0.0".equals(p))
                    pointsValue = p;
            }

            if (pointsValue != null && pointsValue != "") {
                if (questionIdentifier != null)
                    allQuestionPoints.put(questionIdentifier, pointsValue);
            }

            // survey
            if (likertClass || surveyType)
                question.setIsSurvey(true);
            else
                question.setIsSurvey(false);

            question.getTypeSpecificQuestion().consolidate("");
            this.questionService.saveQuestion(question);
        }

        if (questionIdentifier != null && !allQuestions.containsKey(questionIdentifier))
            allQuestions.put(questionIdentifier, question);
        return question;
    }

    /**
     * 
     * @param file
     * @param embedMedia
     * @return
     */
    private boolean findInEmbedMedia(String file, List<String> embedMedia) {
        boolean found = false;
        file = file.replace("\\", "/");
        if (file.lastIndexOf("/") != -1)
            file = file.substring(file.lastIndexOf("/")).trim();

        for (String m : embedMedia) {
            m = m.replace("\\", "/");
            if (m.lastIndexOf("/") != -1)
                m = m.substring(m.lastIndexOf("/")).trim();
            if (m.equalsIgnoreCase(file)) {
                found = true;
                break;
            }
        }
        return found;
    }

    /**
     * Find the Question type by identifying Interaction name.
     * 
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private String findInteraction(Document contentsDOM) throws Exception {
        String interaction = null;
        Element interactionElement = null;
        XPath interactionTypePath = new DOMXPath(
                "/assessmentItem/itemBody//*[contains(local-name(),'Interaction')]");
        interaction = ((interactionElement = (Element) interactionTypePath.selectSingleNode(contentsDOM)) != null)
                ? interactionElement.getNodeName()
                : "";
        return interaction;
    }

    /**
     * Find all interaction to check explain reason. If more than one kind of interaction like inlinechoice and textEntry then textEntry shows explain reason.
     * 
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Set<String> findAllInteraction(Document contentsDOM) throws Exception {
        List<Element> interactionElements = new ArrayList<Element>();
        Set<String> allTypesInteraction = new HashSet<String>();

        XPath interactionTypePath = new DOMXPath(
                "/assessmentItem/itemBody//*[contains(local-name(),'Interaction')]");
        interactionElements = interactionTypePath.selectNodes(contentsDOM);

        for (Element i : interactionElements)
            allTypesInteraction.add(i.getNodeName());
        return allTypesInteraction;
    }

    /**
     * Pool description from metadata element
     * 
     * @param doc
     * @return
     */
    private String findPoolDescription(Document doc) {
        String poolDesc = "";
        try {
            Element manifestElement = doc.getDocumentElement();
            XPath metadataPath = new DOMXPath(".//*[contains(local-name(),'metadata')]");
            Element metadataElement = (Element) metadataPath.selectSingleNode(manifestElement);

            XPath poolDescriptionPath = new DOMXPath(".//*[contains(local-name(),'description')]");
            Element descElement = (Element) poolDescriptionPath.selectSingleNode(metadataElement);
            poolDesc = descElement.getTextContent().trim();
        } catch (Exception ex) {
            M_log.debug("pool title exception" + ex.getMessage());
        }
        return poolDesc;
    }

    private String findRightsInformation(Document doc) {
        String rights = "";
        try {
            Element manifestElement = doc.getDocumentElement();
            XPath metadataPath = new DOMXPath(".//*[contains(local-name(),'metadata')]");
            Element metadataElement = (Element) metadataPath.selectSingleNode(manifestElement);

            XPath rightsPath = new DOMXPath(".//*[contains(local-name(),'rights')]");
            Element rightElement = (Element) rightsPath.selectSingleNode(metadataElement);

            rights = rightElement.getTextContent().trim();
        } catch (Exception ex) {
            M_log.debug("pool title exception" + ex.getMessage());
        }
        return rights;
    }

    /**
     * Find exisiting same titles.If found append (copy X)
     * @param context
     * @param title
     * @return
     */
    private String findAssessmentTitle(String context, String title) {
        String testTitle = "Untitled Assessment";

        try {
            if (title != null && title.length() > 0)
                testTitle = title;

            List<Assessment> assessments = assessmentService.getContextAssessments(context, null, Boolean.FALSE);
            if (assessments == null || assessments.size() == 0)
                return testTitle;

            // if title exists then add (copy x)
            int sameTitle = 0;
            for (Assessment a : assessments) {
                String readTitle = a.getTitle();
                if (readTitle.contains(testTitle)) {
                    if (readTitle.equals(testTitle))
                        sameTitle++;
                    else {
                        readTitle = readTitle.replace(testTitle, "");
                        if (readTitle.contains("(copy"))
                            sameTitle++;
                    }
                }
            }

            if (sameTitle > 0)
                testTitle = testTitle + " (copy" + sameTitle + ")";

        } catch (Exception ex) {
            M_log.debug("assessment title exception" + ex.getMessage());
        }
        return testTitle;
    }

    /**
     * Find the title for pool
     * 
     * @param doc
     * @return
     */
    private String findPoolTitle(String context, String title, Document doc) {
        String poolTitle = "defaultPool";

        try {
            if ("".equals(title)) {
                Element manifestElement = doc.getDocumentElement();
                XPath metadataPath = new DOMXPath(".//*[contains(local-name(),'metadata')]");
                Element metadataElement = (Element) metadataPath.selectSingleNode(manifestElement);
                if (metadataElement == null)
                    return poolTitle;

                XPath poolTitlePath = new DOMXPath(".//*[contains(local-name(),'title')]");
                Element titleElement = (Element) poolTitlePath.selectSingleNode(metadataElement);
                if (titleElement != null && titleElement.getTextContent().length() > 0)
                    poolTitle = titleElement.getTextContent().trim();
                if (poolTitle.length() > 255)
                    poolTitle = poolTitle.substring(0, 245);
            } else
                poolTitle = title;

            List<Pool> pools = poolService.getAllPools(context);
            if (pools == null || pools.size() == 0)
                return poolTitle;

            // if title exists then add (copy x)
            int sameTitle = 0;
            for (Pool p : pools) {
                String readTitle = p.getTitle();
                if (readTitle.contains(poolTitle)) {
                    if (readTitle.equals(poolTitle))
                        sameTitle++;
                    else {
                        readTitle = readTitle.replace(poolTitle, "");
                        if (readTitle.contains("(copy"))
                            sameTitle++;
                    }
                }
            }

            if (sameTitle > 0)
                poolTitle = poolTitle + " (copy" + sameTitle + ")";

        } catch (Exception ex) {
            M_log.debug("pool title exception" + ex.getMessage());
        }
        return poolTitle;
    }

    /**
     * 
     * @param testItem
     * @return
     */
    private String findDescription(Element testItem) {
        try {
            XPath descPath = new DOMXPath(".//*[contains(local-name(),'description')]");
            Element descElement = (Element) descPath.selectSingleNode(testItem);
            if (descElement != null) {
                // return descElement.getTextContent().trim();
                descPath = new DOMXPath(".//*[contains(local-name(),'langstring')]");
                descElement = (Element) descPath.selectSingleNode(descElement);
                if (descElement != null)
                    return normalizeElementBody(descElement.getOwnerDocument(), descElement);
            }
        } catch (Exception e) {
        }
        return "";
    }

    /**
     * 
     * @param testItem
     * @return
     */
    private AssessmentType findAssessmentType(Element testItem) {
        AssessmentType testType = AssessmentType.test;
        try {
            XPath typePath = new DOMXPath(".//*[contains(local-name(),'identifier')]");
            Element typeElement = (Element) typePath.selectSingleNode(testItem);
            if (typeElement != null) {
                String type = typeElement.getTextContent();
                if ("assignment".equals(type))
                    testType = AssessmentType.assignment;
                if ("survey".equals(type))
                    testType = AssessmentType.survey;
            }
        } catch (Exception e) {
        }
        return testType;
    }

    /**
     * 
     * @param sectionElement
     * @return
     */
    private String findPartOrderingShuffle(Element sectionElement) {
        NodeList orderingPath = sectionElement.getElementsByTagName("ordering");
        if (orderingPath != null && orderingPath.getLength() > 0) {
            Element orderingElement = (Element) orderingPath.item(0);
            return orderingElement.getAttribute("shuffle");
        } else
            return "false";
    }

    /**
     * Find if Question is of survey type
     * @param testItem
     * @return
     */
    private boolean findQuestionSurvey(Element testItem) {
        if (testItem == null)
            return false;
        try {
            XPath typePath = new DOMXPath(".//*[contains(local-name(),'identifier')]");
            Element typeElement = (Element) typePath.selectSingleNode(testItem);
            if (typeElement != null) {
                String type = typeElement.getTextContent();
                if (type != null && "survey".equals(type.trim()))
                    return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

    /**
     * return answer text if answerText is true. Multiple choice needs id and fill blanks need text.
     * @param response
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private String getCorrectResponse(Element value, Document contentsDOM, boolean answerTextFlag)
            throws Exception {
        if (value == null)
            return "";

        Element response = (Element) value.getParentNode().getParentNode();
        String baseType = response.getAttribute("baseType");
        String responseIdentifier = response.getAttribute("identifier");

        // if basetype is string then value is the answer. If identifier then fetch the element with this identifier for answer.
        String answerText = "";
        if (("string").equalsIgnoreCase(baseType) && value != null) {
            answerText = value.getTextContent();
        } else if ("identifier".equalsIgnoreCase(baseType)) {
            // /assessmentItem/responseDeclaration[1]/correctResponse[1]/value[1] has identifier
            answerText = value.getTextContent();

            if (answerTextFlag) {
                // sometimes answertext is also identifier
                XPath answerPath = new DOMXPath(".//*[@identifier='" + answerText + "']");
                String checkIdentifier = answerPath.stringValueOf(contentsDOM);
                if (checkIdentifier != null && checkIdentifier != "" && checkIdentifier.length() != 0)
                    answerText = checkIdentifier;
            }
        } else
            answerText = value.getTextContent();

        return answerText;
    }

    /**
     * 
     * @param contentsDOM
     * @param responseIdentifier
     * @param answerTextFlag
     * @return
     */
    private List<String> getCorrectResponsefromResponseProcessing(Document contentsDOM, String responseIdentifier,
            boolean answerTextFlag) {
        ArrayList<String> correctResponses = new ArrayList<String>();
        try {
            XPath answerPath = new DOMXPath(
                    "/assessmentItem/responseProcessing/responseCondition//variable[@identifier='"
                            + responseIdentifier + "']");
            List<Element> answerIdElements = answerPath.selectNodes(contentsDOM);

            for (Element answerIdElement : answerIdElements) {
                // match element
                Element matchElement = (Element) answerIdElement.getParentNode();
                answerPath = new DOMXPath(".//*[@baseType='identifier']");
                answerIdElement = (Element) answerPath.selectSingleNode(matchElement);
                String answerIdentifier = answerIdElement.getTextContent();

                // setoutcome element
                Element responseConditionElement = (Element) matchElement.getParentNode();
                XPath outcomePath = new DOMXPath(
                        ".//setOutcomeValue[contains(@identifier,'SCORE')] | .//setOutcomeValue[contains(@identifier,'score')] | .//setOutcomeValue[contains(@identifier,'Correct')]");
                List<Element> scoreElements = outcomePath.selectNodes(responseConditionElement);
                String scoreValue = "";
                for (Element score : scoreElements) {
                    scoreValue = score.getTextContent();
                    if (scoreValue == null || scoreValue.equals("0") || scoreValue.equals("0.0")
                            || scoreValue.startsWith("-"))
                        continue;
                }

                if (scoreValue == null || scoreValue.equals("") || scoreValue.equals("0")
                        || scoreValue.equals("0.0") || scoreValue.startsWith("-"))
                    continue;
                if (!answerTextFlag)
                    correctResponses.add(answerIdentifier);
                else {
                    // get answer text
                    answerPath = new DOMXPath(".//*[@identifier='" + answerIdentifier + "']");
                    correctResponses.add(answerPath.stringValueOf(contentsDOM));
                }
            }
        } catch (Exception e) {
            // do nothing
        }
        return correctResponses;
    }

    /**
     * 
     * @param dateStr
     * @return
     */
    public Date getDateFromString(String dateStr) {
        Date date = null;

        try {
            if (dateStr == null || dateStr.length() == 0 || dateStr.equals(""))
                return null;
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            date = sdf.parse(dateStr);
        } catch (ParseException e) {
            return null;
        }
        return date;
    }

    /**
     * 
     * @param identifer
     * @param href
     * @param allQuestions
     * @return
     */
    private Question getQuestionfromAllQuestions(String identifer, String href,
            HashMap<String, Question> allQuestions) {
        if (allQuestions == null)
            return null;

        Question question = null;
        if (allQuestions.containsKey(identifer))
            question = allQuestions.get(identifer);
        if (question == null) {
            // Read Question.Xml file
            Document contentsDOM = Xml.readDocument(href);
            if (contentsDOM == null)
                return null;

            Element rootQuestion = contentsDOM.getDocumentElement();
            String questionIdentifier = rootQuestion.getAttribute("identifier");
            if (allQuestions.containsKey(questionIdentifier))
                question = allQuestions.get(questionIdentifier);
        }
        return question;
    }

    /**
     * Get the correct answer hints
     * 
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private String getQuestionHints(Document contentsDOM) throws Exception {
        String hints = "";
        XPath hintsPath = new DOMXPath(
                ".//feedbackInline[@identifier='Correct'] | .//feedbackBlock[@identifier='Correct']");
        hints = hintsPath.stringValueOf(contentsDOM);

        XPath feedbackIdDeterminePath = new DOMXPath(
                "/assessmentItem/responseProcessing/setOutcomeValue[@identifier='FEEDBACK']/variable[@identifier='RESPONSE']");
        String feedbackIdentifier = feedbackIdDeterminePath.stringValueOf(contentsDOM);
        if (feedbackIdentifier != null && feedbackIdentifier.length() > 0) {
            XPath correctAnswerPath = new DOMXPath("/assessmentItem/responseDeclaration/correctResponse/value");
            String correctAnswerIdentifier = correctAnswerPath.stringValueOf(contentsDOM);
            if (correctAnswerIdentifier != null && correctAnswerIdentifier.length() > 0) {
                hintsPath = new DOMXPath(".//feedbackInline[@identifier='" + correctAnswerIdentifier + "']");
                hints = hintsPath.stringValueOf(contentsDOM);
            }
        } else {
            feedbackIdDeterminePath = new DOMXPath(
                    "/assessmentItem/responseProcessing/responseIf/setOutcomeValue[@identifier='FEEDBACK']/baseValue");
            feedbackIdentifier = feedbackIdDeterminePath.stringValueOf(contentsDOM);
            if (feedbackIdentifier != null && feedbackIdentifier.length() > 0) {
                hintsPath = new DOMXPath(".//feedbackInline[@identifier='" + feedbackIdentifier + "']");
                hints = hintsPath.stringValueOf(contentsDOM);
            }
        }
        return hints;
    }

    /**
     * BringIn embed media from instrcutions
     * 
     * @param unzipBackUpLocation
     * @param context
     * @param text
     * @param embedMedia
     * @return
     * @throws Exception
     */
    private String processInstructionsEmbedMedia(String unzipBackUpLocation, String context, String text,
            List<String> embedMedia) throws Exception {
        Pattern p = Pattern.compile("(src|href)[\\s]*=[\\s]*\"([^#\"]*)([#\"])",
                Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
        StringBuffer sb = new StringBuffer();
        Matcher m = p.matcher(text);

        while (m.find()) {
            if (m.groupCount() != 3)
                continue;
            String fileName = m.group(2);
            if (embedMedia != null)
                embedMedia.add(fileName);
            // add to collection
            Reference ref = transferEmbeddedData(unzipBackUpLocation + File.separator + fileName, fileName,
                    context);
            if (ref == null)
                continue;

            // replace with collection Url
            String ref_id = attachmentService.processMnemeUrls(ref.getId());
            m.appendReplacement(sb, m.group(1) + "= \"" + ref_id + "\"");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    /**
     * Find embed media from question text and bring it in 
     * @param itemBodyElement
     * @param unzipBackUpLocation
     * @param context
     * @param embedMedia
     * @throws Exception
     */
    private void processEmbedMedia(Element itemBodyElement, String unzipBackUpLocation, String context,
            List<String> embedMedia) throws Exception {
        List<Element> objects = new ArrayList<Element>();

        if (itemBodyElement.getNodeName().equals("object") || itemBodyElement.getNodeName().equals("img")
                || itemBodyElement.getNodeName().equals("a")) {
            objects.add(itemBodyElement);
        } else {
            XPath objectPath = new DOMXPath(".//object|.//img|.//a");
            objects = objectPath.selectNodes(itemBodyElement);
        }

        if (objects == null || objects.size() == 0)
            return;

        for (Element obj : objects) {
            // find fileName
            String fileName = null;
            if (obj.getNodeName().equals("object"))
                fileName = obj.getAttribute("data");
            else if (obj.getNodeName().equals("img"))
                fileName = obj.getAttribute("src");
            else if (obj.getNodeName().equals("a"))
                fileName = obj.getAttribute("href");

            // add to collection
            Reference ref = transferEmbeddedData(unzipBackUpLocation + File.separator + fileName, fileName,
                    context);
            if (ref == null)
                continue;

            // replace with collection Url
            String ref_id = attachmentService.processMnemeUrls(ref.getId());
            if (ref_id != null && obj.getNodeName().equals("object"))
                obj.setAttribute("data", ref_id);
            else if (ref_id != null && obj.getNodeName().equals("img"))
                obj.setAttribute("src", ref_id);
            else if (ref_id != null && obj.getNodeName().equals("a"))
                obj.setAttribute("href", ref_id);

            if (embedMedia != null)
                embedMedia.add(fileName);
        }

    }

    /**
     * Build the question presentation text. Its important to remove inline feedback text and interaction text while creating this text.
     * 
     * @param contentsDOM
     * @param itemBodyElement
     * @param interaction
     * @return
     * @throws Exception
     */
    private String processQuestionText(String context, Document contentsDOM, Element itemElement,
            List<String> embedMedia, String interaction, String unzipBackUpLocation) throws Exception {
        String text = "";

        if (itemElement == null)
            return text;

        boolean fillBlanks = ("inlineChoiceInteraction".equalsIgnoreCase(interaction)
                || "textEntryInteraction".equalsIgnoreCase(interaction)) ? true : false;

        Element itemBodyElement = (Element) itemElement.cloneNode(true);
        List<Element> interactionElements = new ArrayList<Element>();

        // embed images
        processEmbedMedia(itemBodyElement, unzipBackUpLocation, context, embedMedia);

        itemBodyElement = removeFeedback(itemBodyElement);

        if (interaction == null || "".equals(interaction))
            return normalizeElementBody(contentsDOM, itemBodyElement);

        XPath interactionPath = new DOMXPath(".//" + interaction);
        interactionElements = interactionPath.selectNodes(itemBodyElement);

        if (interactionElements == null || interactionElements.size() == 0)
            return normalizeElementBody(contentsDOM, itemBodyElement);

        // add prompt or block quote inside interaction to question text

        for (Element i : interactionElements) {
            String additionalText = "";
            XPath promptPath = new DOMXPath("prompt|blockquote");
            List<Element> prompts = promptPath.selectNodes(i);
            for (Element prompt : prompts) {
                additionalText = additionalText.concat(normalizeElementBody(contentsDOM, prompt));
            }

            if (fillBlanks) {
                Element replaceDivElement = contentsDOM.createElement("div");
                i.setTextContent(additionalText + "{}");
                itemBodyElement.appendChild(replaceDivElement);
            } else
                i.setTextContent(additionalText);
        }

        // normalize all child nodes and create a string
        String content = normalizeElementBody(contentsDOM, itemBodyElement);
        return content;
    }

    /**
     * read each node and write it as is.
     * 
     * @param doc
     * @param itemBodyElement
     * @return
     */
    private String normalizeElementBody(Document doc, Element itemBodyElement) {
        if (itemBodyElement == null)
            return "";

        try {
            doc.getDocumentElement().normalize();

            DocumentTraversal traversal = (DocumentTraversal) doc;
            NodeIterator iterator = traversal.createNodeIterator(itemBodyElement, NodeFilter.SHOW_ELEMENT, null,
                    true);
            for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) {
                String tagname = ((Element) n).getTagName();

                if (tagname.equalsIgnoreCase(itemBodyElement.getTagName())) {
                    StringBuilder textContent = new StringBuilder();

                    String nodeContent = getAllLevelsTextContent(n, textContent, false, null, null, null);
                    return nodeContent;
                }
            }
        } catch (Exception e) {
            return itemBodyElement.getTextContent();
        }
        return itemBodyElement.getTextContent();
    }

    /**
     * For unsupported question , get the whole text along with options.
     * @param doc
     * @param itemBodyElement
     * @param wholeText
     * @param unzipLocation
     * @param context
     * @param embedMedia
     * @return
     */
    private String normalizeItemBodyElement(Document doc, Element itemBodyElement, boolean wholeText,
            String unzipLocation, String context, List<String> embedMedia) {
        if (itemBodyElement == null)
            return "";

        try {
            doc.getDocumentElement().normalize();

            DocumentTraversal traversal = (DocumentTraversal) doc;
            NodeIterator iterator = traversal.createNodeIterator(itemBodyElement, NodeFilter.SHOW_ELEMENT, null,
                    true);
            for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) {
                String tagname = ((Element) n).getTagName();

                if (tagname.equalsIgnoreCase(itemBodyElement.getTagName())) {
                    StringBuilder textContent = new StringBuilder();

                    String nodeContent = getAllLevelsTextContent(n, textContent, wholeText, unzipLocation, context,
                            embedMedia);
                    return nodeContent;
                }
            }
        } catch (Exception e) {
            return itemBodyElement.getTextContent();
        }
        return itemBodyElement.getTextContent();
    }

    /**
     * 
     * @param node
     * @param textContent
     * @return
     */
    private String getAllLevelsTextContent(Node node, StringBuilder textContent, boolean wholeText,
            String unzipLocation, String context, List<String> embedMedia) {
        NodeList list = node.getChildNodes();

        for (int i = 0; i < list.getLength(); ++i) {
            Node child = list.item(i);

            String childTagName = child.getNodeName();

            if (child.getNodeType() == Node.TEXT_NODE) {
                textContent.append(child.getTextContent());

            }
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                if (wholeText && ("img".equalsIgnoreCase(childTagName) || "a".equalsIgnoreCase(childTagName)
                        || "object".equalsIgnoreCase(childTagName))) {
                    try {
                        processEmbedMedia((Element) child, unzipLocation, context, embedMedia);
                    } catch (Exception e) {
                        // do nothing
                    }
                }

                if (!wholeText && child.getNodeName().contains("Interaction")) {
                    if (child.getTextContent() != null)
                        textContent.append(child.getTextContent());
                } else if (child.getNodeName().contains("feedback")) {
                    // do nothing skip it
                } else if (child.getNodeName().contains("printedVariable")) {
                    Element printedVariableTemplate = null;
                    Element currNodeElement = (Element) child;
                    String id = currNodeElement.getAttribute("identifier");
                    try {
                        XPath printedPath = new DOMXPath(
                                ".//setTemplateValue[@identifier='" + id + "']//randomInteger");
                        printedVariableTemplate = (Element) printedPath.selectSingleNode(child.getOwnerDocument());
                    } catch (Exception e) {
                        printedVariableTemplate = null;
                    }
                    if (printedVariableTemplate != null)
                        textContent.append(this.messages.getString("import_qti2_printedVariable_text")
                                + printedVariableTemplate.getAttribute("min") + " - "
                                + printedVariableTemplate.getAttribute("max") + " "
                                + this.messages.getString("import_qti2_printedVariable_text2"));
                } else {
                    textContent.append("<" + child.getNodeName());

                    if (child.hasAttributes()) {
                        NamedNodeMap attrs = child.getAttributes();
                        for (int k = 0; k < attrs.getLength(); k++) {
                            Node attr = attrs.item(k);
                            textContent.append(" " + attr.getNodeName() + " = \"" + attr.getTextContent() + "\" ");
                        }
                    }
                    textContent.append(">");

                    getAllLevelsTextContent(child, textContent, wholeText, unzipLocation, context, embedMedia);

                    textContent.append("</" + child.getNodeName() + ">");
                }
            }
        }
        return textContent.toString();
    }

    /**
     * Create essay type question
     * 
     * @param essay
     * @param text
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildEssay(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        String modelAnswer = "";
        SubmissionType setting = SubmissionType.inline;
        List<Element> interactions = null;
        boolean essayType = false;

        // correct answer
        XPath identifierPath = new DOMXPath("/assessmentItem/responseDeclaration/correctResponse/value");
        List<Element> values = identifierPath.selectNodes(contentsDOM);

        if (interactionText != null) {
            XPath interactionPath = new DOMXPath(".//" + interactionText);
            interactions = interactionPath.selectNodes(contentsDOM);
            // check if fill in blanks or essay. If one text entry and no correct response then essay
            if ("textEntryInteraction".equals(interactionText) && interactions.size() == 1 && values.size() == 0)
                essayType = true;
            if ("extendedTextInteraction".equals(interactionText))
                essayType = true;
            if ("uploadInteraction".equals(interactionText))
                essayType = true;
        }

        XPath itemBodyPath = new DOMXPath(".//itemBody");
        Element itemBody = (Element) itemBodyPath.selectSingleNode(contentsDOM);
        //      if (containsPrintedText(itemBody))essayType = true;

        if (interactions == null || !essayType)
            return null;
        // submission type
        XPath responsePath = new DOMXPath("/assessmentItem/responseDeclaration");
        Element responseElement = (Element) responsePath.selectSingleNode(contentsDOM);
        String baseType = (responseElement != null && responseElement.getAttribute("baseType") != null)
                ? responseElement.getAttribute("baseType")
                : null;
        if ("file".equals(baseType))
            setting = SubmissionType.attachments;

        if (values != null) {
            for (Element value : values) {
                modelAnswer = modelAnswer.concat(value.getTextContent());
            }
        }

        Question question = this.questionService.newQuestion(pool, "mneme:Essay");
        EssayQuestionImpl essay = (EssayQuestionImpl) (question.getTypeSpecificQuestion());
        text = text.replace("{}", "");
        question.getPresentation().setText(text);
        essay.setModelAnswer(modelAnswer);
        essay.setSubmissionType(setting);

        question.getTypeSpecificQuestion().consolidate("");

        return question;
    }

    /**
     * Build Fill in the blanks question
     * 
     * @param question
     * @param text
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildFillBlanks(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        if (!(("textEntryInteraction").equalsIgnoreCase(interactionText)
                || ("inlineChoiceInteraction").equalsIgnoreCase(interactionText)))
            return null;

        // choice interaction
        XPath interactionPath = new DOMXPath(".//" + interactionText);
        List<Element> interactions = interactionPath.selectNodes(contentsDOM);

        if (interactions == null || interactions.size() == 0)
            return null;

        // correct answer
        XPath responsePath = new DOMXPath("/assessmentItem/responseDeclaration");
        List<Element> responses = responsePath.selectNodes(contentsDOM);
        Boolean responseTextual = null;

        if (responses != null) {
            for (Element response : responses) {
                String answerText = "";

                XPath valuePath = new DOMXPath(".//correctResponse/value");
                Element value = (Element) valuePath.selectSingleNode(response);

                // if response declaration has correct answer 
                if (value != null) {
                    answerText = getCorrectResponse(value, contentsDOM, true);
                } else {
                    // get correct answers from RequestProcessing
                    List<String> answers = getCorrectResponsefromResponseProcessing(contentsDOM,
                            response.getAttribute("identifier"), true);
                    if (answers != null && answers.size() > 0)
                        answerText = answers.get(0);
                }

                text = text.replaceFirst("\\{\\}", "{" + answerText + "}");
                if (responseTextual == null)
                    responseTextual = checkIfTextualorNumeric(answerText);
                else
                    responseTextual = (responseTextual || checkIfTextualorNumeric(answerText));
            }
        }

        return buildMnemeFillBlanks(pool, responseTextual, text);
    }

    /**
     * Check if answer is textual or numeric
     * 
     * @param check
     *        String to check
     * @return false if numeric
     */
    private boolean checkIfTextualorNumeric(String check) {
        try {
            Float.parseFloat(check.trim());
            return false;
        } catch (NumberFormatException e) {
            return true;
        }
    }

    /**
     * If text contains printedVariable then make it as Essay Question
     * @param itemBody
     * @return
     */
    private boolean containsPrintedText(Element itemBody) {
        boolean printedVariable = false;
        try {
            XPath printedPath = new DOMXPath(".//printedVariable");
            List<Element> variables = printedPath.selectNodes(itemBody);
            if (variables != null && variables.size() > 0)
                printedVariable = true;
        } catch (Exception e) {
            printedVariable = false;
        }
        return printedVariable;
    }

    /**
     * 
     * @param randomDraw
     * @param randomCount
     * @param context
     * @param sectionIdentifier
     * @param contentsDOM
     * @param part
     * @param allPools
     * @param allQuestions
     * @return
     */
    private Part buildRandomDrawPart(boolean randomDraw, int randomCount, String context, String sectionIdentifier,
            String unzipLocation, Document contentsDOM, Part part, HashMap<String, Pool> allPools,
            HashMap<String, Question> allQuestions, HashMap<String, String> allQuestionPoints) {
        try {
            if (!randomDraw)
                return part;

            // find all questions
            XPath assessmentItemRefPath = new DOMXPath(
                    ".//assessmentSection[@identifier='" + sectionIdentifier + "']/assessmentItemRef");
            List<Element> optionalQuestions = assessmentItemRefPath.selectNodes(contentsDOM);

            // create randomPool for these questions
            XPath assessmentSectionPath = new DOMXPath(
                    ".//assessmentSection[@identifier='" + sectionIdentifier + "']");
            Element section = (Element) assessmentSectionPath.selectSingleNode(contentsDOM);
            String randomPoolTitle = (section != null) ? section.getAttribute("title")
                    : part.getId() + "_randomPool";
            Pool randomPool = poolService.newPool(context);
            randomPool.setTitle(findPoolTitle(context, randomPoolTitle, contentsDOM));

            // store draw count for each pool
            HashMap<String, Integer> countDrawQuestions = new HashMap<String, Integer>();
            HashMap<String, String> pointDrawQuestions = new HashMap<String, String>();

            // for each question
            for (Element questionElement : optionalQuestions) {
                Question question = null;

                // if question is required skip it, already added as pickDetail
                String required = questionElement.getAttribute("required");
                if ("true".equalsIgnoreCase(required))
                    continue;

                // check identifier
                String identifer = questionElement.getAttribute("identifier");
                String href = questionElement.getAttribute("href");
                question = getQuestionfromAllQuestions(identifer, unzipLocation + File.separator + href,
                        allQuestions);
                if (question == null)
                    continue;

                // if already in etudes pool - count it
                if (identifer.indexOf(":") != -1) {
                    if (!"false".equalsIgnoreCase(required))
                        continue;
                    String poolId = identifer.substring(0, identifer.indexOf(":"));

                    pointDrawQuestions.put(poolId, allQuestionPoints.get(identifer));

                    if (countDrawQuestions.containsKey(poolId)) {
                        Integer count = countDrawQuestions.get(poolId);
                        countDrawQuestions.put(poolId, count.intValue() + 1);
                    } else
                        countDrawQuestions.put(poolId, new Integer("1"));
                }
                // otherwise copy from defaultpool to randomPool
                else {
                    questionService.moveQuestion(question, randomPool);
                    pointDrawQuestions.put(randomPool.getId(), allQuestionPoints.get(identifer));
                }
            }
            // add draw detail
            if (countDrawQuestions.size() > 0) {
                // for etudes different pools
                Iterator<String> iter = countDrawQuestions.keySet().iterator();
                while (iter.hasNext()) {
                    String id = iter.next();
                    PoolDraw draw = part.addDrawDetail(allPools.get(id), countDrawQuestions.get(id));
                    // points draw.setEffectivePoints(points)
                    if (pointDrawQuestions.get(id) != null && countDrawQuestions.get(id) != null)
                        draw.setEffectivePoints(
                                Float.parseFloat(pointDrawQuestions.get(id)) * countDrawQuestions.get(id));
                }
            } else {
                // for other packages
                poolService.savePool(randomPool);
                PoolDraw draw = part.addDrawDetail(randomPool, randomCount);
                if (pointDrawQuestions.get(randomPool.getId()) != null)
                    draw.setEffectivePoints(
                            Float.parseFloat(pointDrawQuestions.get(randomPool.getId())) * randomCount);
                return part;
            }
        } catch (Exception e) {
        }
        return part;
    }

    /**
     * Create Task when there is no interaction
     * 
     * @param question
     * @param text
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildTask(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        Question question = this.questionService.newQuestion(pool, "mneme:Task");
        question.getPresentation().setText(text);
        question.getTypeSpecificQuestion().consolidate("");

        return question;
    }

    /**
     * Create likert scale question
     * 
     * @param pool
     * @param text
     * @param interactionText
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildLikertScaleChoice(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        if (interactionText == null || !interactionText.equals("choiceInteraction"))
            return null;

        XPath scalePath = new DOMXPath(".//correctResponse/value");
        String scale = scalePath.stringValueOf(contentsDOM);

        if (scale == null || scale.length() == 0) {
            XPath simpleChoicesPath = new DOMXPath("//simpleChoice");
            List<Element> choiceList = (List<Element>) simpleChoicesPath.selectNodes(contentsDOM);
            List<String> answerChoices = new ArrayList<String>();

            for (Element Choice : choiceList) {
                String label = normalizeElementBody(contentsDOM, Choice);
                answerChoices.add(label);
            }
        }

        Question question = this.questionService.newQuestion(pool, "mneme:LikertScale");
        LikertScaleQuestionImpl mc = (LikertScaleQuestionImpl) (question.getTypeSpecificQuestion());
        question.getPresentation().setText(text);
        if (scale != null && scale.length() > 0)
            mc.setScale(scale);

        question.getTypeSpecificQuestion().consolidate("");
        return question;
    }

    /**
     * Create match type question when interaction is matchInteraction or associateInteraction
     * 
     * @param question
     * @param text
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildMatch(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        if (!(("matchInteraction").equals(interactionText) || ("associateInteraction").equals(interactionText)))
            return null;

        XPath responsePath = new DOMXPath("/assessmentItem/responseDeclaration");
        Element responseElement = (Element) responsePath.selectSingleNode(contentsDOM);
        String baseType = (responseElement != null && responseElement.getAttribute("baseType") != null)
                ? responseElement.getAttribute("baseType")
                : null;
        if (!("directedPair".equalsIgnoreCase(baseType) || "Pair".equalsIgnoreCase(baseType)))
            return null;

        // match interaction
        XPath choicesPath = new DOMXPath("/assessmentItem/itemBody//" + interactionText);
        Element choices = (Element) choicesPath.selectSingleNode(contentsDOM);

        // correct answer
        XPath identifierPath = new DOMXPath("/assessmentItem/responseDeclaration/correctResponse/value");
        List<Element> values = (List<Element>) identifierPath.selectNodes(contentsDOM);

        if (choices == null || values == null)
            return null;

        // if one maxAssociation then create MC Question
        int maxAssociations = 0;
        if (!"".equals(choices.getAttribute("maxAssociations")))
            maxAssociations = Integer.parseInt(choices.getAttribute("maxAssociations"));
        if (maxAssociations == 1 || values.size() == 1)
            return buildMultipleChoiceFromMatch(pool, text, values, interactionText, contentsDOM);

        Question question = this.questionService.newQuestion(pool, "mneme:Match");
        MatchQuestionImpl mc = (MatchQuestionImpl) (question.getTypeSpecificQuestion());
        mc = buildMatchforAssociate(values, choices, interactionText, contentsDOM, mc);

        if (text == null || text.equals(""))
            text = "Match the equivalent:";
        question.getPresentation().setText(text);
        // save
        question.getTypeSpecificQuestion().consolidate("");

        return question;
    }

    /**
     * 
     * @param correctValues
     * @param interactionText
     * @param contentsDOM
     * @param mc
     * @return
     * @throws Exception
     */
    private MatchQuestionImpl buildMatchforAssociate(List<Element> correctValues, Element choices,
            String interactionText, Document contentsDOM, MatchQuestionImpl mc) throws Exception {
        if (!(("matchInteraction").equals(interactionText) || ("associateInteraction").equals(interactionText)))
            return mc;

        ArrayList<Element> matchDone = new ArrayList<Element>();
        int maxAssociations = 0;
        maxAssociations = Integer.parseInt(choices.getAttribute("maxAssociations"));

        int doneAssociation = 0;

        for (Element value : correctValues) {
            String valuePair = value.getTextContent();
            if (valuePair == null)
                continue;
            String[] parts = StringUtil.split(valuePair, " ");
            if (parts.length == 2) {
                String labelIdentifier1 = parts[0];
                String labelIdentifier2 = parts[1];

                XPath choicePath = new DOMXPath(
                        ".//simpleAssociableChoice[@identifier='" + labelIdentifier1 + "']");
                Element choice1 = (Element) choicePath.selectSingleNode(contentsDOM);
                String choiceLabel1 = normalizeElementBody(contentsDOM, choice1);
                matchDone.add(choice1);

                XPath choicePath2 = new DOMXPath(
                        "/assessmentItem/itemBody//simpleAssociableChoice[@identifier='" + labelIdentifier2 + "']");
                Element choice2 = (Element) choicePath2.selectSingleNode(contentsDOM);
                String choiceLabel2 = normalizeElementBody(contentsDOM, choice2);
                matchDone.add(choice2);

                if (choiceLabel1 != null && choiceLabel2 != null) {
                    mc.addPair(choiceLabel1, choiceLabel2);
                }
            }
            doneAssociation++;
        }
        // distractor
        XPath choicePath = new DOMXPath(".//simpleAssociableChoice");
        List<Element> allChoices = choicePath.selectNodes(contentsDOM);

        for (Element c : allChoices) {
            if (!matchDone.contains(c))
                mc.setDistractor(normalizeElementBody(contentsDOM, c));
        }

        return mc;
    }

    /**
     * 
     * @param pool
     * @param text
     * @param interactionText
     * @param unzipLocation
     * @param context
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildMatchforManyMultipleChoice(Pool pool, String text, String interactionText,
            Document contentsDOM) throws Exception {
        if (!("choiceInteraction").equals(interactionText))
            return null;

        // number of choice interaction
        XPath choicesPath = new DOMXPath("/assessmentItem/itemBody//" + interactionText);
        List<Element> choices = choicesPath.selectNodes(contentsDOM);
        if (choices == null || choices.size() <= 1)
            return null;

        Question question = this.questionService.newQuestion(pool, "mneme:Match");
        MatchQuestionImpl mc = (MatchQuestionImpl) (question.getTypeSpecificQuestion());

        // for finding distractors
        ArrayList<String> choiceStrings = new ArrayList<String>();

        for (Element choice : choices) {
            String choice1 = "";
            XPath promptPath = new DOMXPath(".//prompt|.//blockquote");
            List<Element> prompts = promptPath.selectNodes(choice);
            for (Element prompt : prompts) {
                choice1 = choice1.concat(normalizeElementBody(contentsDOM, prompt));
            }

            //choice 2 from responseDeclaration
            String choiceIdentifier = choice.getAttribute("responseIdentifier");
            if (choiceIdentifier == null || choiceIdentifier.length() == 0)
                continue;
            XPath responsePath = new DOMXPath("/assessmentItem/responseDeclaration[@identifier='" + choiceIdentifier
                    + "']/correctResponse/value");
            Element responseElement = (Element) responsePath.selectSingleNode(contentsDOM);
            String choice2 = "";
            // if response declaration has correct answer 
            if (responseElement != null) {
                choice2 = getCorrectResponse(responseElement, contentsDOM, false);
            } else {
                // get correct answers from RequestProcessing
                List<String> correctAnswerChoices = getCorrectResponsefromResponseProcessing(contentsDOM,
                        choiceIdentifier, true);
                if (correctAnswerChoices != null && correctAnswerChoices.size() > 0)
                    choice2 = correctAnswerChoices.get(0);
            }

            choiceStrings.add(choice2);
            mc.addPair(choice1, choice2);
        }

        // find distractor
        /*      XPath distractorPath = new DOMXPath(".//simpleChoice");
              List<Element> distractors = distractorPath.selectNodes(contentsDOM);
              for (Element distractor : distractors)
              {
                 String dist_text = normalizeElementBody(contentsDOM, distractor);
                 if (!choiceStrings.contains(dist_text)) mc.setDistractor(dist_text);
              }*/

        if (text == null || text.equals(""))
            text = "Match the equivalent:";
        question.getPresentation().setText(text);
        // save
        question.getTypeSpecificQuestion().consolidate("");

        return question;
    }

    /**
     * 
     * @param correctValues
     * @param interactionText
     * @param contentsDOM
     * @param mc
     * @return
     * @throws Exception
     */
    private Question buildFillBlankforGapText(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        if (!("gapMatchInteraction").equals(interactionText))
            return null;

        String questionText = "";

        // correct answer
        XPath identifierPath = new DOMXPath("/assessmentItem/responseDeclaration/correctResponse/value");
        List<Element> values = identifierPath.selectNodes(contentsDOM);

        XPath gapInteractionPath = new DOMXPath("/assessmentItem/itemBody//gapMatchInteraction");
        Element gapInteractionElement = (Element) gapInteractionPath.selectSingleNode(contentsDOM);
        String interactionIdentifier = gapInteractionElement.getAttribute("responseIdentifier");

        // The bandit killed her <gap identifier="G1"/>
        XPath gapPath = new DOMXPath("/assessmentItem/itemBody//gap");
        List<Element> gaps = gapPath.selectNodes(contentsDOM);

        for (Element gap : gaps) {
            String identifier = gap.getAttribute("identifier");
            String otherId = "";

            XPath responseValuePath = new DOMXPath("/assessmentItem/responseDeclaration[@identifier='"
                    + interactionIdentifier + "']/correctResponse/value[contains(text(),'" + identifier + "')]");
            Element value = (Element) responseValuePath.selectSingleNode(contentsDOM);

            if (value != null) {
                String valuePair = value.getTextContent();
                otherId = valuePair.replace(identifier, "").trim();
            } else {
                List<String> answers = getCorrectResponsefromResponseProcessing(contentsDOM, interactionIdentifier,
                        false);
                if (answers != null && answers.size() > 0)
                    otherId = answers.get(0);
            }
            // <gapText identifier="F" matchMax="1">family</gapText>
            if ("".equals(otherId))
                continue;
            XPath idPath = new DOMXPath(".//gapText[@identifier='" + otherId + "']");
            Element gapTextElement = (Element) idPath.selectSingleNode(contentsDOM);
            String choiceLabel2 = normalizeElementBody(contentsDOM, gapTextElement);

            gap.setTextContent("{" + choiceLabel2 + "}");
        }

        // add hints
        XPath gaptextPath = new DOMXPath(".//gapText");
        List<Element> allGapTexts = gaptextPath.selectNodes(contentsDOM);

        String hints = "";
        for (Element g : allGapTexts) {
            hints = hints.concat("\t" + normalizeElementBody(contentsDOM, g));
            g.setTextContent("");
        }

        XPath itemBodyPath = new DOMXPath("/assessmentItem/itemBody");
        Element itemBodyElement = (Element) itemBodyPath.selectSingleNode(contentsDOM);
        questionText = normalizeElementBody(contentsDOM, itemBodyElement);

        Question question = buildMnemeFillBlanks(pool, true, questionText);
        question.setHints(hints);

        return question;
    }

    /**
     * 
     * @param pool
     * @param text
     * @param interactionText
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildFillBlankforOrdered(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        if (!"orderInteraction".equals(interactionText))
            return null;

        // correct answer
        XPath responseDeclarePath = new DOMXPath("/assessmentItem/responseDeclaration");

        // choice interaction
        XPath choicesPath = new DOMXPath("/assessmentItem/itemBody//" + interactionText);
        Element choices = (Element) choicesPath.selectSingleNode(contentsDOM);

        if (choices == null)
            return null;

        // correct answer
        XPath identifierPath = new DOMXPath("/assessmentItem/responseDeclaration/correctResponse/value");
        List<Element> values = identifierPath.selectNodes(contentsDOM);

        XPath simpleChoicesPath = new DOMXPath("//simpleChoice");
        List<Element> choiceList = simpleChoicesPath.selectNodes(contentsDOM);

        text = text.concat("\n <table width='55%' border='0'>");
        for (Element Choice : choiceList) {
            String id = Choice.getAttribute("identifier");
            String label = normalizeElementBody(contentsDOM, Choice);
            text = text.concat("<tr><td><b>" + id + "</b> </td><td>" + label + "</td></tr>");
        }

        text = text.concat("</table> \n Enter the letters in the correct order ");

        int i = 1;
        if (values != null) {
            for (Element value : values) {
                String correctValue = getCorrectResponse(value, contentsDOM, false);
                if (i != values.size())
                    text = text.concat("{" + correctValue + "},");
                else
                    text = text.concat("{" + correctValue + "}.");
                i++;
            }
        }
        return buildMnemeFillBlanks(pool, true, text);
    }

    /**
     * 
     * @param pool
     * @param text
     * @param interactionText
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildMultipleChoiceforUnsupported(Pool pool, String text, String unzipLocation, String context,
            Document contentsDOM) throws Exception {
        // text has all content
        XPath itemPath = new DOMXPath(".//itemBody");
        Element item = (Element) itemPath.selectSingleNode(contentsDOM);
        List<String> bringMedia = new ArrayList<String>();
        text = normalizeItemBodyElement(contentsDOM, item, true, unzipLocation, context, bringMedia);

        int maxChoices = 1;
        boolean shuffle = false;
        List<String> answerChoices = new ArrayList<String>();
        Set<Integer> correctAnswers = new HashSet<Integer>();

        return buildMnemeMultipleChoice(pool, text, maxChoices, shuffle, answerChoices, correctAnswers);
    }

    /**
     * 
     * @param pool
     * @param text
     * @param correctValues
     * @param interactionText
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildMultipleChoiceFromMatch(Pool pool, String text, List<Element> correctValues,
            String interactionText, Document contentsDOM) throws Exception {
        if (!(("matchInteraction").equals(interactionText) || ("associateInteraction").equals(interactionText)))
            return null;

        List<String> answerChoices = new ArrayList<String>();
        Set<Integer> correctAnswers = new HashSet<Integer>();
        String[] parts = null;

        for (Element value : correctValues) {
            String valuePair = value.getTextContent();
            if (valuePair == null)
                continue;
            parts = StringUtil.split(valuePair, " ");
        }

        int i = 0;
        XPath choicePath = new DOMXPath(".//simpleAssociableChoice");
        List<Element> allChoices = choicePath.selectNodes(contentsDOM);

        for (Element c : allChoices) {
            String id = c.getAttribute("identifier");
            answerChoices.add(normalizeElementBody(contentsDOM, c));

            if (parts[1].contains(id)) {
                text = text.concat(normalizeElementBody(contentsDOM, c));
                continue;
            }
            if (parts[0].contains(id))
                correctAnswers.add(i);

            i++;
        }

        return buildMnemeMultipleChoice(pool, text, 1, false, answerChoices, correctAnswers);
    }

    /**
     * Create Multiple Choice Question when interaction is Choice and more than 2 choices.
     * 
     * @param mc
     * @param text
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildMultipleChoice(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {
        if (!("choiceInteraction").equals(interactionText))
            return null;

        // correct answer
        XPath responseDeclarePath = new DOMXPath("/assessmentItem/responseDeclaration");
        Element responseDeclare = (Element) responseDeclarePath.selectSingleNode(contentsDOM);
        String cardinality = (responseDeclare.getAttribute("cardinality") != null)
                ? responseDeclare.getAttribute("cardinality")
                : null;
        String basetype = (responseDeclare.getAttribute("baseType") != null)
                ? responseDeclare.getAttribute("baseType")
                : null;

        // choice interaction
        XPath choicesPath = new DOMXPath("/assessmentItem/itemBody//" + interactionText);
        Element choices = (Element) choicesPath.selectSingleNode(contentsDOM);

        if (choices == null)
            return null;

        // correct answer
        XPath identifierPath = new DOMXPath("/assessmentItem/responseDeclaration/correctResponse/value");
        List<Element> values = identifierPath.selectNodes(contentsDOM);

        XPath simpleChoicesPath = new DOMXPath("//simpleChoice");
        List<Element> choiceList = simpleChoicesPath.selectNodes(contentsDOM);

        if (!("identifier".equalsIgnoreCase(basetype) && cardinality != null && choiceList.size() > 0))
            return null;

        int maxChoices = (choices.getAttribute("maxChoices") != null
                && choices.getAttribute("maxChoices").length() > 0)
                        ? Integer.parseInt(choices.getAttribute("maxChoices"))
                        : 1;
        boolean shuffle = (choices.getAttribute("shuffle") != null)
                ? Boolean.parseBoolean(choices.getAttribute("shuffle"))
                : false;
        List<String> answerChoices = new ArrayList<String>();
        List<String> correctAnswerChoices = new ArrayList<String>();
        Set<Integer> correctAnswers = new HashSet<Integer>();

        // if response declaration has correct answer 
        if (values != null && values.size() > 0) {
            for (Element value : values)
                correctAnswerChoices.add(getCorrectResponse(value, contentsDOM, false));
        } else {
            // get correct answers from RequestProcessing
            correctAnswerChoices = getCorrectResponsefromResponseProcessing(contentsDOM,
                    choices.getAttribute("responseIdentifier"), false);
        }

        int i = 0;
        for (Element Choice : choiceList) {
            String id = Choice.getAttribute("identifier");
            String label = normalizeElementBody(contentsDOM, Choice);
            answerChoices.add(label);

            if (correctAnswerChoices.contains(id))
                correctAnswers.add(i);
            i++;
        }

        if (maxChoices < correctAnswers.size())
            maxChoices = correctAnswers.size();
        return buildMnemeMultipleChoice(pool, text, maxChoices, shuffle, answerChoices, correctAnswers);
    }

    /**
     * 
     * @param pool
     * @param responseTextual
     * @param text
     * @return
     * @throws Exception
     */
    private Question buildMnemeFillBlanks(Pool pool, Boolean responseTextual, String text) throws Exception {
        Question question = this.questionService.newQuestion(pool, "mneme:FillBlanks");
        FillBlanksQuestionImpl fb = (FillBlanksQuestionImpl) (question.getTypeSpecificQuestion());

        fb.setText(text);
        // case sensitive
        fb.setCaseSensitive(Boolean.FALSE.toString());

        // mutually exclusive
        fb.setAnyOrder(Boolean.FALSE.toString());

        // text or numeric
        if (responseTextual == null)
            responseTextual = true;
        fb.setResponseTextual(responseTextual.toString());

        question.getPresentation().setText(text);

        question.getTypeSpecificQuestion().consolidate("");

        return question;
    }

    /**
     * Commonly used method to create mneme's multiple choice question
     * 
     * @param pool
     * @param text
     * @param maxChoices
     * @param shuffle
     * @param values
     * @param answerChoices
     * @param correctAnswers
     * @return
     */
    private Question buildMnemeMultipleChoice(Pool pool, String text, int maxChoices, boolean shuffle,
            List<String> answerChoices, Set<Integer> correctAnswers) throws Exception {
        Question question = this.questionService.newQuestion(pool, "mneme:MultipleChoice");

        MultipleChoiceQuestionImpl mc = (MultipleChoiceQuestionImpl) (question.getTypeSpecificQuestion());
        question.getPresentation().setText(text);

        boolean singleCorrect = false;

        if (maxChoices < 1 || correctAnswers.size() <= 1)
            singleCorrect = true;

        mc.setSingleCorrect(new Boolean(singleCorrect).toString());
        mc.setShuffleChoices(new Boolean(shuffle).toString());
        mc.setAnswerChoices(answerChoices);
        mc.setCorrectAnswerSet(correctAnswers);

        question.getTypeSpecificQuestion().consolidate("");
        return question;
    }

    /**
     * Create True False question when interaction is choiceInteraction and 2 choices.
     * 
     * @param tf
     * @param text
     * @param contentsDOM
     * @return
     * @throws Exception
     */
    private Question buildTrueFalse(Pool pool, String text, String interactionText, Document contentsDOM)
            throws Exception {

        if (interactionText == null || !interactionText.equals("choiceInteraction"))
            return null;

        XPath responseDeclarePath = new DOMXPath("/assessmentItem/responseDeclaration");
        Element responseDeclare = (Element) responseDeclarePath.selectSingleNode(contentsDOM);
        String cardinality = (responseDeclare.getAttribute("cardinality") != null)
                ? responseDeclare.getAttribute("cardinality")
                : null;
        String basetype = (responseDeclare.getAttribute("baseType") != null)
                ? responseDeclare.getAttribute("baseType")
                : null;

        // choice interaction
        XPath choicesPath = new DOMXPath("/assessmentItem/itemBody//choiceInteraction");
        Element choices = (Element) choicesPath.selectSingleNode(contentsDOM);

        if (choices == null)
            return null;

        XPath identifierPath = new DOMXPath("/assessmentItem/responseDeclaration/correctResponse/value");
        List<Element> values = identifierPath.selectNodes(contentsDOM);

        XPath simpleChoicesPath = new DOMXPath("//simpleChoice");
        List<Element> choiceList = simpleChoicesPath.selectNodes(contentsDOM);

        // check for true/false traits
        if (!("identifier".equalsIgnoreCase(basetype) && cardinality != null && choiceList.size() == 2))
            return null;

        List<String> answerChoices = new ArrayList<String>();
        List<String> correctAnswerChoices = new ArrayList<String>();
        String correctAnswer = null;

        // if response declaration has correct answer 
        if (values != null && values.size() > 0) {
            for (Element value : values)
                correctAnswerChoices.add(getCorrectResponse(value, contentsDOM, false));
        } else {
            // get correct answers from RequestProcessing
            correctAnswerChoices = getCorrectResponsefromResponseProcessing(contentsDOM,
                    choices.getAttribute("responseIdentifier"), false);
        }

        for (Element Choice : choiceList) {
            Element ChoiceCopy = (Element) Choice.cloneNode(true);
            ChoiceCopy = removeFeedback(ChoiceCopy);

            String id = ChoiceCopy.getAttribute("identifier");
            String label = normalizeElementBody(contentsDOM, ChoiceCopy);
            if (label == null)
                continue;
            label = label.trim();
            answerChoices.add(label);

            if (correctAnswerChoices.contains(id)) {
                if ("yes".equalsIgnoreCase(label) || "Right".equalsIgnoreCase(label)
                        || "True".equalsIgnoreCase(label))
                    correctAnswer = new Boolean("true").toString();
                else if ("no".equalsIgnoreCase(label) || "Wrong".equalsIgnoreCase(label)
                        || "False".equalsIgnoreCase(label))
                    correctAnswer = new Boolean("false").toString();
            }
        }

        if (correctAnswer == null)
            return null;

        Question question = this.questionService.newQuestion(pool, "mneme:TrueFalse");
        TrueFalseQuestionImpl tf = (TrueFalseQuestionImpl) (question.getTypeSpecificQuestion());
        question.getPresentation().setText(text);
        tf.setCorrectAnswer(correctAnswer);

        question.getTypeSpecificQuestion().consolidate("");
        return question;
    }

    /**
     * Read file contents
     * 
     * @param fileUploadResource
     * @return
     * @throws Exception
     */
    private byte[] readDatafromFile(File fileUploadResource) throws Exception {
        if (fileUploadResource.exists() && fileUploadResource.isFile()) {
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(fileUploadResource);

                byte buf[] = new byte[(int) fileUploadResource.length()];
                fis.read(buf);
                return buf;
            } catch (Exception ex) {
                throw ex;
            } finally {
                if (fis != null)
                    fis.close();
            }
        } else
            return null;
    }

    /**
     * Remove feedback element from the choice and item body so that getTextContent() doesn't bring their content
     * 
     * @param itemBodyElement
     * @return
     * @throws Exception
     */
    private Element removeFeedback(Element itemBodyElement) throws Exception {
        XPath inlineFeedbackPath = new DOMXPath(".//feedbackInline | .//feedbackBlock");
        List<Element> feedbackElements = inlineFeedbackPath.selectNodes(itemBodyElement);

        if (feedbackElements != null && feedbackElements.size() > 0) {
            for (Element fb : feedbackElements)
                fb.setTextContent("");
        }

        return itemBodyElement;
    }

    /**
     * Import embedded data.
     * 
     * @param fileName
     *        Embedded media file name
     * @return the embedded resource Id or null if file doesn't exist
     */
    private Reference transferEmbeddedData(String fileName, String name, String context) {
        String addCollectionId = "/private/mneme/" + context + "/docs/";

        try {
            name = name.replace("\\", "/");
            if (name.lastIndexOf("/") != -1)
                name = name.substring(name.lastIndexOf("/") + 1);

            String res_mime_type = name.substring(name.lastIndexOf(".") + 1);
            res_mime_type = ContentTypeImageService.getContentType(res_mime_type);
            byte[] content_data = readDatafromFile(new File(fileName));
            if (content_data == null || content_data.length == 0)
                return null;
            ResourcePropertiesEdit res = ContentHostingService.newResourceProperties();
            res.addProperty(ResourceProperties.PROP_DISPLAY_NAME, name);

            Reference ref = attachmentService.addAttachment(AttachmentService.MNEME_APPLICATION, context,
                    AttachmentService.DOCS_AREA, AttachmentService.NameConflictResolution.keepExisting, name,
                    content_data, res_mime_type, AttachmentService.MNEME_THUMB_POLICY,
                    AttachmentService.REFERENCE_ROOT);

            return ref;
        } catch (IdUsedException e) {
            // return a reference to the existing file
            Reference reference = entityManager
                    .newReference(ContentHostingService.getReference(addCollectionId + name));
            return reference;
        } catch (Exception e) {
            M_log.debug(e.getMessage());
        }

        return null;
    }

    /**
     * Final initialization, once all dependencies are set.
     */
    public void init() {
        // messages
        if (this.bundle != null)
            this.messages = new ResourceLoader(this.bundle);

        M_log.info("init()");
    }

    /**
     * Dependency: AssessmentService.
     * 
     * @param service
     *        The AssessmentService.
     */
    public void setAssessmentService(AssessmentService service) {
        this.assessmentService = service;
    }

    /**
     * Dependency: AttachmentService.
     * 
     * @param service
     *        The AttachmentService.
     */
    public void setAttachmentService(AttachmentService service) {
        attachmentService = service;
    }

    /**
     * Set the message bundle.
     * 
     * @param bundle
     *        The message bundle.
     */
    public void setBundle(String name) {
        this.bundle = name;
    }

    /**
     * Dependency: EntityManager.
     * 
     * @param service
     *        The EntityManager.
     */
    public void setEntityManager(EntityManager service) {
        entityManager = service;
    }

    /**
     * Dependency: EventTrackingService.
     * 
     * @param service
     *        The EventTrackingService.
     */
    public void setEventTrackingService(EventTrackingService service) {
        eventTrackingService = service;
    }

    /**
     * Set the PoolService.
     * 
     * @param service
     *        the PoolService.
     */
    public void setPoolService(PoolService service) {
        this.poolService = service;
    }

    /**
     * Dependency: QuestionService.
     * 
     * @param service
     *        The QuestionService.
     */
    public void setQuestionService(QuestionService service) {
        this.questionService = service;
    }

    /**
     * Dependency: SecurityService.
     * 
     * @param service
     *        The SecurityService.
     */
    public void setSecurityService(SecurityService service) {
        securityService = service;
    }

    /**
     * Dependency: SessionManager.
     * 
     * @param service
     *        The SessionManager.
     */
    public void setSessionManager(SessionManager service) {
        sessionManager = service;
    }

    /**
     * Dependency: ThreadLocalManager.
     * 
     * @param service
     *        The SqlService.
     */
    public void setThreadLocalManager(ThreadLocalManager service) {
        threadLocalManager = service;
    }

}