Package org.olat.ims.qti.container

Source Code of org.olat.ims.qti.container.SectionContext

/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/

package org.olat.ims.qti.container;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import org.dom4j.Element;
import org.dom4j.Node;
import org.olat.ims.qti.container.qtielements.Objectives;
import org.olat.ims.qti.container.qtielements.SectionFeedback;
import org.olat.ims.qti.process.AssessmentInstance;
import org.olat.ims.qti.process.QTIHelper;
import org.olat.ims.qti.process.elements.ExpressionBuilder;
import org.olat.ims.qti.process.elements.ScoreBooleanEvaluable;

/**
* @author Potable Shop
*/
public class SectionContext implements Serializable {
  private String ident;
  //private String title;
  private AssessmentInstance assessInstance;

  // readonly ref!: the ref to the el_section; transient since it we don't want
  // to serialize it (too long) and can reattach it later
  private Element el_section;
  private Objectives objectives;
  private List itemContexts;

  private float totalScore; // only floats and integers supported at the moment
  private int cutvalue;
  private int currentItemContextPos;
  private long timeOfStart;
  // -1 = not started yet; server time at the time of the start of the section
  private long durationLimit; //
  private long timesAnswered;
  private long latestAnswerTime;
  private Output output;

  //private boolean outcomesProcessing;
  private boolean feedbacktesting; // has the section feedback calculation
  private boolean feedbackswitchedon; // is feedback allowed
  private boolean feedbackavailable; // is feedback currently available?
  private String scoremodel;

  /**
   * default constructor needed for persistence
   */
  public SectionContext() {
  // 
  }

  /**
   *
   */
  public void init() {
    totalScore = 0.0f;
    currentItemContextPos = -1;
    timeOfStart = -1; // not started yet
    timesAnswered = 0; // not answered yet (this flag has no direct meaning in
    // qti)
    latestAnswerTime = -1; //outcomesProcessing = false;
    feedbacktesting = false;
    cutvalue = -1;
    feedbackavailable = false;
  }

  /**
   * Start duration counters.
   */
  public void start() {
    if (timeOfStart == -1) { // if not started already
      timeOfStart = System.currentTimeMillis();
    }
  }

  /**
   *
   */
  public void sectionWasSubmitted() {
    timesAnswered++;
    latestAnswerTime = System.currentTimeMillis();
  }

  /**
   * Method eval.
   */
  public void eval() {
    if (assessInstance.isSurvey()) return;
    int itccnt = getItemContextCount();
    for (int i = 0; i < itccnt; i++) {
      ItemContext ict = getItemContext(i);
      ict.eval();
    }
    calcScore(); // calc feedback
    if (feedbacktesting) calcFeedBack();
  }

  /**
   * @param assessInstance
   * @param el_section
   * @param sw
   */
  public void setUp(AssessmentInstance assessInstance, Element el_section, Switches sw) {
    this.assessInstance = assessInstance;
    this.el_section = el_section;
    this.ident = el_section.attributeValue("ident");
    init();

    Element dur = (Element) el_section.selectSingleNode("duration");
    if (dur == null) {
      durationLimit = -1; // no limit
    } else {
      String sdur = dur.getText();
      durationLimit = QTIHelper.parseISODuration(sdur);
      if (durationLimit == 0) durationLimit = -1; // Assesst Designer fix
    }

    // get objectives
    Element el_objectives = (Element)el_section.selectSingleNode("objectives");
    if (el_objectives != null) objectives = new Objectives(el_objectives);
   
    // feedback switches
    // ---------------------------------------------------------
    if (sw == null) { // no switches from the assessment context dominate
      // retrieve section switches
      Element el_control = (Element) el_section.selectSingleNode("sectioncontrol");
      if (el_control != null) {
        String feedbackswitch = el_control.attributeValue("feedbackswitch");
        String hintswitch = el_control.attributeValue("hintswitch");
        String solutionswitch = el_control.attributeValue("solutionswitch");
        feedbackswitchedon = (feedbackswitch == null) ? true : feedbackswitch.equals("Yes");
        boolean hints = (hintswitch == null) ? true : hintswitch.equals("Yes");
        boolean solutions = (solutionswitch == null) ? true : solutionswitch.equals("Yes");
        sw = new Switches(feedbackswitchedon, hints, solutions);
      }
    }

    // ----------------------- selection
    List el_items = new ArrayList();
    // determine which items (sections not implemented) will be chosen/selected
    // for this section
    // --- 1. take all items and resolved itemrefs which are in the section
    List items = el_section.selectNodes("item|itemref");
    for (Iterator iter = items.iterator(); iter.hasNext();) {
      Element el_item = (Element) iter.next();
      //<!ELEMENT itemref (#PCDATA)> <!ATTLIST itemref %I_LinkRefId; > <!ENTITY
      // % I_LinkRefId " linkrefid CDATA #REQUIRED">
      if (el_item.getName().equals("itemref")) {
        // resolve the entity first
        String linkRefId = el_item.attributeValue("linkrefid");
        el_item = (Element) el_section.selectSingleNode("//item[@ident='" + linkRefId + "']");
        // if item == null -> TODO Error
      }
      el_items.add(el_item);
    }
    // --- 2. select all items from the objectbank which fulfill the selection
    // criteria

    Element el_selordering = (Element) el_section.selectSingleNode("selection_ordering");
    if (el_selordering != null) {
      // do some selection and ordering
      // here comes the selection....
      // xpath =
      // "//item[itemmetadata/qtimetadata/qtimetadatafield[fieldlabel[text()='qmd_dificulty']
      // and fieldentry[text()='4']] or
      // itemmetadata/qtimetadata/qtimetadatafield[fieldlabel[text()='qmd_author']
      // and fieldentry[text()='felix']]]"
      //<!ELEMENT selection_ordering (qticomment? , sequence_parameter* ,
      // selection* , order?)>
      //<!ATTLIST selection_ordering sequence_type CDATA #IMPLIED >
      //<!ELEMENT selection (sourcebank_ref? , selection_number? ,
      // selection_metadata? ,
      //        (and_selection | or_selection | not_selection | selection_extension)?)>
      //<!ELEMENT sourcebank_ref (#PCDATA)>
      //not <!ELEMENT order (order_extension?)>
      //<!ATTLIST order order_type CDATA #REQUIRED >
      //<!ELEMENT selection_number (#PCDATA)>
      //not <!ELEMENT sequence_parameter (#PCDATA)>
      //not <!ATTLIST sequence_parameter %I_Pname; >
      List el_selections = el_selordering.selectNodes("selection");

      // iterate over all selection elements : after each we have some items to
      // add to the run-time-section
      for (Iterator it_selection = el_selections.iterator(); it_selection.hasNext();) {
        List selectedItems;
        Element el_selection = (Element) it_selection.next();
        Element el_sourcebankref = (Element) el_selection.selectSingleNode("sourcebank_ref");
        if (el_sourcebankref == null) {
          // no reference to sourcebank, -> take internal one, but dtd disallows
          // it!?? TODO
          /*
           * 2:27 PM] <felix.jost> aus ims qti sao: [2:27 PM] <felix.jost> 3.2.1
           * <sourcebank_ref> Description: Identifies the objectbank to which
           * the selection and ordering rules are to be applied. This objectbank
           * may or may not be contained in the same <questestinterop> package.
           * [2:27 PM] <felix.jost> aber dtd: [2:28 PM] <felix.jost> <!ELEMENT
           * questestinterop (qticomment? , (objectbank | assessment | (section |
           * item)+))>
           */
          selectedItems = new ArrayList();
        } else {
          String sourceBankRef = el_sourcebankref.getText();
          Element objectBank = assessInstance.getResolver().getObjectBank(sourceBankRef);

          // traverse 1.: process "and" or "or" or "not" selection to get the
          // items, if existing, otherwise take all items
          //          2.: do the selection_number
          Element andornot_selection = (Element) el_selection
              .selectSingleNode("and_selection|or_selection|not_selection|selection_metadata");
          StringBuilder select_expr = new StringBuilder("//item");
          if (andornot_selection != null) {
            // some criteria, extend above xpath to select only the appropriate
            // elements
            select_expr.append("[");
            String elName = andornot_selection.getName();
            ExpressionBuilder eb = QTIHelper.getExpressionBuilder(elName);
            eb.buildXPathExpression(andornot_selection, select_expr, false, true);
            select_expr.append("]");
          }
          selectedItems = objectBank.selectNodes(select_expr.toString());
          el_items.addAll(selectedItems);
        }
        Element el_selection_number = (Element) el_selection.selectSingleNode("selection_number");
        // --- 3. if selection_number exists, pick out some items
        if (el_selection_number != null) {
          String sNum = el_selection_number.getText();
          int num = new Integer(sNum).intValue();
          // now choose some x out of the items if selection_number exists
          List newList = new ArrayList();
          Random r = new Random();
          int size = el_items.size();
          // if num > size ??e.g. 5 elements should be picked, but there are
          // only four
          if (num > size) num = size;
          for (int i = 0; i < num; i++) {
            int n = r.nextInt(size--);
            Object o = el_items.remove(n);
            newList.add(o);
          }
          el_items = newList;
          /*
           * pick out items -> remove unused items from section
           */
          items.removeAll(el_items);
          for (Iterator iter = items.iterator(); iter.hasNext();) {
            el_section.remove((Node)iter.next());           
          }
         
         
        }
        // append found items to existing ones
      }
    } // end of el_ordering != null

    //  if there is order = random -> shuffle
    //<order order_type="Random"/>
    if (el_selordering != null) {
      Element el_order = (Element) el_selordering.selectSingleNode("order");
      if (el_order != null) {
        String order_type = el_order.attributeValue("order_type");
        if (order_type.equals("Random")) {
          Collections.shuffle(el_items);
        }
      }
    }

    // now wrap all item contexts
    itemContexts = new ArrayList(10);
    for (Iterator iter = el_items.iterator(); iter.hasNext();) {
      Element item = (Element) iter.next();
      item.detach();
      ItemContext itc = new ItemContext();
      itc.setUp(assessInstance, item, sw);
      if (durationLimit != -1 && assessInstance.isSectionPage()) itc.clearDurationLimit();
      itemContexts.add(itc);
    }

    //outcomesProcessing
    //<!ELEMENT section (qticomment? , duration? , qtimetadata* ,
    //  objectives* , sectioncontrol* , sectionprecondition* ,
    // sectionpostcondition* ,
    // rubric* , presentation_material? ,
    // outcomes_processing* , sectionproc_extension? ,
    // sectionfeedback* , selection_ordering? ,
    // reference? , (itemref | item | sectionref | section)*)>

    //<!ELEMENT outcomes_processing (qticomment? , outcomes ,
    // objects_condition* , processing_parameter* , map_output* ,
    // outcomes_feedback_test*)>
    //<!ELEMENT outcomes (qticomment? , (decvar , interpretvar*)+)>

    //<!ELEMENT decvar (#PCDATA)>
    //<!ATTLIST decvar %I_VarName; .......cutvalue CDATA #IMPLIED >
    Element el_outpro = (Element) el_section.selectSingleNode("outcomes_processing");
    if (el_outpro != null) {
      // get the scoring model: we need it later for calculating the score
      //<!ENTITY % I_ScoreModel " scoremodel CDATA #IMPLIED">
      scoremodel = el_outpro.attributeValue("scoremodel");
      // may be null -> then assume SumOfScores

      // set the cutvalue if given (only variable score)
      cutvalue = QTIHelper.getIntAttribute(el_outpro, "outcomes/decvar[@varname='SCORE']", "cutvalue");
      List el_oft = el_outpro.selectNodes("outcomes_feedback_test");
      if (el_oft.size() != 0) {
        feedbacktesting = true;
      }
    }
  }

  /**
   * Method calcFeedBack.
   */
  private void calcFeedBack() {
    List el_ofts = el_section.selectNodes("outcomes_processing/outcomes_feedback_test");
    feedbackavailable = false;
    for (Iterator it_oft = el_ofts.iterator(); it_oft.hasNext();) {
      Element el_oft = (Element) it_oft.next();
      //<!ELEMENT outcomes_feedback_test (test_variable , displayfeedback+)>
      Element el_testvar = (Element) el_oft.selectSingleNode("test_variable");
      // must exist: dtd
      //<!ELEMENT test_variable (variable_test | and_test | or_test |
      // not_test)>
      Element el_varandornot = (Element) el_testvar.selectSingleNode("variable_test|and_test|or_test|not_test");
      String elname = el_varandornot.getName();
      ScoreBooleanEvaluable sbe = QTIHelper.getSectionBooleanEvaluableInstance(elname);
      float totalscore = getScore();
      boolean fulfilled = sbe.eval(el_varandornot, totalscore);
      if (fulfilled) {
        // get feedback
        Element el_displayfeedback = (Element) el_oft.selectSingleNode("displayfeedback");
        String linkRefId = el_displayfeedback.attributeValue("linkrefid");
        // must exist (dtd)
        // ignore feedbacktype, since we section or assess feedback only accepts
        // material, no hints or solutions
        Element el_resolved = (Element) el_section.selectSingleNode(".//sectionfeedback[@ident='" + linkRefId + "']");
        getOutput().setEl_response(new SectionFeedback(el_resolved));
        // give the whole sectionfeedback to render
        feedbackavailable = true;
      }
    }
  }

  /**
   * @return List of ItemContext instances
   */
  public List getItemContextsToRender() {
    return itemContexts;
  }

  /**
   * Method calcScore.
   */
  private void calcScore() {
    totalScore = 0;
    if (scoremodel == null || scoremodel.equalsIgnoreCase("SumOfScores")) { // sumofScores
      for (Iterator iter = itemContexts.iterator(); iter.hasNext();) {
        ItemContext ict = (ItemContext) iter.next();
        totalScore += ict.getScore();
      }
    } else if (scoremodel.equalsIgnoreCase("NumberCorrect")) {
      totalScore = 0;
      int tmpscore = 0;
      // correct number of items: an item is correct if it reaches the cutvalue
      for (Iterator iter = itemContexts.iterator(); iter.hasNext();) {
        ItemContext ict = (ItemContext) iter.next();

        Variable var = ict.getVariables().getSCOREVariable();
        if (var == null) {
          // we demand that a SCORE variable must always exist
          throw new RuntimeException("no SCORE def for " + ict.getIdent());
        } else {
          float itemscore = var.getTruncatedValue();
          float itemcutval = var.getCutValue();
          if (itemscore >= itemcutval) tmpscore++; // count items correct
        }
      }
      if (tmpscore >= cutvalue) totalScore = 1.0f; // cutvalue of the section
    } else {
      throw new RuntimeException("scoring algorithm " + scoremodel + " not supported");
    }
  }

  /**
   * @return
   */
  public float getScore() {
    calcScore();
    return totalScore;
  }

  /**
   * @return
   */
  public String getIdent() {
    return ident;
  }

  /**
   * @return
   */
  public String getTitle() {
    return el_section.attributeValue("title");
  }

  /**
   * @see java.lang.Object#toString()
   */
  public String toString() {
    return "<br />section:" + getIdent() + " score:" + getScore() + ",items" + itemContexts.toString() + "=" + super.toString();
  }

  /**
   * Returns the sectionItems.
   *
   * @return List
   */
  public List getSectionItemContexts() {
    return itemContexts;
  }

  /**
   * Method getItemContext.
   *
   * @param sIdent
   * @return ItemContext
   */
  public ItemContext getItemContext(String sIdent) {
    for (Iterator it_icts = getSectionItemContexts().iterator(); it_icts.hasNext();) {
      ItemContext itc = (ItemContext) it_icts.next();
      if (itc.getIdent().equals(sIdent)) { return itc; }
    }

    // not found: for a user answer, no corresponding item could be found ->
    // error (in form?,logic...)
    // the other way round: no answer of user = the ItemInput of an ItemContext
    // is simply not set
    throw new RuntimeException("could not find an itemcontext with ident " + ident + " , but got an itemInput with this ident!");
  }

  /**
   * @return
   */
  public int getItemsAttemptedCount() {
    int total = 0;
    int itcnt = getItemContextCount();
    for (int i = 0; i < itcnt; i++) {
      if (getItemContext(i).getTimesAnswered() > 0) total++;
    }
    return total;
  }

  /**
   * @return
   */
  public int getItemsPresentedCount() {
    int total = 0;
    int itcnt = getItemContextCount();
    for (int i = 0; i < itcnt; i++) {
      if (getItemContext(i).isStarted()) total++;
    }
    return total;
  }

  /**
   * @return
   */
  public int getItemContextCount() {
    return itemContexts.size();
  }

  /**
   * Returns the currentItemContextPos.
   *
   * @return int
   */
  public int getCurrentItemContextPos() {
    return currentItemContextPos;
  }

  /**
   * Sets the currentItemContextPos.
   *
   * @param currentItemContextPos The currentItemContextPos to set
   */
  public void setCurrentItemContextPos(int currentItemContextPos) {
    this.currentItemContextPos = currentItemContextPos;
  }

  /**
   * Method getCurrentItemContext.
   *
   * @return Element
   */
  public ItemContext getCurrentItemContext() {
    return (ItemContext) itemContexts.get(currentItemContextPos);
  }

  /**
   * checks whether the user may still submit answers
   *
   * @return
   */
  public boolean isOpen() {
    // not started yet or no timelimit or within timelimit
    return onTime();
  }

  /**
   * @return
   */
  public boolean onTime() {
    // ok if not started yet or no time limit or within limit
    return (timeOfStart == -1) || (durationLimit == -1) || (System.currentTimeMillis() < (timeOfStart + durationLimit));
  }

  /**
   * @return
   */
  public boolean isStarted() {
    return (timeOfStart != -1);
  }

  /**
   * @param pos
   * @return
   */
  public ItemContext getItemContext(int pos) {
    return (ItemContext) itemContexts.get(pos);
  }

  /**
   * Returns the timeOfStart.
   *
   * @return long
   */
  public long getTimeOfStart() {
    return timeOfStart;
  }

  /**
   * Returns the timeOfStop.
   *
   * @return long
   */
  public long getLatestAnswerTime() {
    return latestAnswerTime;
  }

  /**
   * Get the time limit set on this section
   *
   * @return
   */
  public long getDurationLimit() {
    return durationLimit;
  }

  /**
   * Get the time to completion for this section
   *
   * @return
   */
  public long getDuration() {
    if (timesAnswered == 0) return -1;
    return latestAnswerTime - timeOfStart;
  }

  /**
   * @return
   */
  public float getMaxScore() {
    float score = 0.0f;
    for (int i = 0; i < getItemContextCount(); i++) {
      ItemContext itctx = getItemContext(i);
      float maxScore = itctx.getMaxScore();
      if (maxScore == -1) return -1;
      else score += maxScore;
    }
    return score;
  }

  /**
   * @return Output
   */
  public Output getOutput() {
    if (output == null) {
      output = new Output();
    }
    return output;
  }

  /**
   * @return boolean
   */
  public boolean isFeedbackavailable() {
    return feedbackavailable;
  }

  public Objectives getObjectives() {
    return objectives;
  }

  public int getCutValue() {
    return cutvalue;
  }
}
TOP

Related Classes of org.olat.ims.qti.container.SectionContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.