cqm-validators 0.1.0 → 2.0.2
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 +5 -5
- data/.github/PULL_REQUEST_TEMPLATE.md +23 -0
- data/.gitignore +3 -1
- data/.overcommit.yml +18 -0
- data/.rubocop.yml +68 -0
- data/.simplecov +12 -0
- data/.travis.yml +20 -0
- data/Gemfile +11 -3
- data/README.md +20 -6
- data/Rakefile +8 -6
- data/config/mongoid.yml +6 -0
- data/cqm_validators.gemspec +23 -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 } +1684 -2082
- data/lib/schematron/qrda/{cat_1_r3_1 → cat_1_r5_1}/voc.xml +1223 -1228
- data/lib/schematron_validator.rb +31 -32
- data/lib/validation_error.rb +11 -9
- data/lib/validators.rb +40 -106
- data/notice.md +9 -0
- metadata +97 -36
- 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_r4/voc.xml +0 -1186
- 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
- data/lib/schematron/qrda/cat_3_r2/voc.xml +0 -1186
@@ -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
|