qti 0.4.16 → 0.5.16

Sign up to get free protection for your applications and to get access to all the features.
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