hqmf-parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Gemfile +23 -0
  2. data/README.md +903 -0
  3. data/Rakefile +19 -0
  4. data/VERSION +1 -0
  5. data/lib/hqmf-generator/hqmf-generator.rb +308 -0
  6. data/lib/hqmf-model/attribute.rb +35 -0
  7. data/lib/hqmf-model/data_criteria.rb +322 -0
  8. data/lib/hqmf-model/document.rb +172 -0
  9. data/lib/hqmf-model/population_criteria.rb +90 -0
  10. data/lib/hqmf-model/precondition.rb +85 -0
  11. data/lib/hqmf-model/types.rb +318 -0
  12. data/lib/hqmf-model/utilities.rb +52 -0
  13. data/lib/hqmf-parser.rb +54 -0
  14. data/lib/hqmf-parser/1.0/attribute.rb +68 -0
  15. data/lib/hqmf-parser/1.0/comparison.rb +34 -0
  16. data/lib/hqmf-parser/1.0/data_criteria.rb +105 -0
  17. data/lib/hqmf-parser/1.0/document.rb +209 -0
  18. data/lib/hqmf-parser/1.0/expression.rb +52 -0
  19. data/lib/hqmf-parser/1.0/population_criteria.rb +79 -0
  20. data/lib/hqmf-parser/1.0/precondition.rb +89 -0
  21. data/lib/hqmf-parser/1.0/range.rb +65 -0
  22. data/lib/hqmf-parser/1.0/restriction.rb +157 -0
  23. data/lib/hqmf-parser/1.0/utilities.rb +41 -0
  24. data/lib/hqmf-parser/2.0/data_criteria.rb +319 -0
  25. data/lib/hqmf-parser/2.0/document.rb +165 -0
  26. data/lib/hqmf-parser/2.0/population_criteria.rb +53 -0
  27. data/lib/hqmf-parser/2.0/precondition.rb +44 -0
  28. data/lib/hqmf-parser/2.0/types.rb +223 -0
  29. data/lib/hqmf-parser/2.0/utilities.rb +30 -0
  30. data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +254 -0
  31. data/lib/hqmf-parser/converter/pass1/document_converter.rb +183 -0
  32. data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +135 -0
  33. data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +164 -0
  34. data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +159 -0
  35. data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +35 -0
  36. data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
  37. data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
  38. data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +63 -0
  39. data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
  40. data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +91 -0
  41. data/lib/hqmf-parser/converter/pass2/operator_converter.rb +169 -0
  42. data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter.rb +86 -0
  43. data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter_bak.rb +70 -0
  44. data/lib/hqmf-parser/parser.rb +22 -0
  45. data/lib/hqmf-parser/value_sets/value_set_parser.rb +206 -0
  46. data/lib/tasks/coverme.rake +8 -0
  47. data/lib/tasks/hqmf.rake +141 -0
  48. data/lib/tasks/value_sets.rake +23 -0
  49. metadata +159 -0
@@ -0,0 +1,169 @@
1
+ module HQMF
2
+ # Class for converting an HQMF 1.0 representation to an HQMF 2.0 representation
3
+ class OperatorConverter
4
+
5
+ def self.apply_temporal(data_criteria, precondition, restriction, data_criteria_converter)
6
+ data_criteria.temporal_references ||= []
7
+ value = restriction.operator.value
8
+ type = restriction.operator.type
9
+ temporal_reference = nil
10
+ if (restriction.single_target?)
11
+ # multiple targets appears to be the result of restrictions with restrictions
12
+ target = restriction.target
13
+ if (restriction.multi_target?)
14
+ found = false
15
+ # restrictions with restrictions can have a target that is modified by the child restrcitons
16
+ restriction.preconditions.each do |precondition|
17
+ if precondition.reference.id.start_with? target
18
+ found = true
19
+ target = precondition.reference.id
20
+ end
21
+ end
22
+ unless found
23
+ puts "\tmultiple targets... need to check this" if restriction.multi_target?
24
+ end
25
+ end
26
+ temporal_reference = HQMF::TemporalReference.new(type, HQMF::Reference.new(target),value)
27
+ data_criteria_converter.validate_not_deleted(target)
28
+ elsif (restriction.multi_target?)
29
+
30
+ children_criteria = HQMF::DataCriteriaConverter.extract_data_criteria(restriction.preconditions, data_criteria_converter)
31
+
32
+ if (children_criteria.length == 1)
33
+ target = children_criteria[0].id
34
+ temporal_reference = HQMF::TemporalReference.new(type, HQMF::Reference.new(target),value)
35
+ data_criteria_converter.validate_not_deleted(target)
36
+ else
37
+ parent_id = "GROUP"
38
+ if restriction.generated_data_criteria.nil?
39
+ # we pass in restriction.preconditions here rather than children_criteria because we need to be able to create grouping data criteria for and and or preconditions in a tree
40
+ group_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, "#{type}_CHILDREN", value, parent_id, @@ids.next, "grouping", "temporal")
41
+ # save the generated grouping criteria so that we can reference it from other locations
42
+ restriction.generated_data_criteria = group_criteria
43
+ else
44
+ # we have already processed this restriction and have a grouping criteria for it. Take the one we have previously generated
45
+ group_criteria = restriction.generated_data_criteria
46
+ end
47
+ temporal_reference = HQMF::TemporalReference.new(type, HQMF::Reference.new(group_criteria.id), value)
48
+ end
49
+ else
50
+ raise "no target for temporal restriction"
51
+ end
52
+ restriction.converted=true
53
+
54
+ # add temporal reference to data criteria
55
+ data_criteria.temporal_references << temporal_reference unless data_criteria.has_temporal(temporal_reference)
56
+ end
57
+
58
+
59
+ def self.apply_summary(data_criteria, precondition, restriction, data_criteria_converter)
60
+ value = restriction.operator.value
61
+ type = restriction.operator.type
62
+ subset_operator = HQMF::SubsetOperator.new(type, value)
63
+
64
+ if (restriction.multi_target?)
65
+ children_criteria = HQMF::DataCriteriaConverter.extract_data_criteria(restriction.preconditions, data_criteria_converter)
66
+
67
+ data_criteria = nil
68
+ if (children_criteria.length == 1)
69
+ data_criteria = children_criteria[0]
70
+ # this block used to set the order of operations of values after subset operators.
71
+ # if data_criteria and !data_criteria.value.nil?
72
+ # subset_operator.value ||= data_criteria.value
73
+ # data_criteria.value = nil
74
+ # end
75
+ data_criteria.subset_operators ||= []
76
+ # add subset operator to data criteria
77
+ data_criteria.subset_operators << subset_operator unless data_criteria.has_subset(subset_operator)
78
+ else
79
+ parent_id = "GROUP"
80
+
81
+ # this block of code used to be used to pull the value off of a data criteria and shove it onto a subset operator to evaluate MIN: lab result(result > 10) as MIN > 10: lab result
82
+ # the decision was made by NQF to not support this order of operations based on previous guidance to the measure developers
83
+ #
84
+ # unless subset_operator.value
85
+ # # scalar comparisons are used for MIN>90 etc. The value is on a REFR restriction. We need to store it on the data criteria since the value is processed before the operator is created.
86
+ # scalar_comparison = nil
87
+ #
88
+ # # check to see if we have different values referenced accross the children
89
+ # # this happens on things like most recent blood pressure reading
90
+ # multiple_differing_values = false
91
+ # children_criteria.each do |criteria|
92
+ # if scalar_comparison.nil?
93
+ # scalar_comparison = criteria.value
94
+ # else
95
+ # if scalar_comparison != criteria.value
96
+ # multiple_differing_values = true
97
+ # end
98
+ # end
99
+ # end
100
+ #
101
+ # # if we have multiple differing values, we want to keep the preconditions from the restriction, and attach them to the precondition
102
+ # # we can then apply the subset operator to all of the children individually
103
+ # if (multiple_differing_values)
104
+ # precondition.preconditions.delete(restriction)
105
+ # precondition.preconditions.concat(restriction.preconditions)
106
+ # children_criteria.each do |criteria|
107
+ # subset_operator = HQMF::SubsetOperator.new(type, value)
108
+ # subset_operator.value = criteria.value
109
+ # criteria.value = nil
110
+ # criteria.subset_operators ||= []
111
+ # criteria.subset_operators << subset_operator unless criteria.has_subset(subset_operator)
112
+ # end
113
+ # restriction.converted=true
114
+ # # we want to return since we have applied the subset to the children. We no longer want to create a grouping data critiera
115
+ # return;
116
+ # end
117
+ #
118
+ # # all the children have the same value, apply the value to the subset operator
119
+ # children_criteria.each do |criteria|
120
+ # if scalar_comparison.nil?
121
+ # scalar_comparison = criteria.value
122
+ # else
123
+ # raise "multiple different scalar comparisons for a grouping data criteria" if scalar_comparison != criteria.value
124
+ # end
125
+ # criteria.value = nil
126
+ # end
127
+ # subset_operator.value ||= scalar_comparison
128
+ # end
129
+
130
+ if restriction.generated_data_criteria.nil?
131
+ # we pass in restriction.preconditions here rather than children_criteria because we need to be able to create grouping data criteria for and and or preconditions in a tree
132
+ data_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, type, value, parent_id, @@ids.next, "grouping", "summary")
133
+ # save the generated grouping criteria so that we can reference it from other locations
134
+ restriction.generated_data_criteria = data_criteria
135
+ else
136
+ # we have already processed this restriction and have a grouping criteria for it. Take the one we have previously generated
137
+ data_criteria = restriction.generated_data_criteria
138
+ end
139
+
140
+ data_criteria.subset_operators ||= []
141
+ # add subset operator to data criteria
142
+ data_criteria.subset_operators << subset_operator unless data_criteria.has_subset(subset_operator)
143
+ end
144
+ precondition.reference = HQMF::Reference.new(data_criteria.id)
145
+ elsif (restriction.single_target?)
146
+ subset_operator = HQMF::SubsetOperator.new(type, value)
147
+ data_criteria.subset_operators ||= []
148
+ # add subset operator to data criteria
149
+ data_criteria.subset_operators << subset_operator unless data_criteria.has_subset(subset_operator)
150
+ end
151
+
152
+ restriction.converted=true
153
+ end
154
+
155
+ # Simple class to issue monotonically increasing integer identifiers
156
+ class Counter
157
+ def initialize
158
+ @count = 0
159
+ end
160
+
161
+ def next
162
+ @count+=1
163
+ end
164
+ end
165
+ @@ids = Counter.new
166
+
167
+
168
+ end
169
+ end
@@ -0,0 +1,86 @@
1
+ module HQMF
2
+ # Class for cleaning up references to specific occurrences in the tree
3
+ class SpecificOccurrenceConverter
4
+
5
+ def initialize(data_criteria_converter)
6
+ @data_criteria_converter = data_criteria_converter
7
+ end
8
+
9
+ def convert_specific_occurrences(population_criteria)
10
+ population_criteria.each do |population|
11
+ walk_up_tree(population.preconditions)
12
+ end
13
+ end
14
+
15
+ def walk_up_tree(preconditions)
16
+ preconditions.each do |precondition|
17
+ if (has_child_comparison(precondition))
18
+ walk_up_tree(precondition.preconditions)
19
+ end
20
+ unless (precondition.comparison?)
21
+ occurrence_map = get_grouped_occurrences(precondition.preconditions)
22
+ occurrence_map.each do |key, value|
23
+
24
+ ##
25
+ ##### figure out negations
26
+ ##
27
+
28
+ if (precondition.conjunction_code == 'atLeastOneTrue')
29
+
30
+ group = @data_criteria_converter.build_group_data_criteria(value, "#{key}_union", precondition.id, HQMF::DataCriteria::UNION)
31
+ binding.pry
32
+ elsif (precondition.conjunction_code == 'allTrue')
33
+ binding.pry
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def get_grouped_occurrences(preconditions)
42
+
43
+ result = {}
44
+ preconditions.each do |precondition|
45
+ if (precondition.preconditions and !precondition.preconditions.empty?)
46
+ get_grouped_occurrences(precondition.preconditions).each do |key, value|
47
+ if (result[key])
48
+ result[key].concat(value)
49
+ else
50
+ result[key] = value
51
+ end
52
+ end
53
+ else
54
+ data_criteria = @data_criteria_converter.v2_data_criteria_by_id[precondition.reference.id]
55
+ if is_specific_occurrence(data_criteria)
56
+ binding.pry
57
+ result[data_criteria.source_data_criteria] ||= []
58
+ result[data_criteria.source_data_criteria] << data_criteria
59
+ preconditions.delete(precondition)
60
+ binding.pry
61
+ end
62
+ end
63
+ end
64
+ result
65
+
66
+ end
67
+
68
+ def is_specific_occurrence(data_criteria)
69
+ !data_criteria.specific_occurrence.nil?
70
+ end
71
+
72
+ def has_child_comparison(node)
73
+ value = false
74
+ node.preconditions.each do |precondition|
75
+ if (precondition.comparison?)
76
+ value ||= true
77
+ elsif precondition.has_preconditions?
78
+ value ||= has_child_comparison(precondition)
79
+ end
80
+ end if node.preconditions
81
+ value
82
+ end
83
+
84
+
85
+ end
86
+ end
@@ -0,0 +1,70 @@
1
+ module HQMF
2
+ # Class for cleaning up references to specific occurrences in the tree
3
+ class SpecificOccurrenceConverter
4
+
5
+ def initialize(data_criteria_converter)
6
+ @data_criteria_converter = data_criteria_converter
7
+ end
8
+
9
+ def convert_specific_occurrences(population_criteria)
10
+ create_precondition_map(population_criteria)
11
+
12
+ @specific_occurrence_key_mapper = {}
13
+
14
+ specific_occurrences = {}
15
+ @data_criteria_converter.v2_data_criteria.each do |data_criteria|
16
+ if (is_specific_occurrence(data_criteria))
17
+ if (data_criteria.respond_to? :precondition_id and @precondition_map[data_criteria.precondition_id])
18
+ specific_occurrences[data_criteria.source_data_criteria] ||= []
19
+ specific_occurrences[data_criteria.source_data_criteria] << data_criteria
20
+ elsif (data_criteria.children_criteria or data_criteria.subset_operators or data_criteria.temporal_references)
21
+ binding.pry
22
+ end
23
+ end
24
+ end
25
+ specific_occurrences.each do |key, occurrences|
26
+
27
+ occurrences.sort! do |left, right|
28
+ @precondition_map[right.precondition_id].depth <=> @precondition_map[left.precondition_id].depth
29
+ end
30
+
31
+ occurrences.each_cons(2) do |pair|
32
+ parent = get_common_parent(@precondition_map[pair[0].precondition_id], @precondition_map[pair[1].precondition_id])
33
+ binding.pry
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ def get_common_parent(left, right)
40
+ return left if left == right
41
+ get_common_parent(@precondition_parent_map[left.id], @precondition_parent_map[right.id])
42
+ end
43
+
44
+ def create_precondition_map(population_criteria)
45
+ @precondition_map = {}
46
+ @precondition_parent_map = {}
47
+ depth = 0
48
+ population_criteria.each do |population|
49
+ recurse_precondition_map(population, depth)
50
+ end
51
+ end
52
+
53
+ def recurse_precondition_map(node, depth)
54
+ node.preconditions.each do |precondition|
55
+ precondition.depth = depth
56
+ @precondition_map[precondition.id] = precondition
57
+ @precondition_parent_map[precondition.id] = node
58
+ if precondition.has_preconditions?
59
+ depth += 1
60
+ recurse_precondition_map(precondition, depth)
61
+ end
62
+ end if node.preconditions
63
+ end
64
+
65
+ def is_specific_occurrence(data_criteria)
66
+ !data_criteria.specific_occurrence.nil?
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,22 @@
1
+ module HQMF
2
+ class Parser
3
+
4
+ HQMF_VERSION_1 = "1.0"
5
+ HQMF_VERSION_2 = "2.0"
6
+
7
+ def self.parse(hqmf_contents, version, codes = nil)
8
+
9
+
10
+ case version
11
+ when HQMF_VERSION_1
12
+ puts("\tCodes not passed in, cannot backfill properties like gender") unless codes
13
+ HQMF::DocumentConverter.convert(HQMF1::Document.new(hqmf_contents).to_json, codes)
14
+ when HQMF_VERSION_2
15
+ HQMF2::Document.new(hqmf_contents).to_model
16
+ else
17
+ raise "Unsupported HQMF version specified: #{version}"
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,206 @@
1
+ require 'zip/zipfilesystem'
2
+ require 'spreadsheet'
3
+ require 'google_spreadsheet'
4
+ require 'roo'
5
+ require 'iconv'
6
+
7
+ module HQMF
8
+ module ValueSet
9
+ class Parser
10
+
11
+ GROUP_CODE_SET = "GROUPING"
12
+
13
+ ORGANIZATION_TITLE = "Value Set Developer"
14
+ OID_TITLE = "Value Set OID"
15
+ CONCEPT_TITLE = "Value Set Name"
16
+ CATEGORY_TITLE = "QDM Category"
17
+ CODE_SET_TITLE ="Code System"
18
+ VERSION_TITLE = "Code System Version"
19
+ CODE_TITLE = "Code"
20
+ DESCRIPTION_TITLE = "Descriptor"
21
+
22
+ CODE_SYSTEM_NORMALIZER = {
23
+ 'ICD-9'=>'ICD-9-CM',
24
+ 'ICD-10'=>'ICD-10-CM',
25
+ 'HL7 (2.16.840.1.113883.5.1)'=>'HL7'
26
+ }
27
+ IGNORED_CODE_SYSTEM_NAMES = ['Grouping', 'GROUPING' ,'HL7', "Administrative Sex"]
28
+
29
+ def initialize()
30
+ end
31
+
32
+ # import an excel matrix array into mongo
33
+ def parse(file, options={})
34
+ sheet_array = file_to_array(file, options)
35
+ by_oid_ungrouped = cells_to_hashs_by_oid(sheet_array)
36
+ collapse_groups(by_oid_ungrouped)
37
+ end
38
+
39
+ def collapse_groups(by_oid_ungrouped)
40
+
41
+ final = []
42
+
43
+ # select the grouped code sets and fill in the children... also remove the children that are a
44
+ # member of a group. We remove the children so that we can create parent groups for the orphans
45
+ (by_oid_ungrouped.select {|key,value| value["code_set"].upcase == GROUP_CODE_SET}).each do |key, value|
46
+ # remove the group so that it is not in the orphan list
47
+ by_oid_ungrouped.delete(value["oid"])
48
+ codes = []
49
+ value["codes"].each do |child_oid|
50
+ # codes << by_oid_ungrouped.delete(child_oid)
51
+ # do not delete the children of a group. These may be referenced by other groups or directly by the measure
52
+ code = by_oid_ungrouped[child_oid]
53
+ puts "\tcode could not be found: #{child_oid}" unless code
54
+ codes << code if code
55
+ # for hierarchies we need to probably have codes be a hash that we select from if we don't find the
56
+ # element in by_oid_ungrouped we may need to look for it in final
57
+ end
58
+ value["code_sets"] = codes
59
+ value.delete("codes")
60
+ value.delete("code_set")
61
+ final << value
62
+ end
63
+
64
+ # fill out the orphans
65
+ by_oid_ungrouped.each do |key, orphan|
66
+ final << adopt_orphan(orphan)
67
+ end
68
+
69
+ deleted = []
70
+ final.delete_if {|x| to_delete = x['code_sets'].nil? || x['code_sets'].empty?; deleted << x if to_delete; to_delete }
71
+ deleted.each do |value|
72
+ puts "\tDeleted value set with no code sets: #{value['oid']}"
73
+ end
74
+ final
75
+
76
+ end
77
+
78
+ def adopt_orphan(orphan)
79
+ parent = orphan.dup
80
+ parent["code_sets"] = [orphan]
81
+ parent.delete("codes")
82
+ parent.delete("code_set")
83
+ parent
84
+ end
85
+
86
+ # take an excel matrix array and turn it into an array of db models
87
+ def cells_to_hashs_by_oid(array)
88
+ a = Array.new(array) # new variable for reentrant
89
+ headers = a.shift.map {|i| i.to_s } # because of this shift
90
+ string_data = a.map {|row| row.map {|cell| cell.to_s } }
91
+ array_of_hashes = string_data.map {|row| Hash[*headers.zip(row).flatten] }
92
+
93
+ by_oid = {}
94
+ array_of_hashes.each do |row|
95
+ entry = convert_row(row)
96
+
97
+ existing = by_oid[entry["oid"]]
98
+ if (existing)
99
+ existing["codes"].concat(entry["codes"])
100
+ else
101
+ by_oid[entry["oid"]] = entry
102
+ end
103
+ end
104
+
105
+ by_oid
106
+ end
107
+
108
+ def self.get_format(file_path)
109
+ if file_path =~ /xls$/
110
+ :xls
111
+ elsif file_path =~ /xlsx$/
112
+ :xlsx
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def convert_row(row)
119
+ # Value Set Developer
120
+ # Value Set OID
121
+ # Value Set Name
122
+ # QDM Category
123
+ # Code System
124
+ # Code System Version
125
+ # Code
126
+ # Descriptor
127
+ value = {
128
+ "key" => normalize_names(row[CATEGORY_TITLE],row[CONCEPT_TITLE]),
129
+ "organization" => row[ORGANIZATION_TITLE],
130
+ "oid" => row[OID_TITLE].strip.gsub(/[^0-9\.]/i, ''),
131
+ "concept" => normalize_names(row[CONCEPT_TITLE]),
132
+ "category" => normalize_names(row[CATEGORY_TITLE]),
133
+ "code_set" => normalize_code_system(row[CODE_SET_TITLE]),
134
+ "version" => row[VERSION_TITLE],
135
+ "codes" => extract_code(row[CODE_TITLE], row[CODE_SET_TITLE]),
136
+ "description" => row[DESCRIPTION_TITLE]
137
+ }
138
+ value['codes'].map! {|code| code.strip.gsub(/[^0-9\.]/i, '')} if (value['code_set'].upcase == GROUP_CODE_SET)
139
+ value
140
+ end
141
+
142
+ # Break all the supplied strings into separate words and return the resulting list as a
143
+ # new string with each word separated with '_'
144
+ def normalize_names(*components)
145
+ name = []
146
+ components.each do |component|
147
+ name.concat component.gsub(/\W/,' ').split.collect { |word| word.strip.downcase }
148
+ end
149
+ name.join '_'
150
+ end
151
+
152
+ def normalize_code_system(code_system_name)
153
+ code_system_name = CODE_SYSTEM_NORMALIZER[code_system_name] if CODE_SYSTEM_NORMALIZER[code_system_name]
154
+ return code_system_name if IGNORED_CODE_SYSTEM_NAMES.include? code_system_name
155
+ oid = HealthDataStandards::Util::CodeSystemHelper.oid_for_code_system(code_system_name)
156
+ puts "\tbad code system name: #{code_system_name}" unless oid
157
+ code_system_name
158
+ end
159
+
160
+ def extract_code(code, set)
161
+
162
+ code.strip!
163
+ if set=='CPT' && code.include?('-')
164
+ eval(code.strip.gsub('-','..')).to_a.collect { |i| i.to_s }
165
+ else
166
+ [code]
167
+ end
168
+
169
+ end
170
+
171
+ def file_to_array(file_path, options)
172
+ defaults = {
173
+ :columns => 2, # range of import
174
+ :sheet => 1 # only one sheet at a time can be worked on
175
+ }
176
+ options = defaults.merge(options)
177
+
178
+ book = book_by_format(file_path, options[:format])
179
+ book.default_sheet=book.sheets[options[:sheet]]
180
+
181
+ # catch double byte encoding problems in spreadsheet files
182
+ # Encoding::InvalidByteSequenceError: "\x9E\xDE" on UTF-16LE
183
+ begin
184
+ book.to_matrix.to_a
185
+ rescue Encoding::InvalidByteSequenceError => e
186
+ raise "Spreadsheet encoding problem: #{e}"
187
+ end
188
+ end
189
+
190
+ def book_by_format(file_path, format)
191
+ format = HQMF::ValueSet::Parser.get_format(file_path) unless format
192
+
193
+ if format == :xls
194
+ book = Excel.new(file_path, nil, :ignore)
195
+ elsif format == :xlsx
196
+ book = Excelx.new(file_path, nil, :ignore)
197
+ else
198
+ raise "File does not end in .xls or .xlsx"
199
+ end
200
+ book
201
+ end
202
+
203
+
204
+ end
205
+ end
206
+ end