hqmf-parser 1.0.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.
- data/Gemfile +23 -0
- data/README.md +903 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/lib/hqmf-generator/hqmf-generator.rb +308 -0
- data/lib/hqmf-model/attribute.rb +35 -0
- data/lib/hqmf-model/data_criteria.rb +322 -0
- data/lib/hqmf-model/document.rb +172 -0
- data/lib/hqmf-model/population_criteria.rb +90 -0
- data/lib/hqmf-model/precondition.rb +85 -0
- data/lib/hqmf-model/types.rb +318 -0
- data/lib/hqmf-model/utilities.rb +52 -0
- data/lib/hqmf-parser.rb +54 -0
- data/lib/hqmf-parser/1.0/attribute.rb +68 -0
- data/lib/hqmf-parser/1.0/comparison.rb +34 -0
- data/lib/hqmf-parser/1.0/data_criteria.rb +105 -0
- data/lib/hqmf-parser/1.0/document.rb +209 -0
- data/lib/hqmf-parser/1.0/expression.rb +52 -0
- data/lib/hqmf-parser/1.0/population_criteria.rb +79 -0
- data/lib/hqmf-parser/1.0/precondition.rb +89 -0
- data/lib/hqmf-parser/1.0/range.rb +65 -0
- data/lib/hqmf-parser/1.0/restriction.rb +157 -0
- data/lib/hqmf-parser/1.0/utilities.rb +41 -0
- data/lib/hqmf-parser/2.0/data_criteria.rb +319 -0
- data/lib/hqmf-parser/2.0/document.rb +165 -0
- data/lib/hqmf-parser/2.0/population_criteria.rb +53 -0
- data/lib/hqmf-parser/2.0/precondition.rb +44 -0
- data/lib/hqmf-parser/2.0/types.rb +223 -0
- data/lib/hqmf-parser/2.0/utilities.rb +30 -0
- data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +254 -0
- data/lib/hqmf-parser/converter/pass1/document_converter.rb +183 -0
- data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +135 -0
- data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +164 -0
- data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +159 -0
- data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +35 -0
- data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
- data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
- data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +63 -0
- data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
- data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +91 -0
- data/lib/hqmf-parser/converter/pass2/operator_converter.rb +169 -0
- data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter.rb +86 -0
- data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter_bak.rb +70 -0
- data/lib/hqmf-parser/parser.rb +22 -0
- data/lib/hqmf-parser/value_sets/value_set_parser.rb +206 -0
- data/lib/tasks/coverme.rake +8 -0
- data/lib/tasks/hqmf.rake +141 -0
- data/lib/tasks/value_sets.rake +23 -0
- metadata +159 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module HQMF1
|
2
|
+
|
3
|
+
class Expression
|
4
|
+
|
5
|
+
include HQMF1::Utilities
|
6
|
+
|
7
|
+
attr_reader :text, :value, :type
|
8
|
+
|
9
|
+
def initialize(entry)
|
10
|
+
@entry = entry
|
11
|
+
@text = @entry.xpath('./*/cda:derivationExpr').text.strip
|
12
|
+
type = attr_val('./*/cda:value/@xsi:type')
|
13
|
+
case type
|
14
|
+
when 'IVL_PQ'
|
15
|
+
@value = Range.new(@entry.xpath('./*/cda:value'))
|
16
|
+
when 'PQ'
|
17
|
+
@value = Value.new(@entry.xpath('./*/cda:value'))
|
18
|
+
when 'ANYNonNull'
|
19
|
+
@value = HQMF::AnyValue.new
|
20
|
+
else
|
21
|
+
raise "Unknown expression value type #{type}"
|
22
|
+
end
|
23
|
+
@type = match_type
|
24
|
+
end
|
25
|
+
|
26
|
+
def match_type
|
27
|
+
case @text
|
28
|
+
when /^COUNT(.*)$/
|
29
|
+
"COUNT"
|
30
|
+
when /^MIN(.*)$/
|
31
|
+
"MIN"
|
32
|
+
when /^MAX(.*)$/
|
33
|
+
"MAX"
|
34
|
+
when /^DATEDIFF(.*)$/
|
35
|
+
"DATEDIFF"
|
36
|
+
else
|
37
|
+
raise "unknown expression type: #{@text}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_json
|
42
|
+
|
43
|
+
json = build_hash(self, [:text,:type])
|
44
|
+
json[:value] = self.value.to_json if self.value
|
45
|
+
json
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module HQMF1
|
2
|
+
# Represents an HQMF population criteria
|
3
|
+
class PopulationCriteria
|
4
|
+
|
5
|
+
include HQMF1::Utilities
|
6
|
+
|
7
|
+
attr_reader :preconditions, :entry, :doc
|
8
|
+
attr_accessor :id, :hqmf_id, :stratification_id
|
9
|
+
|
10
|
+
# Create a new population criteria from the supplied HQMF entry
|
11
|
+
# @param [Nokogiri::XML::Element] the HQMF entry
|
12
|
+
def initialize(entry, doc)
|
13
|
+
@doc = doc
|
14
|
+
@entry = entry
|
15
|
+
@id = attr_val('cda:observation/cda:id/@root').upcase
|
16
|
+
@preconditions = @entry.xpath('./*/cda:sourceOf[@typeCode="PRCN"]').collect do |entry|
|
17
|
+
pc = Precondition.new(entry, nil, @doc)
|
18
|
+
if pc.preconditions.length==0 && !pc.comparison && pc.restrictions.length==0
|
19
|
+
nil
|
20
|
+
else
|
21
|
+
pc
|
22
|
+
end
|
23
|
+
end.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get the code for the population criteria
|
27
|
+
# @return [String] the code (e.g. IPP, DEMON, NUMER, DENEX, EXCEP)
|
28
|
+
def code
|
29
|
+
value = attr_val('cda:observation/cda:value/@code')
|
30
|
+
# exclusion population criteria has id of DENOM with actionNegationInd of true
|
31
|
+
# special case this to simply handling
|
32
|
+
if attr_val('cda:observation/@actionNegationInd')=='true'
|
33
|
+
value = HQMF::PopulationCriteria::DENEX
|
34
|
+
end
|
35
|
+
# replace measure population with NUMER. MSRPOPL is used in continuous variable calculations.
|
36
|
+
value = HQMF::PopulationCriteria::NUMER if value == 'MSRPOPL'
|
37
|
+
# replace DENEXCEP with EXCEP. DENEXCEP is used by the MAT instead of EXCEP as required by HQMF V2 and QRDA.
|
38
|
+
value = HQMF::PopulationCriteria::EXCEP if value == 'DENEXCEP'
|
39
|
+
value.upcase
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the id for the population criteria, used elsewhere in the HQMF document to
|
43
|
+
# refer to this criteria
|
44
|
+
# @return [String] the id
|
45
|
+
def id
|
46
|
+
@id
|
47
|
+
end
|
48
|
+
|
49
|
+
def title
|
50
|
+
attr_val('cda:observation/cda:value/@displayName')
|
51
|
+
end
|
52
|
+
|
53
|
+
def reference
|
54
|
+
reference = attr_val('./cda:observation/cda:sourceOf[@typeCode="PRCN"]/cda:observation[@classCode="OBS"]/cda:id/@root')
|
55
|
+
reference = reference.upcase if reference
|
56
|
+
reference
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_json
|
60
|
+
|
61
|
+
json = {}
|
62
|
+
self.preconditions.compact.each do |precondition|
|
63
|
+
json[:preconditions] ||= []
|
64
|
+
json[:preconditions] << precondition.to_json
|
65
|
+
end
|
66
|
+
|
67
|
+
json[:id] = id
|
68
|
+
json[:title] = title
|
69
|
+
json[:code] = code
|
70
|
+
json[:hqmf_id] = hqmf_id if hqmf_id
|
71
|
+
json[:stratification_id] = stratification_id if stratification_id
|
72
|
+
json[:reference] = reference
|
73
|
+
|
74
|
+
{self.code => json}
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module HQMF1
|
2
|
+
|
3
|
+
class Precondition
|
4
|
+
|
5
|
+
include HQMF1::Utilities
|
6
|
+
|
7
|
+
attr_reader :restrictions, :preconditions, :subset, :expression, :id
|
8
|
+
|
9
|
+
def initialize(entry, parent, doc)
|
10
|
+
@doc = doc
|
11
|
+
@entry = entry
|
12
|
+
@id = attr_val('./*/cda:id/@root')
|
13
|
+
@restrictions = []
|
14
|
+
|
15
|
+
local_subset = attr_val('./cda:subsetCode/@code')
|
16
|
+
if local_subset
|
17
|
+
@subset = local_subset
|
18
|
+
end
|
19
|
+
#@subset = attr_val('./cda:subsetCode/@code')
|
20
|
+
|
21
|
+
local_restrictions = @entry.xpath('./*/cda:sourceOf[@typeCode!="PRCN" and @typeCode!="COMP"]').collect do |entry|
|
22
|
+
Restriction.new(entry, self, @doc)
|
23
|
+
end
|
24
|
+
@restrictions.concat(local_restrictions)
|
25
|
+
|
26
|
+
@expression = Expression.new(@entry) if @entry.at_xpath('./*/cda:derivationExpr')
|
27
|
+
|
28
|
+
@preconditions = @entry.xpath('./*/cda:sourceOf[@typeCode="PRCN"]').collect do |entry|
|
29
|
+
Precondition.new(entry, self, @doc)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the conjunction code, e.g. AND, OR
|
34
|
+
# @return [String] conjunction code
|
35
|
+
def conjunction
|
36
|
+
attr_val('./cda:conjunctionCode/@code')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return whether the precondition is negated (true) or not (false)
|
40
|
+
def negation
|
41
|
+
if @entry.at_xpath('./cda:act[@actionNegationInd="true"]')
|
42
|
+
is_negation_rationale = (comparison.restrictions.map {|restriction| restriction.type }).include? 'RSON' if comparison
|
43
|
+
if is_negation_rationale
|
44
|
+
false
|
45
|
+
else
|
46
|
+
true
|
47
|
+
end
|
48
|
+
else
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def comparison
|
54
|
+
comparison_def = @entry.at_xpath('./*/cda:sourceOf[@typeCode="COMP"]')
|
55
|
+
if comparison_def
|
56
|
+
data_criteria_id = attr_val('./*/cda:id/@root')
|
57
|
+
@comparison = Comparison.new(data_criteria_id, comparison_def, self, @doc)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def first_comparison
|
62
|
+
if comparison
|
63
|
+
return comparison
|
64
|
+
elsif @preconditions
|
65
|
+
@preconditions.each do |precondition|
|
66
|
+
first = precondition.first_comparison
|
67
|
+
if first
|
68
|
+
return first
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_json
|
76
|
+
|
77
|
+
json = build_hash(self, [:id,:conjunction,:negation,:subset])
|
78
|
+
json[:comparison] = self.comparison.to_json if self.comparison
|
79
|
+
json[:expression] = self.expression.to_json if self.expression
|
80
|
+
json[:preconditions] = json_array(self.preconditions)
|
81
|
+
json[:restrictions] = json_array(self.restrictions)
|
82
|
+
json
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module HQMF1
|
2
|
+
# Represents a bound within a HQMF pauseQuantity, has a value, a unit and an
|
3
|
+
# inclusive/exclusive indicator
|
4
|
+
class Value
|
5
|
+
include HQMF1::Utilities
|
6
|
+
|
7
|
+
def initialize(entry)
|
8
|
+
@entry = entry
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
attr_val('./@value')
|
13
|
+
end
|
14
|
+
|
15
|
+
def unit
|
16
|
+
attr_val('./@unit')
|
17
|
+
end
|
18
|
+
|
19
|
+
def inclusive?
|
20
|
+
case attr_val('./@inclusive')
|
21
|
+
when 'true'
|
22
|
+
true
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_json
|
29
|
+
build_hash(self, [:value,:unit,:inclusive?])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Represents a HQMF pauseQuantity which can have low and high bounds
|
34
|
+
class Range
|
35
|
+
include HQMF1::Utilities
|
36
|
+
attr_reader :low, :high
|
37
|
+
|
38
|
+
def initialize(entry)
|
39
|
+
@entry = entry
|
40
|
+
if @entry
|
41
|
+
@low = optional_value('./cda:low')
|
42
|
+
@high = optional_value('./cda:high')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_json
|
47
|
+
json = {}
|
48
|
+
json[:low] = self.low.to_json if self.low
|
49
|
+
json[:high] = self.high.to_json if self.high
|
50
|
+
json
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def optional_value(xpath)
|
56
|
+
value_def = @entry.at_xpath(xpath)
|
57
|
+
if value_def
|
58
|
+
Value.new(value_def)
|
59
|
+
else
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module HQMF1
|
2
|
+
# Represents a restriction on the allowable values of a data item
|
3
|
+
class Restriction
|
4
|
+
|
5
|
+
include HQMF1::Utilities
|
6
|
+
|
7
|
+
attr_reader :range, :comparison, :restrictions, :subset, :preconditions
|
8
|
+
attr_accessor :from_parent
|
9
|
+
|
10
|
+
def initialize(entry, parent, doc)
|
11
|
+
@doc = doc
|
12
|
+
@entry = entry
|
13
|
+
@restrictions = []
|
14
|
+
|
15
|
+
range_def = @entry.at_xpath('./cda:pauseQuantity')
|
16
|
+
if range_def
|
17
|
+
@range = Range.new(range_def)
|
18
|
+
end
|
19
|
+
|
20
|
+
local_restrictions = @entry.xpath('./*/cda:sourceOf[@typeCode!="PRCN" and @typeCode!="COMP"]').collect do |entry|
|
21
|
+
Restriction.new(entry, self, @doc)
|
22
|
+
end
|
23
|
+
|
24
|
+
@restrictions.concat(local_restrictions)
|
25
|
+
|
26
|
+
local_subset = attr_val('./cda:subsetCode/@code')
|
27
|
+
if local_subset
|
28
|
+
@subset = local_subset
|
29
|
+
end
|
30
|
+
|
31
|
+
#@subset = attr_val('./cda:subsetCode/@code')
|
32
|
+
|
33
|
+
comparison_def = @entry.at_xpath('./*/cda:sourceOf[@typeCode="COMP"]')
|
34
|
+
if comparison_def
|
35
|
+
data_criteria_id = attr_val('./*/cda:id/@root')
|
36
|
+
data_criteria_id = comparison_def.at_xpath('./*/cda:id/@root').value if (data_criteria_id.nil? and comparison_def.at_xpath('./*/cda:id/@root'))
|
37
|
+
@comparison = Comparison.new(data_criteria_id, comparison_def, self, @doc)
|
38
|
+
end
|
39
|
+
|
40
|
+
@preconditions = @entry.xpath('./*/cda:sourceOf[@typeCode="PRCN"]').collect do |entry|
|
41
|
+
# create a dummy parent with a single restriction copied from self minus the
|
42
|
+
# nested preconditions to avoid an infinite loop
|
43
|
+
prior_comparison = nil
|
44
|
+
if parent.class==HQMF1::Precondition
|
45
|
+
prior_comparison = parent.first_comparison
|
46
|
+
else
|
47
|
+
prior_comparison = @comparsion
|
48
|
+
end
|
49
|
+
current_restriction = OpenStruct.new(
|
50
|
+
'range' => @range,
|
51
|
+
'comparison' => prior_comparison,
|
52
|
+
'restrictions' => [],
|
53
|
+
'preconditions' => [],
|
54
|
+
'subset' => @subset,
|
55
|
+
'type' => type,
|
56
|
+
'target_id' => target_id,
|
57
|
+
'field' => field,
|
58
|
+
'field_code' => field_code,
|
59
|
+
'field_time' => field_time,
|
60
|
+
'value' => value)
|
61
|
+
all_restrictions = []
|
62
|
+
all_restrictions.concat @restrictions
|
63
|
+
all_restrictions << current_restriction
|
64
|
+
parent = OpenStruct.new(
|
65
|
+
'restrictions' => all_restrictions,
|
66
|
+
'subset' => @subset
|
67
|
+
)
|
68
|
+
p = Precondition.new(entry, parent, @doc)
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# The type of restriction, e.g. SBS, SBE etc
|
75
|
+
def type
|
76
|
+
attr_val('./@typeCode')
|
77
|
+
end
|
78
|
+
|
79
|
+
# is this type negated? true or false
|
80
|
+
def negation
|
81
|
+
attr_val('./@inversionInd') == "true"
|
82
|
+
end
|
83
|
+
|
84
|
+
# The id of the data criteria or measurement property that the value
|
85
|
+
# will be compared against
|
86
|
+
def target_id
|
87
|
+
attr_val('./*/cda:id/@root')
|
88
|
+
end
|
89
|
+
|
90
|
+
def field
|
91
|
+
attr_val('./cda:observation/cda:code/@displayName')
|
92
|
+
end
|
93
|
+
|
94
|
+
def field_code
|
95
|
+
attr_val('./cda:observation/cda:code/@code') || attr_val('./cda:encounter/cda:participant/cda:roleParticipant/@classCode')
|
96
|
+
end
|
97
|
+
|
98
|
+
def field_time
|
99
|
+
effectiveTime = @entry.at_xpath('./cda:observation/cda:effectiveTime') || @entry.at_xpath('./cda:encounter/cda:participant/cda:roleParticipant/cda:effectiveTime')
|
100
|
+
|
101
|
+
time = nil
|
102
|
+
if effectiveTime
|
103
|
+
time = :start if effectiveTime.at_xpath('./cda:low')
|
104
|
+
time = :end if effectiveTime.at_xpath('./cda:high')
|
105
|
+
end
|
106
|
+
time
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def value
|
111
|
+
value = nil
|
112
|
+
type = attr_val('./cda:observation/cda:value/@xsi:type') || 'CD'
|
113
|
+
case type
|
114
|
+
when 'IVL_PQ'
|
115
|
+
value = HQMF1::Range.new(@entry.xpath('./cda:observation/cda:value'))
|
116
|
+
when 'PQ'
|
117
|
+
value = HQMF1::Value.new(@entry.xpath('./cda:observation/cda:value'))
|
118
|
+
when 'CD'
|
119
|
+
if field && field.downcase == 'status'
|
120
|
+
code = attr_val('./cda:observation/cda:value/@displayName').downcase
|
121
|
+
value = HQMF::Coded.for_single_code('status',code,code)
|
122
|
+
elsif attr_val('./cda:observation/cda:value/@code')
|
123
|
+
oid = attr_val('./cda:observation/cda:value/@code')
|
124
|
+
title = attr_val('./cda:observation/cda:value/@displayName')
|
125
|
+
value = HQMF::Coded.for_code_list(oid,title)
|
126
|
+
elsif attr_val('./cda:encounter/cda:participant/cda:roleParticipant/cda:code/@code')
|
127
|
+
oid = attr_val('./cda:encounter/cda:participant/cda:roleParticipant/cda:code/@code')
|
128
|
+
title = attr_val('./cda:encounter/cda:participant/cda:roleParticipant/cda:code/@displayName')
|
129
|
+
value = HQMF::Coded.for_code_list(oid,title)
|
130
|
+
end
|
131
|
+
when 'ANYNonNull'
|
132
|
+
value = HQMF::AnyValue.new
|
133
|
+
else
|
134
|
+
raise "Unknown restriction value type #{type}"
|
135
|
+
end if type
|
136
|
+
value
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_json
|
140
|
+
return nil if from_parent
|
141
|
+
json = build_hash(self, [:subset,:type,:target_id,:field,:field_code,:from_parent, :negation, :field_time])
|
142
|
+
json[:range] = range.to_json if range
|
143
|
+
if value
|
144
|
+
if value.is_a? String
|
145
|
+
json[:value] = value
|
146
|
+
else
|
147
|
+
json[:value] = value.to_json
|
148
|
+
end
|
149
|
+
end
|
150
|
+
json[:comparison] = comparison.to_json if comparison
|
151
|
+
json[:restrictions] = json_array(self.restrictions)
|
152
|
+
json[:preconditions] = json_array(self.preconditions)
|
153
|
+
json
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module HQMF1
|
2
|
+
module Utilities
|
3
|
+
|
4
|
+
include HQMF::Conversion::Utilities
|
5
|
+
|
6
|
+
# Utility function to handle optional attributes
|
7
|
+
# @param xpath an XPath that identifies an XML attribute
|
8
|
+
# @return the value of the attribute or nil if the attribute is missing
|
9
|
+
def attr_val(xpath)
|
10
|
+
attr = @entry.at_xpath(xpath)
|
11
|
+
if attr
|
12
|
+
attr.value
|
13
|
+
else
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def clean_json(json)
|
19
|
+
json.reject!{|k,v| v.nil? || (v.respond_to?(:empty?) && v.empty?)}
|
20
|
+
end
|
21
|
+
|
22
|
+
def clean_json_recursive(json)
|
23
|
+
json.each do |k,v|
|
24
|
+
if v.is_a? Hash
|
25
|
+
clean_json_recursive(v)
|
26
|
+
clean_json(v)
|
27
|
+
elsif v.is_a? Array
|
28
|
+
v.each do |e|
|
29
|
+
if e.is_a? Hash
|
30
|
+
clean_json_recursive(e)
|
31
|
+
clean_json(e)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
clean_json(json)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|