<commit_before>module Qti
class AssessmentItemConverter
  include Canvas::XMLHelper
  DEFAULT_CORRECT_WEIGHT = 100
  DEFAULT_INCORRECT_WEIGHT = 0
  DEFAULT_POINTS_POSSIBLE = 1
  UNSUPPORTED_TYPES = ['File Upload', 'Hot Spot', 'Quiz Bowl', 'WCT_JumbledSentence', 'file_upload_question']

  attr_reader :base_dir, :identifier, :href, :interaction_type, :title, :question

  def initialize(opts)
    @log = Canvas::Migration::logger
    reset_local_ids
    @manifest_node = opts[:manifest_node]
    @migration_type = opts[:interaction_type]
    @doc = nil
    @flavor = opts[:flavor]
    @opts = opts
    
    if @manifest_node
      @base_dir = opts[:base_dir]
      @identifier = @manifest_node['identifier']
      @href = File.join(@base_dir, @manifest_node['href'])
      if title = @manifest_node.at_css('title langstring') || title = @manifest_node.at_css('xmlns|title xmlns|langstring', 'xmlns' => Qti::QtiExporter::IMS_MD)
        @title = title.text
      end
    else
      @qti_data = opts[:qti_data]
    end

    @question = {:answers=>[],
                 :correct_comments=>"",
                 :incorrect_comments=>"",
                 :points_possible=>AssessmentItemConverter::DEFAULT_POINTS_POSSIBLE,
                 :question_text=>""}
  end

  #This should be implemented in the children classes to do the type-specific parsing
  def parse_question_data
    @log.error "No question type used..."
    raise "No question type used when trying to parse a qti question"
  end

  def create_doc
    create_xml_doc
  end

  def create_xml_doc
    if @manifest_node
      @doc = Nokogiri::XML(open(@href))
    else
      @doc = Nokogiri::XML(@qti_data)
    end
  end

  def create_instructure_question
    begin
      create_doc
      @question[:question_name] = @title || get_node_att(@doc, 'assessmentItem', 'title')
      # The colons are replaced with dashes in the conversion from QTI 1.2
      @question[:migration_id] = get_node_att(@doc, 'assessmentItem', 'identifier').gsub(/:/, '-')
      if @opts[:alternate_ids]
        # In D2L-generated QTI the assessments reference the items by the label instead of the identifier
        alt_id = get_node_att(@doc, 'assessmentItem', 'label')
        @opts[:alternate_ids][alt_id] = @question[:migration_id]
      end
      if @doc.at_css('itemBody div.html')
        @question[:question_text] = ''
        @doc.css('itemBody > div.html').each_with_index do |text, i|
          @question[:question_text] += "\n<br/>\n" if i > 0
          @question[:question_text] += sanitize_html_string(text.text)
        end
      elsif text = @doc.at_css('itemBody div:first-child') || @doc.at_css('itemBody p:first-child') || @doc.at_css('itemBody div') || @doc.at_css('itemBody p')
        @question[:question_text] = sanitize_html!(text)
      elsif @doc.at_css('itemBody')
        if text = @doc.at_css('itemBody').children.find{|c|c.text.strip != ''}
          @question[:question_text] = sanitize_html_string(text.text)
        end
      end
      parse_instructure_metadata

      if @migration_type and UNSUPPORTED_TYPES.member?(@migration_type)
        @question[:question_type] = @migration_type
        @question[:unsupported] = true
      elsif !%w(text_only_question).include?(@migration_type)
        self.parse_question_data
      end
    rescue => e
      message = "There was an error exporting an assessment question"
      @question[:qti_error] = "#{message} - #{e.to_s}"
      @question[:question_type] = "Error"
      @log.error "#{e.to_s}: #{e.backtrace}"
    end
    
    @question
  end

  QUESTION_TYPE_MAPPING = {
    /matching/i => 'matching_question',
    'textInformation' => 'text_only_question',
  }
  
  def parse_instructure_metadata
    if meta = @doc.at_css('instructureMetadata')
      if bank =  get_node_att(meta, 'instructureField[name=question_bank]',  'value')
        @question[:question_bank_name] = bank
      end
      if bank =  get_node_att(meta, 'instructureField[name=question_bank_iden]', 'value')
        @question[:question_bank_id] = bank<commit_msg>support respondus TF questions, refs #5386<commit_after>module Qti
class AssessmentItemConverter
  include Canvas::XMLHelper
  DEFAULT_CORRECT_WEIGHT = 100
  DEFAULT_INCORRECT_WEIGHT = 0
  DEFAULT_POINTS_POSSIBLE = 1
  UNSUPPORTED_TYPES = ['File Upload', 'Hot Spot', 'Quiz Bowl', 'WCT_JumbledSentence', 'file_upload_question']

  attr_reader :base_dir, :identifier, :href, :interaction_type, :title, :question

  def initialize(opts)
    @log = Canvas::Migration::logger
    reset_local_ids
    @manifest_node = opts[:manifest_node]
    @migration_type = opts[:interaction_type]
    @doc = nil
    @flavor = opts[:flavor]
    @opts = opts
    
    if @manifest_node
      @base_dir = opts[:base_dir]
      @identifier = @manifest_node['identifier']
      @href = File.join(@base_dir, @manifest_node['href'])
      if title = @manifest_node.at_css('title langstring') || title = @manifest_node.at_css('xmlns|title xmlns|langstring', 'xmlns' => Qti::QtiExporter::IMS_MD)
        @title = title.text
      end
    else
      @qti_data = opts[:qti_data]
    end

    @question = {:answers=>[],
                 :correct_comments=>"",
                 :incorrect_comments=>"",
                 :points_possible=>AssessmentItemConverter::DEFAULT_POINTS_POSSIBLE,
                 :question_text=>""}
  end

  #This should be implemented in the children classes to do the type-specific parsing
  def parse_question_data
    @log.error "No question type used..."
    raise "No question type used when trying to parse a qti question"
  end

  def create_doc
    create_xml_doc
  end

  def create_xml_doc
    if @manifest_node
      @doc = Nokogiri::XML(open(@href))
    else
      @doc = Nokogiri::XML(@qti_data)
    end
  end

  def create_instructure_question
    begin
      create_doc
      @question[:question_name] = @title || get_node_att(@doc, 'assessmentItem', 'title')
      # The colons are replaced with dashes in the conversion from QTI 1.2
      @question[:migration_id] = get_node_att(@doc, 'assessmentItem', 'identifier').gsub(/:/, '-')
      if @opts[:alternate_ids]
        # In D2L-generated QTI the assessments reference the items by the label instead of the identifier
        alt_id = get_node_att(@doc, 'assessmentItem', 'label')
        @opts[:alternate_ids][alt_id] = @question[:migration_id]
      end
      if @doc.at_css('itemBody div.html')
        @question[:question_text] = ''
        @doc.css('itemBody > div.html').each_with_index do |text, i|
          @question[:question_text] += "\n<br/>\n" if i > 0
          @question[:question_text] += sanitize_html_string(text.text)
        end
      elsif text = @doc.at_css('itemBody div:first-child') || @doc.at_css('itemBody p:first-child') || @doc.at_css('itemBody div') || @doc.at_css('itemBody p')
        @question[:question_text] = sanitize_html!(text)
      elsif @doc.at_css('itemBody')
        if text = @doc.at_css('itemBody').children.find{|c|c.text.strip != ''}
          @question[:question_text] = sanitize_html_string(text.text)
        end
      end
      parse_instructure_metadata

      if @migration_type and UNSUPPORTED_TYPES.member?(@migration_type)
        @question[:question_type] = @migration_type
        @question[:unsupported] = true
      elsif !%w(text_only_question).include?(@migration_type)
        self.parse_question_data
      end
    rescue => e
      message = "There was an error exporting an assessment question"
      @question[:qti_error] = "#{message} - #{e.to_s}"
      @question[:question_type] = "Error"
      @log.error "#{e.to_s}: #{e.backtrace}"
    end
    
    @question
  end

  QUESTION_TYPE_MAPPING = {
    /matching/i => 'matching_question',
    'textInformation' => 'text_only_question',
    'trueFalse' => 'true_false_question',
  }
  
  def parse_instructure_metadata
    if meta = @doc.at_css('instructureMetadata')
      if bank =  get_node_att(meta, 'instructureField[name=question_bank]',  'value')
        @question[:question_bank_name] = bank
      end
      if bank =  get_node_att(meta, 'instructureField[name=question_bank_iden]', 'value')
        @question[:question_bank_id] = bank
{'fraction_non_alphanumeric': 0.12467882050654594, 'fraction_numerical': 0.0026917900403768506, 'mean_word_length': 3.3548215237080448, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6543140', 'n_tokens_mistral': 2717, 'n_tokens_neox': 2646, 'n_words': 546}