qti 0.4.16 → 0.5.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4f6c8a03de219970a4b393485157b90247559323
4
- data.tar.gz: b92d0b520d3b4ed2f18ad89eb25c4b77435c55e1
3
+ metadata.gz: 4883be4fd0b3c4f0f662068c6b7d42e2cfee0645
4
+ data.tar.gz: 262e1f6ad0e5d3d80c844d30084bcb6eb461fcf0
5
5
  SHA512:
6
- metadata.gz: f1d379ca58f26bdb0d92db3a3f3929f90774a05104381c9653246a228d9dd3998b78c77b660fb74ae1a049e846bdf2eb8b042d0dcef27ee59781622efcdae95f
7
- data.tar.gz: 70bba648502519b9d9f6a43c7cdf97dddb141e5359e0e1391ea3db138397a4d0dfd630b593abe6d9a505f48e4bfdb728fc051177848e395f07a5dba6895fdf60
6
+ metadata.gz: beddd3cfcec82686646a5635773414d32502f58ea993719afb175f4ca9adaeed7ee9f6a622016618497a0a6f474915a0ff205d80fd0984f610ce1093675270ba
7
+ data.tar.gz: 81bc3c599b5c15b0c1437d1abf2fe985676d1f9d2ec2e433f5be5d1cac458822a113eb1e5e6290d995315a05984cf541a89453e4a900597bd5fc5d799762184b
data/lib/qti.rb CHANGED
@@ -62,7 +62,6 @@ require 'qti/v1/models/choices/fill_blank_choice'
62
62
  require 'qti/v1/models/scoring_data'
63
63
 
64
64
  require 'qti/v2/models/base'
65
- require 'qti/v2/models/interactions/base_interaction'
66
65
  require 'qti/v2/models/choices/simple_choice'
67
66
  require 'qti/v2/models/choices/simple_associable_choice'
68
67
  require 'qti/v2/models/choices/gap_match_choice'
@@ -3,18 +3,12 @@ module Qti
3
3
  module Models
4
4
  module Interactions
5
5
  class MatchInteraction < BaseInteraction
6
- attr_reader :node
7
-
8
6
  def self.matches(node)
9
7
  matches = node.xpath('.//xmlns:response_lid')
10
8
  return false if matches.count <= 1
11
9
  new(node)
12
10
  end
13
11
 
14
- def initialize(node)
15
- @node = node
16
- end
17
-
18
12
  def answers
19
13
  @answers ||= answer_nodes.map do |node|
20
14
  V1::Models::Choices::LogicalIdentifierChoice.new(node)
@@ -5,6 +5,13 @@ module Qti
5
5
  module Models
6
6
  module Choices
7
7
  class SimpleAssociableChoice < SimpleChoice
8
+ def match_max
9
+ @_match_max ||= @node.attributes['matchMax'].value || 0
10
+ end
11
+
12
+ def match_min
13
+ @_match_min ||= @node.attributes['matchMin'].value || 0
14
+ end
8
15
  end
9
16
  end
10
17
  end
@@ -6,6 +6,7 @@ module Qti
6
6
  module Choices
7
7
  class SimpleChoice < Qti::V2::Models::Base
8
8
  PROHIBITED_NODE_NAMES = %w(feedbackInline).join(',').freeze
9
+
9
10
  def initialize(node)
10
11
  @node = node
11
12
  end
@@ -1,4 +1,4 @@
1
- # Populate the subclasses
1
+ require 'qti/v2/models/interactions/base_interaction'
2
2
  Dir["#{__dir__}/interactions/*.rb"].each { |f| load f }
3
3
 
4
4
  module Qti
@@ -7,8 +7,6 @@ module Qti
7
7
  module Interactions
8
8
  # This one finds the correct parsing model based on the provided xml node
9
9
  def self.interaction_model(node)
10
- subclasses = constants.map { |c| const_get(c) }
11
-
12
10
  # Check for matches
13
11
  matches = subclasses.each_with_object([]) do |interaction_class, result|
14
12
  match = interaction_class.matches(node)
@@ -19,6 +17,12 @@ module Qti
19
17
 
20
18
  matches.first
21
19
  end
20
+
21
+ def self.subclasses
22
+ constants.map { |c| const_get(c) }
23
+ .reject { |c| c.name =~ /Implementations|Base/ }
24
+ .select { |c| c.name =~ /^Qti::V2.*Interaction$/ }
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -0,0 +1,69 @@
1
+ require_relative 'match_interaction'
2
+
3
+ module Qti
4
+ module V2
5
+ module Models
6
+ module Interactions
7
+ class CategorizationInteraction < MatchInteraction
8
+ def self.matches(node)
9
+ if use_match_interaction_implementation?(node)
10
+ new(node, MatchItemTagProcesssors::MatchInteractionTagProcessor)
11
+ else
12
+ false
13
+ end
14
+ end
15
+
16
+ def self.use_match_interaction_implementation?(node)
17
+ MatchItemTagProcesssors::MatchInteractionTagProcessor.match_interaction_tag?(node) &&
18
+ MatchItemTagProcesssors::MatchInteractionTagProcessor.number_of_questions_per_answer(node)
19
+ .any? { |n| n != 1 }
20
+ end
21
+
22
+ def scoring_data_structs
23
+ questions_categories_choices_hash.map do |category_choice, questions_choices|
24
+ ScoringData.new(category_choice.text, 'directedPair',
25
+ id: node_identifier(category_choice),
26
+ questions_ids: questions_choices.map { |q| node_identifier(q) })
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def questions_categories_choices_hash
33
+ category_choices.each_with_object({}) do |category_choice, hash|
34
+ hash[category_choice] = questions_for_category_choice(category_choice)
35
+ end
36
+ end
37
+
38
+ def questions_for_category_choice(choice)
39
+ choice_identifier = node_identifier(choice)
40
+ category_questions_ids = implementation.question_response_pairs
41
+ .select { |_, category_id| category_id == choice_identifier }
42
+ .map { |question_id, _| question_id }
43
+ question_choices.select { |q| category_questions_ids.include? node_identifier(q) }
44
+ end
45
+
46
+ def category_choices
47
+ @_category_choices ||= implementation.choices.select { |c| categories_ids.include? node_identifier(c) }
48
+ end
49
+
50
+ def question_choices
51
+ @_question_choices ||= implementation.choices.select { |c| questions_ids.include? node_identifier(c) }
52
+ end
53
+
54
+ def categories_ids
55
+ @_categories_ids ||= implementation.question_response_pairs.map { |_, category_id| category_id }.uniq
56
+ end
57
+
58
+ def questions_ids
59
+ @_questions_ids ||= implementation.question_response_pairs.map { |question_id, _| question_id }.uniq
60
+ end
61
+
62
+ def node_identifier(node)
63
+ node.attributes['identifier'].value
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,51 +1,48 @@
1
+ require_relative 'match_item_tag_processors/match_interaction_tag_processor'
2
+ require_relative 'match_item_tag_processors/associate_interaction_tag_processor'
3
+
1
4
  module Qti
2
5
  module V2
3
6
  module Models
4
7
  module Interactions
5
8
  class MatchInteraction < BaseInteraction
6
- def self.matches(node)
7
- matches = node.xpath('//xmlns:matchInteraction')
8
- return false if matches.count != 1
9
- new(node)
10
- end
9
+ extend Forwardable
11
10
 
12
- def questions
13
- node.xpath('.//xmlns:correctResponse//xmlns:value')
14
- .map { |value| value.content.split.first }
15
- .map { |id| { id: id, question_body: choices_by_identifier[id].content } }
16
- end
11
+ attr_reader :implementation
17
12
 
18
- def shuffled?
19
- node.at_xpath('.//xmlns:matchInteraction').attributes['shuffle']&.value.try(:downcase) == 'true'
13
+ def initialize(node, implementation)
14
+ super(node)
15
+ @implementation = implementation.new(node)
20
16
  end
21
17
 
22
- def answers
23
- answer_nodes.map { |node| Choices::SimpleAssociableChoice.new(node) }
24
- end
18
+ def_delegators :@implementation, :answers, :questions, :shuffled?
25
19
 
26
20
  def scoring_data_structs
27
- question_response_pairs = node.xpath('.//xmlns:correctResponse//xmlns:value').map do |value|
28
- value.content.split
29
- end
30
- question_response_id_mapping = Hash[question_response_pairs]
31
- data = question_response_id_mapping.reduce({}) do |acc, pair|
32
- question_id, answer_id = pair
33
- acc.update question_id => choices_by_identifier[answer_id].content
34
- end
35
- [ScoringData.new(data, 'directedPair')]
21
+ implementation.scoring_data_structs
36
22
  end
37
23
 
38
- private
24
+ def self.matches(node)
25
+ implementation =
26
+ if use_associate_interaction_implementation?(node)
27
+ MatchItemTagProcesssors::AssociateInteractionTagProcessor
28
+ elsif use_match_interaction_implementation?(node)
29
+ MatchItemTagProcesssors::MatchInteractionTagProcessor
30
+ end
31
+
32
+ return false unless implementation.present?
33
+ new(node, implementation)
34
+ end
39
35
 
40
- def choices_by_identifier
41
- choices = node.xpath('.//xmlns:simpleAssociableChoice')
42
- @choices_by_identifier ||= choices.reduce({}) do |acc, choice|
43
- acc.update choice.attributes['identifier'].value => choice
44
- end
36
+ def self.use_associate_interaction_implementation?(node)
37
+ MatchItemTagProcesssors::AssociateInteractionTagProcessor.associate_interaction_tag?(node) &&
38
+ MatchItemTagProcesssors::AssociateInteractionTagProcessor.number_of_questions_per_answer(node)
39
+ .all? { |n| n == 1 }
45
40
  end
46
41
 
47
- def answer_nodes
48
- node.xpath('.//xmlns:matchInteraction//xmlns:simpleMatchSet[2]//xmlns:simpleAssociableChoice')
42
+ def self.use_match_interaction_implementation?(node)
43
+ MatchItemTagProcesssors::MatchInteractionTagProcessor.match_interaction_tag?(node) &&
44
+ MatchItemTagProcesssors::MatchInteractionTagProcessor.number_of_questions_per_answer(node)
45
+ .all? { |n| n == 1 }
49
46
  end
50
47
  end
51
48
  end
@@ -0,0 +1,75 @@
1
+ module Qti
2
+ module V2
3
+ module Models
4
+ module Interactions
5
+ module MatchItemTagProcesssors
6
+ class AssociateInteractionTagProcessor < Interactions::BaseInteraction
7
+ def self.associate_interaction_tag?(node)
8
+ node.xpath('.//xmlns:associateInteraction').count == 1
9
+ end
10
+
11
+ def self.number_of_questions_per_answer(node)
12
+ question_response_pairs = node.xpath('.//xmlns:correctResponse//xmlns:value').map do |value|
13
+ value.content.split
14
+ end
15
+ count = Hash.new { 0 }
16
+ question_response_pairs.reduce(count) do |acc, pair|
17
+ acc.update pair.last => acc[pair.last] + 1
18
+ end
19
+ count.values
20
+ end
21
+
22
+ def shuffled?
23
+ node.at_xpath('.//xmlns:associateInteraction').attributes['shuffle']&.value.try(:downcase) == 'true'
24
+ end
25
+
26
+ def questions
27
+ questions_ids.map { |id| { id: id, question_body: choices_by_identifier[id].content } }
28
+ end
29
+
30
+ def answers
31
+ answer_nodes.map { |node| Choices::SimpleAssociableChoice.new(node) }
32
+ end
33
+
34
+ def scoring_data_structs
35
+ question_response_pairs.map do |question_id, answer_id|
36
+ content = choices_by_identifier[answer_id].content
37
+ ScoringData.new(content, 'Pair', id: answer_id, question_id: question_id)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def choices_by_identifier
44
+ @_choices_by_identifier ||= choices.reduce({}) do |acc, choice|
45
+ acc.update choice.attributes['identifier'].value => choice
46
+ end
47
+ end
48
+
49
+ def answer_nodes
50
+ @_answer_nodes ||= choices.select { |c| answer_ids.include?(c.attributes['identifier'].value) }
51
+ end
52
+
53
+ def questions_ids
54
+ @_questions_ids ||= question_response_pairs.map { |question_id, _| question_id }
55
+ end
56
+
57
+ def answer_ids
58
+ @_answer_ids ||= question_response_pairs.map { |_, answer_id| answer_id }
59
+ end
60
+
61
+ def choices
62
+ node.xpath('.//xmlns:simpleAssociableChoice')
63
+ end
64
+
65
+ def question_response_pairs
66
+ node.xpath('.//xmlns:correctResponse//xmlns:value').map do |value|
67
+ value.content.split
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,67 @@
1
+ module Qti
2
+ module V2
3
+ module Models
4
+ module Interactions
5
+ module MatchItemTagProcesssors
6
+ class MatchInteractionTagProcessor < Interactions::BaseInteraction
7
+ def self.match_interaction_tag?(node)
8
+ node.xpath('//xmlns:matchInteraction').count == 1
9
+ end
10
+
11
+ def self.number_of_questions_per_answer(node)
12
+ correct_categories = node.xpath('.//xmlns:correctResponse//xmlns:value')
13
+ .map { |value| value.content.split.last }
14
+
15
+ correct_categories.each_with_object(Hash.new(0)) { |category_id, counts| counts[category_id] += 1 }.values
16
+ end
17
+
18
+ def questions
19
+ questions_ids.map { |id| { id: id, question_body: choices_by_identifier[id].content } }
20
+ end
21
+
22
+ def shuffled?
23
+ node.at_xpath('.//xmlns:matchInteraction').attributes['shuffle']&.value.try(:downcase) == 'true'
24
+ end
25
+
26
+ def answers
27
+ answer_nodes.map { |node| Choices::SimpleAssociableChoice.new(node) }
28
+ end
29
+
30
+ def scoring_data_structs
31
+ question_response_pairs.map do |question_id, answer_id|
32
+ content = choices_by_identifier[answer_id].content
33
+ ScoringData.new(content, 'Pair', id: answer_id, question_id: question_id)
34
+ end
35
+ end
36
+
37
+ def choices
38
+ node.xpath('.//xmlns:simpleAssociableChoice')
39
+ end
40
+
41
+ def question_response_pairs
42
+ node.xpath('.//xmlns:correctResponse//xmlns:value').map do |value|
43
+ value.content.split
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def questions_ids
50
+ @_questions_ids ||= question_response_pairs.map { |question_id, _| question_id }
51
+ end
52
+
53
+ def choices_by_identifier
54
+ @choices_by_identifier ||= choices.reduce({}) do |acc, choice|
55
+ acc.update choice.attributes['identifier'].value => choice
56
+ end
57
+ end
58
+
59
+ def answer_nodes
60
+ node.xpath('.//xmlns:matchInteraction//xmlns:simpleMatchSet[2]//xmlns:simpleAssociableChoice')
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,9 +1,9 @@
1
1
  module Qti
2
2
  module V2
3
3
  module Models
4
- ScoringData = Struct.new(:values, :type, :id, :case) do
5
- def initialize(values, rcardinality, options={})
6
- super(values, rcardinality, options[:id], options[:case])
4
+ ScoringData = Struct.new(:values, :type, :id, :case, :question_id, :questions_ids) do
5
+ def initialize(values, type, options={})
6
+ super(values, type, options[:id], options[:case], options[:question_id], options[:questions_ids])
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,35 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p2 http://www.imsglobal.org/xsd/qti/qtiv2p2/imsqti_v2p2.xsd" identifier="match" title="Characters and Plays" adaptive="false" timeDependent="false">
3
+ <responseDeclaration identifier="RESPONSE" cardinality="multiple" baseType="directedPair">
4
+ <correctResponse>
5
+ <value>C R</value>
6
+ <value>D M</value>
7
+ <value>L M</value>
8
+ <value>P T</value>
9
+ </correctResponse>
10
+ <mapping defaultValue="0">
11
+ <mapEntry mapKey="C R" mappedValue="1"/>
12
+ <mapEntry mapKey="D M" mappedValue="0.5"/>
13
+ <mapEntry mapKey="L M" mappedValue="0.5"/>
14
+ <mapEntry mapKey="P T" mappedValue="1"/>
15
+ </mapping>
16
+ </responseDeclaration>
17
+ <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
18
+ <itemBody>
19
+ <matchInteraction responseIdentifier="RESPONSE" shuffle="true" maxAssociations="4">
20
+ <prompt>Match the following characters to the Shakespeare play they appeared in:</prompt>
21
+ <simpleMatchSet>
22
+ <simpleAssociableChoice identifier="C" matchMax="1">Capulet</simpleAssociableChoice>
23
+ <simpleAssociableChoice identifier="D" matchMax="1">Demetrius</simpleAssociableChoice>
24
+ <simpleAssociableChoice identifier="L" matchMax="1">Lysander</simpleAssociableChoice>
25
+ <simpleAssociableChoice identifier="P" matchMax="1">Prospero</simpleAssociableChoice>
26
+ </simpleMatchSet>
27
+ <simpleMatchSet>
28
+ <simpleAssociableChoice identifier="M" matchMax="4">A Midsummer-Night's Dream</simpleAssociableChoice>
29
+ <simpleAssociableChoice identifier="R" matchMax="4">Romeo and Juliet</simpleAssociableChoice>
30
+ <simpleAssociableChoice identifier="T" matchMax="4">The Tempest</simpleAssociableChoice>
31
+ </simpleMatchSet>
32
+ </matchInteraction>
33
+ </itemBody>
34
+ <responseProcessing template="http://www.imsglobal.org/question/qti_v2p2/rptemplates/map_response"/>
35
+ </assessmentItem>
@@ -1,35 +1,28 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p2 http://www.imsglobal.org/xsd/qti/qtiv2p2/imsqti_v2p2.xsd" identifier="match" title="Characters and Plays" adaptive="false" timeDependent="false">
3
- <responseDeclaration identifier="RESPONSE" cardinality="multiple" baseType="directedPair">
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_v2p1p2.xsd" identifier="associate" title="Shakespearian Rivals" adaptive="false" timeDependent="false">
3
+ <responseDeclaration identifier="RESPONSE" cardinality="multiple" baseType="pair">
4
4
  <correctResponse>
5
- <value>C R</value>
6
- <value>D M</value>
7
- <value>L M</value>
8
- <value>P T</value>
5
+ <value>A P</value>
6
+ <value>C M</value>
7
+ <value>D L</value>
9
8
  </correctResponse>
10
9
  <mapping defaultValue="0">
11
- <mapEntry mapKey="C R" mappedValue="1"/>
12
- <mapEntry mapKey="D M" mappedValue="0.5"/>
13
- <mapEntry mapKey="L M" mappedValue="0.5"/>
14
- <mapEntry mapKey="P T" mappedValue="1"/>
10
+ <mapEntry mapKey="A P" mappedValue="2"/>
11
+ <mapEntry mapKey="C M" mappedValue="1"/>
12
+ <mapEntry mapKey="D L" mappedValue="1"/>
15
13
  </mapping>
16
14
  </responseDeclaration>
17
15
  <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
18
16
  <itemBody>
19
- <matchInteraction responseIdentifier="RESPONSE" shuffle="true" maxAssociations="4">
20
- <prompt>Match the following characters to the Shakespeare play they appeared in:</prompt>
21
- <simpleMatchSet>
22
- <simpleAssociableChoice identifier="C" matchMax="1">Capulet</simpleAssociableChoice>
23
- <simpleAssociableChoice identifier="D" matchMax="1">Demetrius</simpleAssociableChoice>
24
- <simpleAssociableChoice identifier="L" matchMax="1">Lysander</simpleAssociableChoice>
25
- <simpleAssociableChoice identifier="P" matchMax="1">Prospero</simpleAssociableChoice>
26
- </simpleMatchSet>
27
- <simpleMatchSet>
28
- <simpleAssociableChoice identifier="M" matchMax="4">A Midsummer-Night's Dream</simpleAssociableChoice>
29
- <simpleAssociableChoice identifier="R" matchMax="4">Romeo and Juliet</simpleAssociableChoice>
30
- <simpleAssociableChoice identifier="T" matchMax="4">The Tempest</simpleAssociableChoice>
31
- </simpleMatchSet>
32
- </matchInteraction>
17
+ <associateInteraction responseIdentifier="RESPONSE" shuffle="true" maxAssociations="3">
18
+ <prompt>Hidden in this list of characters from famous Shakespeare plays are three pairs of rivals. Can you match each character to his adversary?</prompt>
19
+ <simpleAssociableChoice identifier="A" matchMax="1">Antonio</simpleAssociableChoice>
20
+ <simpleAssociableChoice identifier="C" matchMax="1">Capulet</simpleAssociableChoice>
21
+ <simpleAssociableChoice identifier="D" matchMax="1">Demetrius</simpleAssociableChoice>
22
+ <simpleAssociableChoice identifier="L" matchMax="1">Lysander</simpleAssociableChoice>
23
+ <simpleAssociableChoice identifier="M" matchMax="1">Montague</simpleAssociableChoice>
24
+ <simpleAssociableChoice identifier="P" matchMax="1">Prospero</simpleAssociableChoice>
25
+ </associateInteraction>
33
26
  </itemBody>
34
- <responseProcessing template="http://www.imsglobal.org/question/qti_v2p2/rptemplates/map_response"/>
27
+ <responseProcessing template="http://www.imsglobal.org/question/qti_v2p1/rptemplates/map_response"/>
35
28
  </assessmentItem>
@@ -0,0 +1,119 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--This file was created using: Onyx Editor, Version 3.4.3 (Full, licensed version), (c)2012 BPS Bildungsportal Sachsen GmbH-->
3
+ <assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p2 http://www.imsglobal.org/xsd/qti/qtiv2p2/imsqti_v2p2.xsd" identifier="Matchsingle_143114773" title="match (single)" adaptive="false" timeDependent="false">
4
+ <responseDeclaration identifier="RESPONSE" cardinality="multiple" baseType="directedPair">
5
+ <correctResponse>
6
+ <value>Match28433682 Match2675678</value>
7
+ <value>Match7191791 Match9372581</value>
8
+ <value>Match20473010 Match22744006</value>
9
+ <value>Match6429655 Match17943221</value>
10
+ </correctResponse>
11
+ </responseDeclaration>
12
+ <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float">
13
+ <defaultValue>
14
+ <value>0.0</value>
15
+ </defaultValue>
16
+ </outcomeDeclaration>
17
+ <outcomeDeclaration identifier="MAXSCORE" cardinality="single" baseType="float">
18
+ <defaultValue>
19
+ <value>1.0</value>
20
+ </defaultValue>
21
+ </outcomeDeclaration>
22
+ <outcomeDeclaration identifier="FEEDBACKBASIC" cardinality="single" baseType="identifier">
23
+ <defaultValue>
24
+ <value>empty</value>
25
+ </defaultValue>
26
+ </outcomeDeclaration>
27
+ <outcomeDeclaration identifier="FEEDBACKMODAL" cardinality="multiple" baseType="identifier" view="testConstructor"/>
28
+ <itemBody>
29
+ <div>Der wichtigste, größte und einzig schiffbare Fluss des Bundeslandes Sachsens ist die
30
+ Elbe. Die verschiedene Quellflüsse der Elbe durchziehen die sächsischen Städte.
31
+ <br/>Ordne anhand des Flusslaufs die Quellflüsse den gegebenen Städten zu.</div>
32
+ <matchInteraction responseIdentifier="RESPONSE" shuffle="true" maxAssociations="0">
33
+ <simpleMatchSet>
34
+ <simpleAssociableChoice identifier="Match28433682" fixed="false" matchMin="1" matchMax="1">Weißeritz</simpleAssociableChoice>
35
+ <simpleAssociableChoice identifier="Match7191791" fixed="false" matchMin="1" matchMax="1">Mulde</simpleAssociableChoice>
36
+ <simpleAssociableChoice identifier="Match20473010" fixed="false" matchMin="1" matchMax="1">Saale</simpleAssociableChoice>
37
+ <simpleAssociableChoice identifier="Match6429655" fixed="false" matchMin="1" matchMax="1">Spree</simpleAssociableChoice>
38
+ </simpleMatchSet>
39
+ <simpleMatchSet>
40
+ <simpleAssociableChoice identifier="Match2675678" fixed="false" matchMin="1" matchMax="1">Dresden</simpleAssociableChoice>
41
+ <simpleAssociableChoice identifier="Match9372581" fixed="false" matchMin="1" matchMax="1">Leipzig</simpleAssociableChoice>
42
+ <simpleAssociableChoice identifier="Match22744006" fixed="false" matchMin="1" matchMax="1">Halle</simpleAssociableChoice>
43
+ <simpleAssociableChoice identifier="Match17943221" fixed="false" matchMin="1" matchMax="1">Bautzen</simpleAssociableChoice>
44
+ </simpleMatchSet>
45
+ </matchInteraction>
46
+ </itemBody>
47
+ <responseProcessing>
48
+ <responseCondition>
49
+ <responseIf>
50
+ <isNull>
51
+ <variable identifier="RESPONSE"/>
52
+ </isNull>
53
+ <setOutcomeValue identifier="FEEDBACKBASIC">
54
+ <baseValue baseType="identifier">empty</baseValue>
55
+ </setOutcomeValue>
56
+ </responseIf>
57
+ <responseElseIf>
58
+ <match>
59
+ <variable identifier="RESPONSE"/>
60
+ <correct identifier="RESPONSE"/>
61
+ </match>
62
+ <setOutcomeValue identifier="SCORE">
63
+ <sum>
64
+ <variable identifier="SCORE"/>
65
+ <variable identifier="MAXSCORE"/>
66
+ </sum>
67
+ </setOutcomeValue>
68
+ <setOutcomeValue identifier="FEEDBACKBASIC">
69
+ <baseValue baseType="identifier">correct</baseValue>
70
+ </setOutcomeValue>
71
+ </responseElseIf>
72
+ <responseElse>
73
+ <setOutcomeValue identifier="FEEDBACKBASIC">
74
+ <baseValue baseType="identifier">incorrect</baseValue>
75
+ </setOutcomeValue>
76
+ </responseElse>
77
+ </responseCondition>
78
+ <responseCondition>
79
+ <responseIf>
80
+ <and>
81
+ <match>
82
+ <baseValue baseType="identifier">correct</baseValue>
83
+ <variable identifier="FEEDBACKBASIC"/>
84
+ </match>
85
+ </and>
86
+ <setOutcomeValue identifier="FEEDBACKMODAL">
87
+ <multiple>
88
+ <variable identifier="FEEDBACKMODAL"/>
89
+ <baseValue baseType="identifier">FEEDBACK_27448254</baseValue>
90
+ </multiple>
91
+ </setOutcomeValue>
92
+ </responseIf>
93
+ </responseCondition>
94
+ <responseCondition>
95
+ <responseIf>
96
+ <and>
97
+ <match>
98
+ <baseValue baseType="identifier">incorrect</baseValue>
99
+ <variable identifier="FEEDBACKBASIC"/>
100
+ </match>
101
+ </and>
102
+ <setOutcomeValue identifier="FEEDBACKMODAL">
103
+ <multiple>
104
+ <variable identifier="FEEDBACKMODAL"/>
105
+ <baseValue baseType="identifier">FEEDBACK_16479834</baseValue>
106
+ </multiple>
107
+ </setOutcomeValue>
108
+ </responseIf>
109
+ </responseCondition>
110
+ </responseProcessing>
111
+ <modalFeedback identifier="FEEDBACK_27448254" outcomeIdentifier="FEEDBACKMODAL" showHide="show" title="Richtig!">Die Elbe durchzieht den Freistaat von Südosten nach Nordwesten. Wichtige
112
+ Quellflüsse sind die Mulde, die Weißeritz, die Zschopau, die Weiße Elster und die Spree,
113
+ deren allgemeine Fließrichtung Norden ist und die ebenfalls zum Fluss-System der Elbe
114
+ gehören.</modalFeedback>
115
+ <modalFeedback identifier="FEEDBACK_16479834" outcomeIdentifier="FEEDBACKMODAL" showHide="show" title="Leider Falsch!">Die Elbe durchzieht den Freistaat von Südosten nach Nordwesten.
116
+ Wichtige Quellflüsse sind die Mulde, die Weißeritz, die Zschopau, die Weiße Elster und die
117
+ Spree, deren allgemeine Fließrichtung Norden ist und die ebenfalls zum Fluss-System der Elbe
118
+ gehören.</modalFeedback>
119
+ </assessmentItem>
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Qti::V1::Models::Interactions::MatchInteraction do
4
+ let(:file) { File.join('spec', 'fixtures', 'items_1.2', 'matching.xml') }
5
+ let(:assessment) { Qti::V1::Models::Assessment.from_path!(file) }
6
+ let(:item) { assessment.assessment_items.first }
7
+ subject { described_class.new(item) }
8
+
9
+ it 'returns shuffle setting' do
10
+ expect(subject.shuffled?).to eq false
11
+ end
12
+
13
+ it 'returns the answers' do
14
+ expect(subject.answers.map(&:item_body)).to eq(
15
+ ['Magnify up to about 400 times. Sometimes requires colored staining of cells.',
16
+ "Uses a beam of electrons. Can provide details of cells' internal structure.",
17
+ 'A distractor answer.']
18
+ )
19
+ end
20
+
21
+ describe '.matches' do
22
+ it 'matches the item in file' do
23
+ expect(described_class.matches(item)).to be_truthy
24
+ end
25
+ end
26
+
27
+ describe '#scoring_data_structs' do
28
+ it 'grabs scoring data value for matching questions' do
29
+ expect(subject.scoring_data_structs.first.values).to eq(
30
+ 'question_1' => 'Magnify up to about 400 times. Sometimes requires colored staining of cells.',
31
+ 'question_2' => "Uses a beam of electrons. Can provide details of cells' internal structure."
32
+ )
33
+ end
34
+ end
35
+
36
+ describe '#questions' do
37
+ it 'returns the questions' do
38
+ expect(subject.questions).to eq(
39
+ [{ id: 'question_1', question_body: 'Light Microscope' },
40
+ { id: 'question_2', question_body: 'Electron Microscopes' }]
41
+ )
42
+ end
43
+ end
44
+ end
@@ -64,28 +64,4 @@ describe Qti::V2::Models::AssessmentItem do
64
64
  end
65
65
  end
66
66
  end
67
-
68
- describe '#scoring_data_structs' do
69
- it 'grabs scoring data value for matching questions' do
70
- assessment_item = Qti::V2::Models::AssessmentItem.from_path! 'spec/fixtures/items_2.1/match.xml'
71
- expect(assessment_item.interaction_model).to be_instance_of Qti::V2::Models::Interactions::MatchInteraction
72
- expect(assessment_item.scoring_data_structs.first.values).to eq(
73
- 'C' => 'Romeo and Juliet',
74
- 'D' => "A Midsummer-Night's Dream",
75
- 'L' => "A Midsummer-Night's Dream",
76
- 'P' => 'The Tempest'
77
- )
78
- expect(assessment_item.interaction_model.questions).to eq(
79
- [{ id: 'C', question_body: 'Capulet' },
80
- { id: 'D', question_body: 'Demetrius' },
81
- { id: 'L', question_body: 'Lysander' },
82
- { id: 'P', question_body: 'Prospero' }]
83
- )
84
- expect(assessment_item.interaction_model.answers.map(&:item_body)).to eq(
85
- ["A Midsummer-Night's Dream",
86
- 'Romeo and Juliet',
87
- 'The Tempest']
88
- )
89
- end
90
- end
91
67
  end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Qti::V2::Models::Choices::SimpleAssociableChoice do
4
+ let(:io) { File.read('spec/fixtures/items_2.1/match2.xml') }
5
+ let(:nodes) { Nokogiri::XML(io).xpath('//xmlns:simpleAssociableChoice') }
6
+ let(:choices) { nodes.map { |node| described_class.new(node) } }
7
+
8
+ it 'returns the expected attributes' do
9
+ expected_attrs = [
10
+ %w(Match28433682 1 1 Weißeritz),
11
+ %w(Match7191791 1 1 Mulde),
12
+ %w(Match20473010 1 1 Saale),
13
+ %w(Match6429655 1 1 Spree),
14
+ %w(Match2675678 1 1 Dresden),
15
+ %w(Match9372581 1 1 Leipzig),
16
+ %w(Match22744006 1 1 Halle),
17
+ %w(Match17943221 1 1 Bautzen)
18
+ ]
19
+ expect(choices.map { |c| [c.identifier, c.match_min, c.match_max, c.item_body] }).to eq expected_attrs
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Qti::V2::Models::Interactions::CategorizationInteraction do
4
+ let(:assessment_item) { Qti::V2::Models::AssessmentItem.from_path! 'spec/fixtures/items_2.1/categorization.xml' }
5
+ subject { assessment_item.interaction_model }
6
+
7
+ it { is_expected.to be_an_instance_of(described_class) }
8
+
9
+ it 'returns shuffle setting' do
10
+ expect(subject.shuffled?).to eq true
11
+ end
12
+
13
+ describe '#scoring_data_structs' do
14
+ it 'returns the scoring data for categorization questions' do
15
+ expected_scoring_data_structs = [
16
+ Qti::V2::Models::ScoringData.new("A Midsummer-Night's Dream", "directedPair", id: "M", questions_ids: ["D", "L"]),
17
+ Qti::V2::Models::ScoringData.new('Romeo and Juliet', "directedPair", id: "R", questions_ids: ['C']),
18
+ Qti::V2::Models::ScoringData.new('The Tempest', "directedPair", id: "T", questions_ids: ['P']),
19
+ ]
20
+ expect(subject.scoring_data_structs).to eq expected_scoring_data_structs
21
+ end
22
+ end
23
+ end
@@ -1,15 +1,79 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Qti::V2::Models::Interactions::MatchInteraction do
4
- let(:file) { File.read(File.join('spec', 'fixtures', 'items_2.1', 'match.xml')) }
5
- let(:node) { Nokogiri::XML(file) }
6
- subject { described_class.new(node) }
4
+ context 'assessmentItem is implemented with <associateInteraction>' do
5
+ let(:assessment_item) { Qti::V2::Models::AssessmentItem.from_path! 'spec/fixtures/items_2.1/match.xml' }
6
+ subject { assessment_item.interaction_model }
7
7
 
8
- it 'returns shuffle setting' do
9
- expect(subject.shuffled?).to eq true
8
+ it { is_expected.to be_an_instance_of(described_class) }
9
+
10
+ it 'returns shuffle setting' do
11
+ expect(subject.shuffled?).to eq true
12
+ end
13
+
14
+ describe '#scoring_data_structs' do
15
+ it 'grabs scoring data value for matching questions' do
16
+ expect(subject.scoring_data_structs).to eq [
17
+ Qti::V2::Models::ScoringData.new('Prospero', 'Pair', id: 'P', question_id: 'A'),
18
+ Qti::V2::Models::ScoringData.new('Montague', 'Pair', id: 'M', question_id: 'C'),
19
+ Qti::V2::Models::ScoringData.new('Lysander', 'Pair', id: 'L', question_id: 'D')
20
+ ]
21
+ end
22
+ end
23
+
24
+ describe '#answers' do
25
+ it 'returns the correct_answers' do
26
+ expect(subject.answers.map(&:item_body)).to eq(%w[Lysander Montague Prospero])
27
+ end
28
+ end
29
+
30
+ describe '#questions' do
31
+ it 'returns the questions' do
32
+ expect(subject.questions).to eq(
33
+ [{ id: 'A', question_body: 'Antonio' },
34
+ { id: 'C', question_body: 'Capulet' },
35
+ { id: 'D', question_body: 'Demetrius' }]
36
+ )
37
+ end
38
+ end
10
39
  end
11
40
 
12
- it 'returns the answers' do
13
- expect(subject.answers.map(&:item_body)).to eq ["A Midsummer-Night's Dream", 'Romeo and Juliet', 'The Tempest']
41
+ context 'assessmentItem is implemented with <matchInteraction>' do
42
+ let(:assessment_item) { Qti::V2::Models::AssessmentItem.from_path! 'spec/fixtures/items_2.1/match2.xml' }
43
+ subject { assessment_item.interaction_model }
44
+
45
+ it { is_expected.to be_an_instance_of(described_class) }
46
+
47
+ it 'returns shuffle setting' do
48
+ expect(subject.shuffled?).to eq true
49
+ end
50
+
51
+ describe '#scoring_data_structs' do
52
+ it 'grabs scoring data value for matching questions' do
53
+ expect(subject.scoring_data_structs).to eq [
54
+ Qti::V2::Models::ScoringData.new('Dresden', 'Pair', id: 'Match2675678', question_id: 'Match28433682'),
55
+ Qti::V2::Models::ScoringData.new('Leipzig', 'Pair', id: 'Match9372581', question_id: 'Match7191791'),
56
+ Qti::V2::Models::ScoringData.new('Halle' , 'Pair', id: 'Match22744006', question_id: 'Match20473010'),
57
+ Qti::V2::Models::ScoringData.new('Bautzen', 'Pair', id: 'Match17943221', question_id: 'Match6429655')
58
+ ]
59
+ end
60
+ end
61
+
62
+ describe '#answers' do
63
+ it 'returns the correct_answers' do
64
+ expect(subject.answers.map(&:item_body)).to eq %w[Dresden Leipzig Halle Bautzen]
65
+ end
66
+ end
67
+
68
+ describe '#questions' do
69
+ it 'returns the questions' do
70
+ expect(subject.questions).to eq(
71
+ [{ id: 'Match28433682', question_body: 'Weißeritz' },
72
+ { id: 'Match7191791', question_body: 'Mulde' },
73
+ { id: 'Match20473010', question_body: 'Saale' },
74
+ { id: 'Match6429655', question_body: 'Spree' }]
75
+ )
76
+ end
77
+ end
14
78
  end
15
79
  end
@@ -0,0 +1,14 @@
1
+ # ObjectSpace.each_object(Class).select { |c| c.name =~ /V1.*(?<!Base)Interaction$/ }.each do |klass|
2
+ # klass.class_eval do
3
+ # class << self
4
+ # alias_method :old_matches?, :matches?
5
+ # def matches?(node)
6
+ # raise ArgumentError.new("Expected Nokogiri::XML::Element and got #{node.class}") unless item_element?(node)
7
+ # old_matches?(node)
8
+ # end
9
+ # def item_element?(node)
10
+ # node.class == Nokogiri::XML::Element && node.name == 'item'
11
+ # end
12
+ # end
13
+ # end
14
+ # end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qti
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.16
4
+ version: 0.5.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hannah Bottalla
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-05-12 00:00:00.000000000 Z
12
+ date: 2017-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -165,6 +165,20 @@ dependencies:
165
165
  - - ">="
166
166
  - !ruby/object:Gem::Version
167
167
  version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: rubocop
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
168
182
  - !ruby/object:Gem::Dependency
169
183
  name: simplecov
170
184
  requirement: !ruby/object:Gem::Requirement
@@ -223,10 +237,13 @@ files:
223
237
  - lib/qti/v2/models/choices/simple_choice.rb
224
238
  - lib/qti/v2/models/interactions.rb
225
239
  - lib/qti/v2/models/interactions/base_interaction.rb
240
+ - lib/qti/v2/models/interactions/categorization_interaction.rb
226
241
  - lib/qti/v2/models/interactions/choice_interaction.rb
227
242
  - lib/qti/v2/models/interactions/extended_text_interaction.rb
228
243
  - lib/qti/v2/models/interactions/gap_match_interaction.rb
229
244
  - lib/qti/v2/models/interactions/match_interaction.rb
245
+ - lib/qti/v2/models/interactions/match_item_tag_processors/associate_interaction_tag_processor.rb
246
+ - lib/qti/v2/models/interactions/match_item_tag_processors/match_interaction_tag_processor.rb
230
247
  - lib/qti/v2/models/scoring_data.rb
231
248
  - spec/fixtures/items_1.2/choice.xml
232
249
  - spec/fixtures/items_1.2/essay.xml
@@ -247,6 +264,7 @@ files:
247
264
  - spec/fixtures/items_2.1/adaptive_template.xml
248
265
  - spec/fixtures/items_2.1/associate.xml
249
266
  - spec/fixtures/items_2.1/audio-video.xml
267
+ - spec/fixtures/items_2.1/categorization.xml
250
268
  - spec/fixtures/items_2.1/choice.xml
251
269
  - spec/fixtures/items_2.1/choice_aria.xml
252
270
  - spec/fixtures/items_2.1/choice_fixed.xml
@@ -304,6 +322,7 @@ files:
304
322
  - spec/fixtures/items_2.1/inline_choice_math.xml
305
323
  - spec/fixtures/items_2.1/likert.xml
306
324
  - spec/fixtures/items_2.1/match.xml
325
+ - spec/fixtures/items_2.1/match2.xml
307
326
  - spec/fixtures/items_2.1/math.xml
308
327
  - spec/fixtures/items_2.1/mc_calc3.xml
309
328
  - spec/fixtures/items_2.1/mc_calc5.xml
@@ -422,11 +441,14 @@ files:
422
441
  - spec/lib/qti/v1/models/choices/logical_identifier_choice_spec.rb
423
442
  - spec/lib/qti/v1/models/interactions/choice_interaction_spec.rb
424
443
  - spec/lib/qti/v1/models/interactions/fill_blank_interaction_spec.rb
444
+ - spec/lib/qti/v1/models/interactions/match_interaction_spec.rb
425
445
  - spec/lib/qti/v1/models/interactions/string_interaction_spec.rb
426
446
  - spec/lib/qti/v2/models/assessment_item_spec.rb
427
447
  - spec/lib/qti/v2/models/assessment_test_spec.rb
428
448
  - spec/lib/qti/v2/models/choices/gap_match_choice_spec.rb
449
+ - spec/lib/qti/v2/models/choices/simple_associable_choice_spec.rb
429
450
  - spec/lib/qti/v2/models/choices/simple_choice_spec.rb
451
+ - spec/lib/qti/v2/models/interactions/categorization_interaction_spec.rb
430
452
  - spec/lib/qti/v2/models/interactions/choice_interaction_spec.rb
431
453
  - spec/lib/qti/v2/models/interactions/extended_text_interaction_spec.rb
432
454
  - spec/lib/qti/v2/models/interactions/gap_match_interaction_spec.rb
@@ -434,6 +456,7 @@ files:
434
456
  - spec/lib/qti_spec.rb
435
457
  - spec/spec_helper.rb
436
458
  - spec/support/custom_matchers.rb
459
+ - spec/support/monkey_patches.rb
437
460
  homepage:
438
461
  licenses: []
439
462
  metadata: {}
@@ -477,6 +500,7 @@ test_files:
477
500
  - spec/fixtures/items_2.1/adaptive_template.xml
478
501
  - spec/fixtures/items_2.1/associate.xml
479
502
  - spec/fixtures/items_2.1/audio-video.xml
503
+ - spec/fixtures/items_2.1/categorization.xml
480
504
  - spec/fixtures/items_2.1/choice.xml
481
505
  - spec/fixtures/items_2.1/choice_aria.xml
482
506
  - spec/fixtures/items_2.1/choice_fixed.xml
@@ -534,6 +558,7 @@ test_files:
534
558
  - spec/fixtures/items_2.1/inline_choice_math.xml
535
559
  - spec/fixtures/items_2.1/likert.xml
536
560
  - spec/fixtures/items_2.1/match.xml
561
+ - spec/fixtures/items_2.1/match2.xml
537
562
  - spec/fixtures/items_2.1/math.xml
538
563
  - spec/fixtures/items_2.1/mc_calc3.xml
539
564
  - spec/fixtures/items_2.1/mc_calc5.xml
@@ -652,11 +677,14 @@ test_files:
652
677
  - spec/lib/qti/v1/models/choices/logical_identifier_choice_spec.rb
653
678
  - spec/lib/qti/v1/models/interactions/choice_interaction_spec.rb
654
679
  - spec/lib/qti/v1/models/interactions/fill_blank_interaction_spec.rb
680
+ - spec/lib/qti/v1/models/interactions/match_interaction_spec.rb
655
681
  - spec/lib/qti/v1/models/interactions/string_interaction_spec.rb
656
682
  - spec/lib/qti/v2/models/assessment_item_spec.rb
657
683
  - spec/lib/qti/v2/models/assessment_test_spec.rb
658
684
  - spec/lib/qti/v2/models/choices/gap_match_choice_spec.rb
685
+ - spec/lib/qti/v2/models/choices/simple_associable_choice_spec.rb
659
686
  - spec/lib/qti/v2/models/choices/simple_choice_spec.rb
687
+ - spec/lib/qti/v2/models/interactions/categorization_interaction_spec.rb
660
688
  - spec/lib/qti/v2/models/interactions/choice_interaction_spec.rb
661
689
  - spec/lib/qti/v2/models/interactions/extended_text_interaction_spec.rb
662
690
  - spec/lib/qti/v2/models/interactions/gap_match_interaction_spec.rb
@@ -664,3 +692,4 @@ test_files:
664
692
  - spec/lib/qti_spec.rb
665
693
  - spec/spec_helper.rb
666
694
  - spec/support/custom_matchers.rb
695
+ - spec/support/monkey_patches.rb