proformaxml 0.10.0

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.
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns1="urn:proforma:tests:java-checkstyle:v1.1" targetNamespace="urn:proforma:tests:java-checkstyle:v1.1" elementFormDefault="qualified" attributeFormDefault="unqualified">
3
+ <xs:element name="java-checkstyle">
4
+ <xs:complexType>
5
+ <xs:sequence>
6
+ <xs:element name="max-checkstyle-warnings" type="xs:string"/>
7
+ </xs:sequence>
8
+ <xs:attribute name="version" type="xs:string" use="required"/>
9
+ </xs:complexType>
10
+ </xs:element>
11
+ </xs:schema>
@@ -0,0 +1,50 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- This is meant for blackbox tests of the output of a program. For regexp tests of
3
+ sourcecode something like checkstyle can be used.-->
4
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns1="urn:proforma:tests:regexptest:v0.9"
5
+ targetNamespace="urn:proforma:tests:regexptest:v0.9" elementFormDefault="qualified"
6
+ attributeFormDefault="unqualified">
7
+ <xs:element name="regexptest">
8
+ <xs:complexType>
9
+ <xs:sequence>
10
+ <!-- entry-point is a file or a main-class needed to start a program -->
11
+ <xs:element name="entry-point" type="xs:string"/>
12
+ <!-- parameter: optional list of command line arguments -->
13
+ <xs:element name="parameter" minOccurs="0">
14
+ <xs:simpleType>
15
+ <xs:list itemType="xs:string"/>
16
+ </xs:simpleType>
17
+ </xs:element>
18
+ <xs:element name="regular-expressions">
19
+ <xs:complexType>
20
+ <!-- choice means: regexp-allow OR regexp-disallow -->
21
+ <xs:choice>
22
+ <xs:sequence>
23
+ <!-- regular expression for allowed program outputs -->
24
+ <xs:element name="regexp-allow" type="ns1:regexpType" maxOccurs="unbounded"/>
25
+ <!-- regular expression for not allowed program outputs -->
26
+ <xs:element name="regexp-disallow" type="ns1:regexpType" minOccurs="0" maxOccurs="unbounded"/>
27
+ </xs:sequence>
28
+ <xs:element name="regexp-disallow" type="ns1:regexpType" maxOccurs="unbounded"/>
29
+ </xs:choice>
30
+ </xs:complexType>
31
+ </xs:element>
32
+ </xs:sequence>
33
+ </xs:complexType>
34
+ </xs:element>
35
+
36
+ <xs:complexType name="regexpType">
37
+ <xs:simpleContent>
38
+ <xs:extension base="xs:string">
39
+ <xs:attributeGroup ref="ns1:regexp-flags"/>
40
+ </xs:extension>
41
+ </xs:simpleContent>
42
+ </xs:complexType>
43
+ <!-- attributes to define, which regexp-flags are applied to a specified regular expression -->
44
+ <xs:attributeGroup name="regexp-flags">
45
+ <xs:attribute name="case-insensitive" type="xs:boolean" default="false"/>
46
+ <xs:attribute name="dotall" type="xs:boolean" default="false"/>
47
+ <xs:attribute name="multiline" type="xs:boolean" default="false"/>
48
+ <xs:attribute name="free-spacing" type="xs:boolean" default="false"/>
49
+ </xs:attributeGroup>
50
+ </xs:schema>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns1="urn:proforma:tests:unittest:v1.1" targetNamespace="urn:proforma:tests:unittest:v1.1" elementFormDefault="qualified" attributeFormDefault="unqualified">
3
+ <xs:element name="unittest">
4
+ <xs:complexType>
5
+ <xs:sequence>
6
+ <xs:element name="entry-point" type="xs:string" maxOccurs="unbounded"/>
7
+ </xs:sequence>
8
+ <xs:attribute name="framework" type="xs:string" use="required"/>
9
+ <xs:attribute name="version" type="xs:string" use="required"/>
10
+ </xs:complexType>
11
+ </xs:element>
12
+ </xs:schema>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProformaXML
4
+ class ProformaError < StandardError; end
5
+
6
+ class PostGenerateValidationError < ProformaError; end
7
+
8
+ class PreImportValidationError < ProformaError; end
9
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProformaXML
4
+ module Helpers
5
+ module ExportHelpers
6
+ def attach_file(xml, file)
7
+ if file.embed?
8
+ embed_file(file, xml)
9
+ else
10
+ xml.send "attached-#{file.binary ? 'bin' : 'txt'}-file", file.filename
11
+ end
12
+ end
13
+
14
+ def embed_file(file, xml)
15
+ if file.binary
16
+ xml.send :'embedded-bin-file', {filename: file.filename}, Base64.encode64(file.content)
17
+ else
18
+ xml.send :'embedded-txt-file', {filename: file.filename}, file.content
19
+ end
20
+ end
21
+
22
+ def add_description_to_xml(xml, description)
23
+ xml.send(:description, description) if description.present?
24
+ end
25
+
26
+ def add_test_configuration(xml, test)
27
+ xml.send(:'test-configuration') do
28
+ add_filerefs(xml, test) if test.files
29
+ add_configuration(xml, test.configuration) unless test.configuration.nil?
30
+ if test.meta_data
31
+ xml.send(:'test-meta-data') do
32
+ meta_data(xml, test.meta_data)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def inner_meta_data(xml, namespace, data)
39
+ data.each do |key, value|
40
+ case value.class.name
41
+ when 'Hash'
42
+ # underscore is used to disambiguate tag names from ruby methods
43
+ xml[namespace].send("#{key}_") do |meta_data_xml|
44
+ inner_meta_data(meta_data_xml, namespace, value)
45
+ end
46
+ else
47
+ xml[namespace].send("#{key}_", value)
48
+ end
49
+ end
50
+ end
51
+
52
+ def meta_data(xml, meta_data)
53
+ meta_data.each do |namespace, data|
54
+ inner_meta_data(xml, namespace, data)
55
+ end
56
+ end
57
+
58
+ def add_configuration(xml, configuration)
59
+ xml_snippet = Dachsfisch::JSON2XMLConverter.perform(json: configuration.to_json)
60
+ configuration.flat_map {|_, val| val['@xmlns'].to_a }.uniq.each do |namespace|
61
+ xml.doc.root.add_namespace(namespace[0], namespace[1])
62
+ end
63
+
64
+ xml << xml_snippet
65
+ end
66
+
67
+ def add_namespaces_to_header(header, custom_namespaces)
68
+ custom_namespaces.each do |namespace|
69
+ header["xmlns:#{namespace[:prefix]}"] = namespace[:uri]
70
+ end
71
+ end
72
+
73
+ def add_parent_uuid_and_lang_to_header(header)
74
+ header['lang'] = @task.language if @task.language.present?
75
+ header['parent-uuid'] = @task.parent_uuid if @task.parent_uuid.present?
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProformaXML
4
+ module Helpers
5
+ module ImportHelpers
6
+ CONFIGURATION_NODES = %w[filerefs timeout externalresourcerefs test-meta-data].freeze
7
+
8
+ def set_hash_value_if_present(hash:, name:, attributes: nil, value_overwrite: nil)
9
+ raise unless attributes || value_overwrite
10
+
11
+ value = value_overwrite || attributes[name.to_s]&.value
12
+ hash[name.underscore.to_sym] = value if value.present?
13
+ end
14
+
15
+ def set_value_from_xml(object:, node:, name:, attribute: false, check_presence: true)
16
+ value = value_from_node(name, node, attribute)
17
+ return if check_presence && value.blank?
18
+
19
+ set_value(object:, name: (name.is_a?(Array) ? name[1] : name).underscore, value:)
20
+ end
21
+
22
+ def set_value(object:, name:, value:)
23
+ if object.is_a? Hash
24
+ object[name] = value
25
+ else
26
+ object.send("#{name}=", value)
27
+ end
28
+ end
29
+
30
+ def embedded_file_attributes(attributes, file_tag)
31
+ shared = shared_file_attributes(attributes, file_tag)
32
+ shared.merge(
33
+ content: content_from_file_tag(file_tag, shared[:binary])
34
+ ).tap do |hash|
35
+ hash[:filename] = file_tag.attributes['filename']&.value if file_tag.attributes['filename']&.value.present?
36
+ end
37
+ end
38
+
39
+ def attached_file_attributes(attributes, file_tag)
40
+ filename = file_tag.text
41
+ shared_file_attributes(attributes, file_tag).merge(filename:,
42
+ content: filestring_from_zip(filename))
43
+ end
44
+
45
+ def shared_file_attributes(attributes, file_tag)
46
+ {
47
+ id: attributes['id']&.value,
48
+ used_by_grader: attributes['used-by-grader']&.value == 'true',
49
+ visible: attributes['visible']&.value,
50
+ binary: file_tag.name.include?('-bin-file'),
51
+ }.tap do |hash|
52
+ set_hash_value_if_present(hash:, name: 'usage-by-lms', attributes:)
53
+ set_value_from_xml(object: hash, node: file_tag.parent, name: 'internal-description')
54
+ set_hash_value_if_present(hash:, name: 'mimetype', attributes:)
55
+ end
56
+ end
57
+
58
+ def add_test_configuration(test, test_node)
59
+ test_configuration_node = test_node.xpath('xmlns:test-configuration')
60
+ test.files = test_files_from_test_configuration(test_configuration_node)
61
+ test.configuration = extra_configuration_from_test_configuration(test_configuration_node)
62
+ meta_data_node = test_node.xpath('xmlns:test-configuration').xpath('xmlns:test-meta-data')
63
+ test.meta_data = meta_data(meta_data_node, use_namespace: true) if meta_data_node.present?
64
+ end
65
+
66
+ def extra_configuration_from_test_configuration(test_configuration_node)
67
+ configuration_any_nodes = test_configuration_node.children.reject {|c| CONFIGURATION_NODES.include? c.name }
68
+ return nil if configuration_any_nodes.empty?
69
+
70
+ {}.tap do |config_hash|
71
+ configuration_any_nodes.each do |config_node|
72
+ config_hash.merge! convert_xml_node_to_json(config_node)
73
+ end
74
+ end
75
+ end
76
+
77
+ def test_files_from_test_configuration(test_configuration_node)
78
+ files_from_filerefs(test_configuration_node.search('filerefs'))
79
+ end
80
+
81
+ def meta_data(any_data_node, use_namespace: false)
82
+ # use_namespace forces the use of the namespace as hash key - it should only be used at the entry of the recursion
83
+ {}.tap do |any_data|
84
+ any_data_node.children.each do |node|
85
+ key = (use_namespace ? node.namespace.prefix : any_data_node.name).to_sym
86
+ any_data[key] = if node.node_type == Nokogiri::XML::Node::TEXT_NODE
87
+ node.text
88
+ else
89
+ # preserve any existing data in the nested hash
90
+ (any_data[key] || {}).merge meta_data(node)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def convert_xml_node_to_json(any_node)
99
+ xml_snippet = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new, any_node)
100
+ xml_snippet.children.first.add_namespace_definition(any_node.namespace.prefix, any_node.namespace.href)
101
+ JSON.parse(Dachsfisch::XML2JSONConverter.perform(xml: xml_snippet.to_xml))
102
+ end
103
+
104
+ def value_from_node(name, node, attribute)
105
+ xml_name = name.is_a?(Array) ? name[0] : name
106
+ attribute ? node.attribute(xml_name)&.value : node.xpath("xmlns:#{xml_name}").text
107
+ end
108
+
109
+ def content_from_file_tag(file_tag, binary)
110
+ binary ? Base64.decode64(file_tag.text) : file_tag.text
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProformaXML
4
+ class Base
5
+ include ActiveModel::AttributeAssignment
6
+
7
+ def initialize(attributes = {})
8
+ assign_attributes(attributes)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProformaXML
4
+ class ModelSolution < Base
5
+ attr_accessor :id, :files, :description, :internal_description
6
+
7
+ def initialize(attributes = {})
8
+ super
9
+ self.files = [] if files.nil?
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proformaxml/models/base'
4
+ require 'proformaxml/models/task_file'
5
+ require 'proformaxml/models/test'
6
+ require 'proformaxml/models/model_solution'
7
+ require 'proformaxml/errors'
8
+
9
+ module ProformaXML
10
+ class Task < Base
11
+ attr_accessor :title, :description, :internal_description, :proglang, :uuid, :parent_uuid,
12
+ :language, :model_solutions, :files, :tests, :meta_data
13
+
14
+ def initialize(attributes = {})
15
+ super
16
+ self.files = [] if files.nil?
17
+ self.tests = [] if tests.nil?
18
+ self.model_solutions = [] if model_solutions.nil?
19
+ self.meta_data = {} if meta_data.nil?
20
+ end
21
+
22
+ def all_files
23
+ task_files = files
24
+ model_solution_files = model_solutions.map(&:files).filter(&:present?)
25
+ test_files = tests.map(&:files).filter(&:present?)
26
+ (task_files + model_solution_files + test_files).flatten.uniq
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProformaXML
4
+ class TaskFile < Base
5
+ attr_accessor :id, :content, :filename, :used_by_grader, :visible, :usage_by_lms, :binary, :internal_description, :mimetype
6
+
7
+ def embed?
8
+ (content&.length || 0) < MAX_EMBEDDED_FILE_SIZE_KB * (2**10)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProformaXML
4
+ class Test < Base
5
+ attr_accessor :id, :title, :description, :internal_description, :test_type, :files, :configuration, :meta_data
6
+
7
+ def initialize(attributes = {})
8
+ super
9
+ self.files = [] if files.nil?
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proformaxml/helpers/export_helpers'
4
+
5
+ module ProformaXML
6
+ class Exporter
7
+ include ProformaXML::Helpers::ExportHelpers
8
+
9
+ def initialize(task:, custom_namespaces: [], version: nil)
10
+ @files = {}
11
+ @task = task
12
+ @custom_namespaces = custom_namespaces
13
+ @version = version || SCHEMA_VERSIONS.first
14
+ add_placeholders if @version == '2.0'
15
+ end
16
+
17
+ def perform
18
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
19
+ xml_task(xml)
20
+ end
21
+ xmldoc = builder.to_xml
22
+ doc = Nokogiri::XML(xmldoc)
23
+ errors = validate(doc)
24
+
25
+ raise PostGenerateValidationError.new(errors) if errors.any?
26
+
27
+ # File.open('../testfile.zip', 'wb') { |file| file.write(write_to_zip(xmldoc).string) }
28
+ write_to_zip(xmldoc)
29
+ end
30
+
31
+ private
32
+
33
+ def xml_task(xml)
34
+ xml.task(headers) do
35
+ xml.title @task.title
36
+ xml.description @task.description
37
+ add_internal_description_to_xml(xml, @task.internal_description)
38
+ xml.proglang({version: @task.proglang&.dig(:version)}, @task.proglang&.dig(:name))
39
+
40
+ add_objects_to_xml(xml)
41
+ add_meta_data(xml)
42
+ end
43
+ end
44
+
45
+ def add_internal_description_to_xml(xml, internal_description)
46
+ xml.send(:'internal-description', internal_description) if internal_description.present?
47
+ end
48
+
49
+ def add_meta_data(xml)
50
+ xml.send(:'meta-data') { meta_data(xml, @task.meta_data) }
51
+ end
52
+
53
+ def add_objects_to_xml(xml)
54
+ xml.files { files(xml) }
55
+ xml.send(:'model-solutions') { model_solutions(xml) } if @task.model_solutions.any? || @version == '2.0'
56
+ xml.tests { tests(xml) }
57
+ end
58
+
59
+ def files(xml)
60
+ @task.all_files.each do |file|
61
+ xml.file({
62
+ id: file.id, 'used-by-grader' => file.used_by_grader, visible: file.visible,
63
+ 'usage-by-lms' => file.usage_by_lms, mimetype: file.mimetype
64
+ }.compact) do
65
+ attach_file(xml, file)
66
+ add_internal_description_to_xml(xml, file.internal_description)
67
+ end
68
+ end
69
+ end
70
+
71
+ def model_solutions(xml)
72
+ @task.model_solutions&.each do |model_solution|
73
+ xml.send(:'model-solution', id: model_solution.id) do
74
+ add_filerefs(xml, model_solution)
75
+ add_description_to_xml(xml, model_solution.description)
76
+ add_internal_description_to_xml(xml, model_solution.internal_description)
77
+ end
78
+ end
79
+ end
80
+
81
+ def add_filerefs(xml, object)
82
+ return unless object.files.any?
83
+
84
+ xml.filerefs do
85
+ object.files.each do |file|
86
+ xml.fileref(refid: file.id) {}
87
+ end
88
+ end
89
+ end
90
+
91
+ def tests(xml)
92
+ @task.tests&.each do |test|
93
+ xml.test(id: test.id) do
94
+ xml.title test.title
95
+ add_description_to_xml(xml, test.description)
96
+ add_internal_description_to_xml(xml, test.internal_description)
97
+ xml.send(:'test-type', test.test_type)
98
+ add_test_configuration(xml, test)
99
+ end
100
+ end
101
+ end
102
+
103
+ # ms-placeholder only necessary for version 2.0 where model-solutions were mandatory
104
+ def add_placeholders
105
+ return if @task.model_solutions&.any?
106
+
107
+ file = TaskFile.new(content: '', id: 'ms-placeholder-file', used_by_grader: false, visible: 'no', binary: false)
108
+ model_solution = ModelSolution.new(id: 'ms-placeholder', files: [file])
109
+ @task.model_solutions = [model_solution]
110
+ end
111
+
112
+ def headers
113
+ {
114
+ 'xmlns' => "urn:proforma:v#{@version}",
115
+ 'uuid' => @task.uuid,
116
+ }.tap do |header|
117
+ add_namespaces_to_header(header, @custom_namespaces)
118
+ add_parent_uuid_and_lang_to_header(header)
119
+ end
120
+ end
121
+
122
+ def validate(doc)
123
+ validator = ProformaXML::Validator.new doc, @version
124
+ validator.perform
125
+ end
126
+
127
+ def write_to_zip(xmldoc)
128
+ Zip::OutputStream.write_buffer do |zio|
129
+ zio.put_next_entry('task.xml')
130
+ zio.write xmldoc
131
+ @task.all_files.each do |file|
132
+ next if file.embed?
133
+
134
+ zio.put_next_entry(file.filename)
135
+ zio.write file.content
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string'
4
+ require 'proformaxml/helpers/import_helpers'
5
+
6
+ module ProformaXML
7
+ class Importer
8
+ include ProformaXML::Helpers::ImportHelpers
9
+
10
+ def initialize(zip:, expected_version: nil)
11
+ @zip = zip
12
+ @expected_version = expected_version
13
+
14
+ xml = filestring_from_zip('task.xml')
15
+ raise PreImportValidationError if xml.nil?
16
+
17
+ @doc = Nokogiri::XML(xml, &:noblanks)
18
+ @task = Task.new
19
+ end
20
+
21
+ def perform
22
+ errors = validate
23
+
24
+ raise PreImportValidationError.new(errors) if errors.any?
25
+
26
+ @task_node = @doc.xpath('/xmlns:task')
27
+
28
+ set_data
29
+ {task: @task, custom_namespaces: @custom_namespaces}
30
+ end
31
+
32
+ private
33
+
34
+ def filestring_from_zip(filename)
35
+ Zip::File.open(@zip.path) do |zip_file|
36
+ return zip_file.glob(filename).first&.get_input_stream&.read
37
+ end
38
+ end
39
+
40
+ def set_data
41
+ set_namespaces
42
+ set_base_data
43
+ set_files
44
+ set_model_solutions
45
+ set_tests
46
+ set_meta_data
47
+ end
48
+
49
+ def set_namespaces
50
+ @custom_namespaces = @doc.namespaces.except('xmlns').map {|k, v| {prefix: k[6..], uri: v} }
51
+ end
52
+
53
+ def set_base_data
54
+ set_value_from_xml(object: @task, node: @task_node, name: 'title')
55
+ set_value_from_xml(object: @task, node: @task_node, name: 'description')
56
+ set_value_from_xml(object: @task, node: @task_node, name: 'internal-description')
57
+ set_proglang
58
+ set_value_from_xml(object: @task, node: @task_node, name: %w[lang language], attribute: true)
59
+ set_value_from_xml(object: @task, node: @task_node, name: 'parent-uuid', attribute: true)
60
+ set_value_from_xml(object: @task, node: @task_node, name: 'uuid', attribute: true)
61
+ end
62
+
63
+ def set_proglang
64
+ return if @task_node.xpath('xmlns:proglang').text.blank?
65
+
66
+ @task.proglang = {name: @task_node.xpath('xmlns:proglang').text,
67
+ version: @task_node.xpath('xmlns:proglang').attribute('version').value.presence}.compact
68
+ end
69
+
70
+ def set_files
71
+ @task_node.search('files//file').each {|file_node| add_file file_node }
72
+ end
73
+
74
+ def set_tests
75
+ @task_node.search('tests//test').each {|test_node| add_test test_node }
76
+ end
77
+
78
+ def set_model_solutions
79
+ @task_node.search('model-solutions//model-solution').each do |model_solution_node|
80
+ add_model_solution model_solution_node
81
+ end
82
+ end
83
+
84
+ def set_meta_data
85
+ meta_data_node = @task_node.xpath('xmlns:meta-data')
86
+ @task.meta_data = meta_data(meta_data_node, use_namespace: true) if meta_data_node.text.present?
87
+ end
88
+
89
+ def add_model_solution(model_solution_node)
90
+ model_solution = ModelSolution.new
91
+ model_solution.id = model_solution_node.attributes['id'].value
92
+ model_solution.files = files_from_filerefs(model_solution_node.search('filerefs'))
93
+ set_value_from_xml(object: model_solution, node: model_solution_node, name: 'description')
94
+ set_value_from_xml(object: model_solution, node: model_solution_node, name: 'internal-description')
95
+ @task.model_solutions << model_solution unless model_solution.files.first&.id == 'ms-placeholder-file'
96
+ end
97
+
98
+ def add_file(file_node)
99
+ file_tag = file_node.children.first
100
+ file = nil
101
+ case file_tag.name
102
+ when /embedded-(bin|txt)-file/
103
+ file = TaskFile.new(embedded_file_attributes(file_node.attributes, file_tag))
104
+ when /attached-(bin|txt)-file/
105
+ file = TaskFile.new(attached_file_attributes(file_node.attributes, file_tag))
106
+ end
107
+ @task.files << file
108
+ end
109
+
110
+ def add_test(test_node)
111
+ test = Test.new
112
+ set_value_from_xml(object: test, node: test_node, name: 'id', attribute: true, check_presence: false)
113
+ set_value_from_xml(object: test, node: test_node, name: 'title', check_presence: false)
114
+ set_value_from_xml(object: test, node: test_node, name: 'description')
115
+ set_value_from_xml(object: test, node: test_node, name: 'internal-description')
116
+ set_value_from_xml(object: test, node: test_node, name: 'test-type', check_presence: false)
117
+ add_test_configuration(test, test_node)
118
+ @task.tests << test
119
+ end
120
+
121
+ def files_from_filerefs(filerefs_node)
122
+ [].tap do |files|
123
+ filerefs_node.search('fileref').each do |fileref_node|
124
+ fileref = fileref_node.attributes['refid'].value
125
+ files << @task.files.delete(@task.files.detect {|file| file.id == fileref })
126
+ end
127
+ end
128
+ end
129
+
130
+ def validate
131
+ validator = ProformaXML::Validator.new @doc, @expected_version
132
+ validator.perform
133
+ end
134
+ end
135
+ end