cqm-validators 0.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +23 -0
- data/.gitignore +3 -1
- data/.overcommit.yml +18 -0
- data/.rubocop.yml +72 -0
- data/.simplecov +10 -0
- data/.travis.yml +22 -0
- data/Gemfile +11 -3
- data/README.md +22 -6
- data/Rakefile +8 -6
- data/config/mongoid.yml +6 -0
- data/cqm_validators.gemspec +21 -14
- data/lib/base_validator.rb +20 -20
- data/lib/cqm_validators.rb +4 -2
- data/lib/cqm_validators/version.rb +3 -1
- data/lib/data_validator.rb +74 -79
- data/lib/measure_validator.rb +104 -107
- data/lib/performance_rate_validator.rb +43 -63
- data/lib/qrda_qdm_template_validator.rb +164 -311
- data/lib/reported_result_extractor.rb +137 -139
- data/lib/schema_validator.rb +20 -19
- data/lib/schematron/c_processor.rb +19 -19
- data/lib/schematron/java_processor.rb +66 -68
- data/lib/schematron/qrda/{cat_1_r4/HL7 QRDA Category I STU 4.sch → cat_1_r5_1/HL7 QRDA Category I STU 5.1.sch } +1559 -1964
- data/lib/schematron/qrda/{cat_1_r4 → cat_1_r5_1}/voc.xml +7 -2
- data/lib/schematron/qrda/cat_1_r5_2/HL7 QRDA Category I STU 5.2.sch +3961 -0
- data/lib/schematron/qrda/{cat_3_r2 → cat_1_r5_2}/voc.xml +7 -2
- data/lib/schematron_validator.rb +31 -32
- data/lib/validation_error.rb +11 -9
- data/lib/validators.rb +48 -105
- data/notice.md +9 -0
- metadata +90 -26
- data/lib/schematron/qrda/cat_1/HL7_CDAR2_QRDA_Category_I_2_12_16.sch +0 -4693
- data/lib/schematron/qrda/cat_1/voc.xml +0 -1177
- data/lib/schematron/qrda/cat_1_r2/QRDA Category I Release 2.sch +0 -4069
- data/lib/schematron/qrda/cat_1_r2/voc.xml +0 -1065
- data/lib/schematron/qrda/cat_1_r3_1/HL7 QRDA Category I STU 3.1.sch +0 -3573
- data/lib/schematron/qrda/cat_1_r3_1/HL7 QRDA Category III STU 1.1.sch +0 -464
- data/lib/schematron/qrda/cat_1_r3_1/QRDA Category I STU Release 3.1.sch +0 -5394
- data/lib/schematron/qrda/cat_1_r3_1/voc.xml +0 -1229
- data/lib/schematron/qrda/cat_3/QRDA Category III.sch +0 -675
- data/lib/schematron/qrda/cat_3/voc.xml +0 -21
- data/lib/schematron/qrda/cat_3_r1_1/HL7 QRDA Category III STU 1.1.sch +0 -528
- data/lib/schematron/qrda/cat_3_r1_1/voc.xml +0 -8
- data/lib/schematron/qrda/cat_3_r2/HL7 QRDA Category III STU 2.sch +0 -677
@@ -1,172 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CqmValidators
|
2
4
|
module ReportedResultExtractor
|
5
|
+
# takes a document and a list of 1 or more id hashes, e.g.:
|
6
|
+
# [{measure_id:"8a4d92b2-36af-5758-0136-ea8c43244986", set_id:"03876d69-085b-415c-ae9d-9924171040c2", ipp:"D77106C4-8ED0-4C5D-B29E-13DBF255B9FF",
|
7
|
+
# den:"8B0FA80F-8FFE-494C-958A-191C1BB36DBF", num:"9363135E-A816-451F-8022-96CDA7E540DD"}]
|
8
|
+
# returns nil if nothing matching is found
|
9
|
+
# returns a hash with the values of the populations filled out along with the population_ids added to the result
|
3
10
|
|
4
|
-
|
5
|
-
#[{measure_id:"8a4d92b2-36af-5758-0136-ea8c43244986", set_id:"03876d69-085b-415c-ae9d-9924171040c2", ipp:"D77106C4-8ED0-4C5D-B29E-13DBF255B9FF", den:"8B0FA80F-8FFE-494C-958A-191C1BB36DBF", num:"9363135E-A816-451F-8022-96CDA7E540DD"}]
|
6
|
-
#returns nil if nothing matching is found
|
7
|
-
# returns a hash with the values of the populations filled out along with the population_ids added to the result
|
8
|
-
|
9
|
-
|
10
|
-
def extract_results_by_ids(measure_id, ids, doc)
|
11
|
-
results = nil
|
12
|
-
_ids = ids.dup
|
13
|
-
stratification = _ids.delete("stratification")
|
14
|
-
stratification ||= _ids.delete("STRAT")
|
15
|
-
errors = []
|
16
|
-
nodes = find_measure_node(measure_id, doc)
|
11
|
+
ALL_POPULATION_CODES = %w[IPP DENOM NUMER NUMEX DENEX DENEXCEP MSRPOPL MSRPOPLEX OBSERV].freeze
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
13
|
+
def extract_results_by_ids(measure, poulation_set_id, doc, stratification_id = nil)
|
14
|
+
results = nil
|
15
|
+
nodes = find_measure_node(measure.hqmf_id, doc)
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
return nil if results.nil?
|
28
|
-
results[:population_ids] = ids.dup
|
29
|
-
results
|
17
|
+
if nodes.nil? || nodes.empty?
|
18
|
+
# short circuit and return nil
|
19
|
+
return {}
|
30
20
|
end
|
31
21
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
and ./cda:reference/cda:externalDocument/cda:id[#{translate("@extension")}='#{id.upcase}' and #{translate("@root")}='2.16.840.1.113883.4.738']]}
|
36
|
-
return doc.xpath(xpath_measures)
|
22
|
+
nodes.each do |n|
|
23
|
+
results = get_measure_components(n, measure.population_sets.where(population_set_id: poulation_set_id).first, stratification_id)
|
24
|
+
break if !results.nil? || (!results.nil? && !results.empty?)
|
37
25
|
end
|
26
|
+
return nil if results.nil?
|
38
27
|
|
39
|
-
|
40
|
-
|
41
|
-
ids.each_pair do |k,v|
|
42
|
-
val = nil
|
43
|
-
sup = nil
|
44
|
-
if (k == 'OBSERV')
|
45
|
-
msrpopl = ids['MSRPOPL']
|
46
|
-
val, sup = extract_cv_value(n,v,msrpopl, stratification)
|
47
|
-
else
|
48
|
-
val,sup,pr =extract_component_value(n,k,v,stratification)
|
49
|
-
end
|
50
|
-
if !val.nil?
|
51
|
-
results[k.to_s] = val
|
52
|
-
results[:supplemental_data][k] = sup
|
53
|
-
else
|
54
|
-
# return nil
|
55
|
-
end
|
56
|
-
if !pr.nil?
|
57
|
-
results["PR"] = pr
|
58
|
-
end
|
59
|
-
end
|
60
|
-
results
|
61
|
-
end
|
28
|
+
results
|
29
|
+
end
|
62
30
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
return val, (strata.nil? ? extract_supplemental_data(cv) : nil)
|
76
|
-
end
|
31
|
+
def find_measure_node(id, doc)
|
32
|
+
xpath_measures = %(/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section
|
33
|
+
/cda:entry/cda:organizer[ ./cda:templateId[@root = "2.16.840.1.113883.10.20.27.3.1"]
|
34
|
+
and ./cda:reference/cda:externalDocument/cda:id[#{translate('@extension')}='#{id.upcase}' and #{translate('@root')}='2.16.840.1.113883.4.738']])
|
35
|
+
doc.xpath(xpath_measures)
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_measure_components(n, population_set, stratification_id)
|
39
|
+
results = { supplemental_data: {} }
|
40
|
+
stratification = stratification_id ? population_set.stratifications.where(stratification_id: stratification_id).first.hqmf_id : nil
|
41
|
+
ALL_POPULATION_CODES.each do |pop_code|
|
42
|
+
next unless population_set.populations[pop_code] || pop_code == 'OBSERV'
|
77
43
|
|
78
|
-
def extract_component_value(node, code, id, strata = nil)
|
79
|
-
xpath_observation = %{ cda:component/cda:observation[./cda:value[@code = "#{code}"] and ./cda:reference/cda:externalObservation/cda:id[#{translate("@root")}='#{id.upcase}']]}
|
80
|
-
cv = node.at_xpath(xpath_observation)
|
81
|
-
return nil unless cv
|
82
44
|
val = nil
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
45
|
+
sup = nil
|
46
|
+
if pop_code == 'OBSERV'
|
47
|
+
next unless population_set.populations['MSRPOPL']
|
48
|
+
|
49
|
+
msrpopl = population_set.populations['MSRPOPL']['hqmf_id']
|
50
|
+
val, sup = extract_cv_value(n, population_set.observations.first.hqmf_id, msrpopl, stratification)
|
87
51
|
else
|
88
|
-
val =
|
52
|
+
val, sup, pr = extract_component_value(n, pop_code, population_set.populations[pop_code]['hqmf_id'], stratification)
|
89
53
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
54
|
+
unless val.nil?
|
55
|
+
results[pop_code] = val
|
56
|
+
results[:supplemental_data][pop_code] = sup
|
93
57
|
end
|
94
|
-
|
58
|
+
results['PR'] = pr unless pr.nil?
|
95
59
|
end
|
60
|
+
results
|
61
|
+
end
|
96
62
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
return nil
|
63
|
+
def extract_cv_value(node, id, msrpopl, strata = nil)
|
64
|
+
xpath_observation = %( cda:component/cda:observation[./cda:value[@code = "MSRPOPL"] and ./cda:reference/cda:externalObservation/cda:id[#{translate('@root')}='#{msrpopl.upcase}']])
|
65
|
+
cv = node.at_xpath(xpath_observation)
|
66
|
+
return nil unless cv
|
67
|
+
|
68
|
+
val = nil
|
69
|
+
if strata
|
70
|
+
strata_path = %( cda:entryRelationship[@typeCode="COMP"]/cda:observation[./cda:templateId[@root = "2.16.840.1.113883.10.20.27.3.4"] and ./cda:reference/cda:externalObservation/cda:id[#{translate('@root')}='#{strata.upcase}']])
|
71
|
+
n = cv.xpath(strata_path)
|
72
|
+
val = get_cv_value(n, id)
|
73
|
+
else
|
74
|
+
val = get_cv_value(cv, id)
|
111
75
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
76
|
+
[val, (strata.nil? ? extract_supplemental_data(cv) : nil)]
|
77
|
+
end
|
78
|
+
|
79
|
+
def extract_component_value(node, code, id, strata = nil)
|
80
|
+
code = 'IPOP' if code == 'IPP'
|
81
|
+
xpath_observation = %( cda:component/cda:observation[./cda:value[@code = "#{code}"] and ./cda:reference/cda:externalObservation/cda:id[#{translate('@root')}='#{id.upcase}']])
|
82
|
+
cv = node.at_xpath(xpath_observation)
|
83
|
+
return nil unless cv
|
84
|
+
|
85
|
+
val = nil
|
86
|
+
if strata
|
87
|
+
strata_path = %( cda:entryRelationship[@typeCode="COMP"]/cda:observation[./cda:templateId[@root = "2.16.840.1.113883.10.20.27.3.4"] and ./cda:reference/cda:externalObservation/cda:id[#{translate('@root')}='#{strata.upcase}']])
|
88
|
+
n = cv.xpath(strata_path)
|
89
|
+
val = get_aggregate_count(n) if n
|
90
|
+
else
|
91
|
+
val = get_aggregate_count(cv)
|
92
|
+
end
|
93
|
+
# Performance rate is only applicable for unstratified values
|
94
|
+
pref_rate_value = extract_performance_rate(node, code, id) if code == 'NUMER' && strata.nil?
|
95
|
+
[val, (strata.nil? ? extract_supplemental_data(cv) : nil), pref_rate_value]
|
96
|
+
end
|
97
|
+
|
98
|
+
def extract_performance_rate(node, _code, id)
|
99
|
+
xpath_perf_rate = %( cda:component/cda:observation[./cda:templateId[@root = "2.16.840.1.113883.10.20.27.3.14"] and ./cda:reference/cda:externalObservation/cda:id[#{translate('@root')}='#{id.upcase}']]/cda:value)
|
100
|
+
perf_rate = node.at_xpath(xpath_perf_rate)
|
101
|
+
pref_rate_value = {}
|
102
|
+
unless perf_rate.nil?
|
103
|
+
if perf_rate.at_xpath('./@nullFlavor')
|
104
|
+
pref_rate_value['nullFlavor'] = 'NA'
|
119
105
|
else
|
120
|
-
|
106
|
+
pref_rate_value['value'] = perf_rate.at_xpath('./@value').value
|
121
107
|
end
|
108
|
+
return pref_rate_value
|
122
109
|
end
|
110
|
+
nil
|
111
|
+
end
|
123
112
|
|
124
|
-
|
125
|
-
|
126
|
-
|
113
|
+
# convert numbers in value nodes to Int / Float as necessary
|
114
|
+
# TODO: add more types other than 'REAL'
|
115
|
+
def convert_value(value_node)
|
116
|
+
return if value_node.nil?
|
117
|
+
return value_node['value'].to_f if value_node['type'] == 'REAL' || value_node['value'].include?('.')
|
127
118
|
|
128
|
-
|
129
|
-
|
130
|
-
value
|
131
|
-
end
|
119
|
+
value_node['value'].to_i
|
120
|
+
end
|
132
121
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
122
|
+
# given an observation node with an aggregate count node, return the reported and expected value within the count node
|
123
|
+
def get_cv_value(node, cv_id)
|
124
|
+
xpath_value = %(cda:entryRelationship/cda:observation[./cda:templateId[@root="2.16.840.1.113883.10.20.27.3.2"] and ./cda:reference/cda:externalObservation/cda:id[#{translate('@root')}='#{cv_id.upcase}']]/cda:value)
|
125
|
+
|
126
|
+
value_node = node.at_xpath(xpath_value)
|
127
|
+
value = convert_value(value_node) if value_node
|
128
|
+
value
|
129
|
+
end
|
130
|
+
|
131
|
+
# given an observation node with an aggregate count node, return the reported and expected value within the count node
|
132
|
+
def get_aggregate_count(node)
|
133
|
+
xpath_value = 'cda:entryRelationship/cda:observation[./cda:templateId[@root="2.16.840.1.113883.10.20.27.3.3"]]/cda:value'
|
134
|
+
value_node = node.at_xpath(xpath_value)
|
135
|
+
value = convert_value(value_node) if value_node
|
136
|
+
value
|
137
|
+
end
|
140
138
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
key_hash[value.at_xpath(
|
156
|
-
else
|
157
|
-
key_hash["UNK"] = count
|
158
|
-
end
|
139
|
+
def extract_supplemental_data(cv)
|
140
|
+
ret = {}
|
141
|
+
supplemental_data_mapping = { 'RACE' => '2.16.840.1.113883.10.20.27.3.8',
|
142
|
+
'ETHNICITY' => '2.16.840.1.113883.10.20.27.3.7',
|
143
|
+
'SEX' => '2.16.840.1.113883.10.20.27.3.6',
|
144
|
+
'PAYER' => '2.16.840.1.113883.10.20.27.3.9' }
|
145
|
+
supplemental_data_mapping.each_pair do |supp, id|
|
146
|
+
key_hash = {}
|
147
|
+
xpath = "cda:entryRelationship/cda:observation[cda:templateId[@root='#{id}']]"
|
148
|
+
(cv.xpath(xpath) || []).each do |node|
|
149
|
+
value = node.at_xpath('cda:value')
|
150
|
+
count = get_aggregate_count(node)
|
151
|
+
if value.at_xpath('./@nullFlavor')
|
152
|
+
if supp == 'PAYER' && value['xsi:type'] == 'CD' && value['nullFlavor'] == 'OTH' && value.at_xpath('cda:translation') && value.at_xpath('cda:translation')['code']
|
153
|
+
key_hash[value.at_xpath('cda:translation')['code']] = count
|
159
154
|
else
|
160
|
-
|
155
|
+
key_hash['UNK'] = count
|
161
156
|
end
|
157
|
+
else
|
158
|
+
key_hash[value['code']] = count
|
162
159
|
end
|
163
|
-
ret[supp.to_s] = key_hash
|
164
160
|
end
|
165
|
-
ret
|
161
|
+
ret[supp.to_s] = key_hash
|
166
162
|
end
|
163
|
+
ret
|
164
|
+
end
|
167
165
|
|
168
|
-
|
169
|
-
|
170
|
-
end
|
166
|
+
def translate(id)
|
167
|
+
%{translate(#{id}, "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")}
|
171
168
|
end
|
169
|
+
end
|
172
170
|
end
|
data/lib/schema_validator.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CqmValidators
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
4
|
+
module Schema
|
5
|
+
class Validator
|
6
|
+
include BaseValidator
|
7
|
+
|
8
|
+
def initialize(name, schema_file)
|
9
|
+
@name = name
|
10
|
+
@schema_file = schema_file
|
11
|
+
@xsd = Nokogiri::XML::Schema(File.new(@schema_file))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Validate the document against the configured schema
|
15
|
+
def validate(document, data = {})
|
16
|
+
@xsd.errors.clear
|
17
|
+
doc = get_document(document)
|
18
|
+
@xsd.validate(doc).map do |error|
|
19
|
+
build_error(error.message, '/', data[:file_name])
|
20
20
|
end
|
21
21
|
end
|
22
|
+
end
|
23
|
+
end
|
22
24
|
end
|
23
|
-
|
@@ -1,26 +1,26 @@
|
|
1
|
-
|
2
|
-
module Schematron
|
3
|
-
module CProcessor
|
4
|
-
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module CqmValidators
|
4
|
+
module Schematron
|
5
|
+
module CProcessor
|
6
|
+
def get_errors(document)
|
7
|
+
document = get_document(document)
|
8
|
+
processor.transform(document)
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
doc = Nokogiri::XML(File.open(@schematron_file))
|
14
|
-
doc.root["defaultPhase"] = ("errors")
|
11
|
+
def processor
|
12
|
+
return @processor if @processor
|
15
13
|
|
16
|
-
|
14
|
+
doc = Nokogiri::XML(File.open(@schematron_file))
|
15
|
+
doc.root['defaultPhase'] = 'errors'
|
17
16
|
|
18
|
-
|
19
|
-
#this is stupid but needs to be done to assocaite the xslt file with a dirctory
|
20
|
-
result = Nokogiri::XML(result.to_s,@schematron_file)
|
21
|
-
@processor = Nokogiri::XSLT::Stylesheet.parse_stylesheet_doc(result)
|
22
|
-
end
|
17
|
+
xslt = Nokogiri::XSLT(File.open(ISO_SCHEMATRON))
|
23
18
|
|
24
|
-
|
19
|
+
result = xslt.transform(doc)
|
20
|
+
# this needs to be done to associate the xslt file with a dirctory
|
21
|
+
result = Nokogiri::XML(result.to_s, @schematron_file)
|
22
|
+
@processor = Nokogiri::XSLT::Stylesheet.parse_stylesheet_doc(result)
|
25
23
|
end
|
24
|
+
end
|
25
|
+
end
|
26
26
|
end
|
@@ -1,92 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'java'
|
2
4
|
require_relative '../../../../java/saxon9he.jar'
|
3
5
|
|
4
|
-
java_import
|
5
|
-
java_import
|
6
|
-
|
7
|
-
java_import "javax.xml.parsers.DocumentBuilder"
|
8
|
-
java_import "javax.xml.parsers.DocumentBuilderFactory"
|
6
|
+
java_import 'java.io.StringReader'
|
7
|
+
java_import 'java.io.StringWriter'
|
9
8
|
|
10
|
-
java_import
|
11
|
-
java_import
|
12
|
-
java_import "javax.xml.transform.dom.DOMSource"
|
13
|
-
java_import "javax.xml.transform.stream.StreamSource"
|
14
|
-
java_import "javax.xml.transform.stream.StreamResult"
|
9
|
+
java_import 'javax.xml.parsers.DocumentBuilder'
|
10
|
+
java_import 'javax.xml.parsers.DocumentBuilderFactory'
|
15
11
|
|
16
|
-
java_import
|
12
|
+
java_import 'javax.xml.transform.TransformerFactory'
|
13
|
+
java_import 'javax.xml.transform.Transformer'
|
14
|
+
java_import 'javax.xml.transform.dom.DOMSource'
|
15
|
+
java_import 'javax.xml.transform.stream.StreamSource'
|
16
|
+
java_import 'javax.xml.transform.stream.StreamResult'
|
17
17
|
|
18
|
+
java_import 'org.w3c.dom.Document'
|
18
19
|
|
19
|
-
TRANSFORMER_FACTORY_IMPL =
|
20
|
+
TRANSFORMER_FACTORY_IMPL = 'net.sf.saxon.TransformerFactoryImpl'
|
20
21
|
|
21
22
|
module CqmValidators
|
22
|
-
|
23
|
-
|
23
|
+
module Schematron
|
24
|
+
module JavaProcessor
|
25
|
+
ISO_SCHEMATRON2 = File.join(DIR, 'lib/schematron/iso-schematron-xslt2/iso_svrl_for_xslt2.xsl')
|
24
26
|
|
25
|
-
|
27
|
+
class HdsUrlResolver
|
28
|
+
include javax.xml.transform.URIResolver
|
26
29
|
|
27
|
-
|
28
|
-
|
30
|
+
def initialize(schematron)
|
31
|
+
@file = schematron
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
def resolve(href, _base)
|
35
|
+
path = File.join(File.dirname(@file), href)
|
36
|
+
StreamSource.new(java.io.File.new(path))
|
37
|
+
end
|
38
|
+
end
|
33
39
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
40
|
+
def get_errors(document)
|
41
|
+
document_j = get_document_j(document)
|
42
|
+
output = build_transformer(StringReader.new(processor), StreamSource.new(document_j), true)
|
43
|
+
Nokogiri::XML(output)
|
44
|
+
end
|
38
45
|
|
46
|
+
def get_document_j(doc)
|
47
|
+
case doc
|
48
|
+
when File
|
49
|
+
java.io.File.new(doc.path)
|
50
|
+
else
|
51
|
+
StringReader.new(doc.to_s)
|
39
52
|
end
|
53
|
+
end
|
40
54
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Nokogiri::XML(output)
|
45
|
-
end
|
55
|
+
def processor
|
56
|
+
@processor ||= build_transformer(java.io.File.new(ISO_SCHEMATRON2), schematron_file)
|
57
|
+
end
|
46
58
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
59
|
+
def schematron_file
|
60
|
+
# this allows us to run the validation utility app in jBoss/TorqueBox
|
61
|
+
# for some reason it breaks the first time you call DocumentBuilderFactory,
|
62
|
+
# so the solution is to catch the error and retry
|
63
|
+
# TODO: pull this out when the above is no longer the case.
|
64
|
+
begin
|
65
|
+
dbf = DocumentBuilderFactory.new_instance
|
66
|
+
rescue Exception
|
67
|
+
retry
|
54
68
|
end
|
69
|
+
dbf.setIgnoringElementContentWhitespace(true)
|
70
|
+
db = dbf.new_document_builder
|
71
|
+
document = db.parse(java.io.File.new(@schematron_file))
|
55
72
|
|
56
|
-
|
57
|
-
|
58
|
-
end
|
73
|
+
root = document.document_element
|
74
|
+
root.set_attribute('defaultPhase', 'errors')
|
59
75
|
|
60
|
-
|
61
|
-
|
62
|
-
# this allows us to run the validation utility app in jBoss/TorqueBox
|
63
|
-
# for some reason it breaks the first time you call DocumentBuilderFactory,
|
64
|
-
# so the solution is to catch the error and retry
|
65
|
-
# TODO: pull this out when the above is no longer the case.
|
66
|
-
begin
|
67
|
-
dbf = DocumentBuilderFactory.new_instance
|
68
|
-
rescue Exception => ex
|
69
|
-
retry
|
70
|
-
end
|
71
|
-
dbf.setIgnoringElementContentWhitespace(true);
|
72
|
-
db = dbf.new_document_builder
|
73
|
-
document = db.parse(java.io.File.new(@schematron_file))
|
74
|
-
|
75
|
-
root = document.document_element
|
76
|
-
phase = root.set_attribute("defaultPhase", "errors")
|
77
|
-
|
78
|
-
DOMSource.new(root)
|
79
|
-
end
|
76
|
+
DOMSource.new(root)
|
77
|
+
end
|
80
78
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
79
|
+
def build_transformer(xslt, input_file, url = false)
|
80
|
+
factory = TransformerFactory.newInstance(TRANSFORMER_FACTORY_IMPL, nil)
|
81
|
+
factory.uri_resolver = HdsUrlResolver.new(@schematron_file) if url
|
82
|
+
transformer = factory.new_transformer(StreamSource.new(xslt))
|
83
|
+
sw = StringWriter.new
|
84
|
+
output = StreamResult.new(sw)
|
85
|
+
transformer.transform(input_file, output)
|
86
|
+
sw.to_s
|
90
87
|
end
|
91
88
|
end
|
92
89
|
end
|
90
|
+
end
|