oddb2xml 2.6.9 → 2.7.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +40 -0
- data/.standard.yml +2 -0
- data/Elexis_Artikelstamm_v5.xsd +0 -3
- data/Gemfile +3 -3
- data/History.txt +28 -0
- data/README.md +3 -3
- data/Rakefile +24 -23
- data/bin/check_artikelstamm +11 -11
- data/bin/compare_v5 +23 -23
- data/bin/oddb2xml +14 -13
- data/lib/oddb2xml.rb +1 -1
- data/lib/oddb2xml/builder.rb +1075 -1048
- data/lib/oddb2xml/calc.rb +232 -233
- data/lib/oddb2xml/chapter_70_hack.rb +38 -32
- data/lib/oddb2xml/cli.rb +252 -235
- data/lib/oddb2xml/compare.rb +70 -59
- data/lib/oddb2xml/compositions_syntax.rb +448 -430
- data/lib/oddb2xml/compressor.rb +20 -20
- data/lib/oddb2xml/downloader.rb +156 -128
- data/lib/oddb2xml/extractor.rb +295 -302
- data/lib/oddb2xml/options.rb +34 -35
- data/lib/oddb2xml/parslet_compositions.rb +263 -269
- data/lib/oddb2xml/semantic_check.rb +39 -33
- data/lib/oddb2xml/util.rb +166 -164
- data/lib/oddb2xml/version.rb +1 -1
- data/lib/oddb2xml/xml_definitions.rb +32 -33
- data/oddb2xml.gemspec +32 -31
- data/spec/artikelstamm_spec.rb +116 -135
- data/spec/builder_spec.rb +495 -524
- data/spec/calc_spec.rb +552 -593
- data/spec/check_artikelstamm_spec.rb +26 -26
- data/spec/cli_spec.rb +173 -174
- data/spec/compare_spec.rb +9 -11
- data/spec/composition_syntax_spec.rb +390 -409
- data/spec/compressor_spec.rb +48 -48
- data/spec/data/refdata_NonPharma.xml +0 -3
- data/spec/data/refdata_Pharma.xml +0 -26
- data/spec/data/transfer.dat +1 -0
- data/spec/data/varia_De.htm +2 -2
- data/spec/data_helper.rb +47 -49
- data/spec/downloader_spec.rb +251 -260
- data/spec/extractor_spec.rb +172 -164
- data/spec/galenic_spec.rb +233 -256
- data/spec/options_spec.rb +116 -119
- data/spec/parslet_spec.rb +833 -861
- data/spec/spec_helper.rb +153 -153
- data/test_options.rb +39 -42
- data/tools/win_fetch_cacerts.rb +2 -3
- metadata +48 -5
- data/.travis.yml +0 -29
data/lib/oddb2xml/compare.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
-
|
2
|
-
require 'xmlsimple'
|
1
|
+
require "xmlsimple"
|
3
2
|
|
4
3
|
module Oddb2xml
|
5
4
|
def self.log_timestamp(msg)
|
6
5
|
full_msg = "#{Time.now.strftime("%H:%M:%S")}: #{msg}"
|
7
6
|
puts full_msg
|
8
|
-
|
7
|
+
$stdout.flush
|
9
8
|
full_msg
|
10
9
|
end
|
10
|
+
|
11
11
|
class StammXML
|
12
12
|
V3_NAME_REG = /_([N,P])_/
|
13
13
|
attr_accessor :components
|
14
14
|
attr_reader :keys, :sub_key_names, :filename, :basename, :version, :hash
|
15
|
-
def initialize(filename, components =
|
15
|
+
def initialize(filename, components = ["ITEMS"])
|
16
16
|
raise "File #{filename} must exist" unless File.exist?(filename)
|
17
17
|
@filename = filename
|
18
18
|
@basename = File.basename(filename)
|
19
|
-
@version =
|
19
|
+
@version = V3_NAME_REG.match(filename) ? 3 : 5
|
20
20
|
@components = components
|
21
21
|
if @version == 5
|
22
22
|
@hash = load_file(@filename)
|
@@ -24,41 +24,47 @@ module Oddb2xml
|
|
24
24
|
raise "Unsupported version #{@version}"
|
25
25
|
end
|
26
26
|
end
|
27
|
+
|
27
28
|
def self.get_component_key_name(component_name)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
return "LIMNAMEBAG" if /LIMITATION/i.match?(component_name)
|
30
|
+
return "PRODNO" if /PRODUCT/i.match?(component_name)
|
31
|
+
return "GTIN" if /ITEM/i.match?(component_name)
|
32
|
+
raise "Cannot determine keyname for component #{component_name}"
|
32
33
|
end
|
34
|
+
|
33
35
|
def get_limitation_from_v5(item)
|
34
|
-
get_item(
|
36
|
+
get_item("PRODUCTS", item["PRODNO"].first.to_i)["LIMNAMEBAG"] ? ["true"] : nil
|
35
37
|
end
|
38
|
+
|
36
39
|
def get_field_from_v5_product(item, field_name)
|
37
|
-
get_item(
|
40
|
+
get_item("PRODUCTS", item["PRODNO"].first.to_i)[field_name]
|
38
41
|
end
|
42
|
+
|
39
43
|
def get_items(component_name)
|
40
44
|
if @version == 3
|
41
|
-
|
45
|
+
@hash[component_name]
|
42
46
|
else
|
43
|
-
|
47
|
+
@hash[component_name].first.values.first
|
44
48
|
end
|
45
|
-
items
|
46
49
|
end
|
50
|
+
|
47
51
|
def get_item(component_name, id)
|
48
52
|
keyname = StammXML.get_component_key_name(component_name)
|
49
|
-
get_items(component_name).find{|item| item[keyname].first.to_i == id}
|
53
|
+
get_items(component_name).find { |item| item[keyname].first.to_i == id }
|
50
54
|
end
|
55
|
+
|
51
56
|
def load_file(name)
|
52
|
-
Oddb2xml.log_timestamp "Reading #{name} #{(File.size(name)/1024/1024).to_i} MB. This may take some time"
|
57
|
+
Oddb2xml.log_timestamp "Reading #{name} #{(File.size(name) / 1024 / 1024).to_i} MB. This may take some time"
|
53
58
|
XmlSimple.xml_in(IO.read(name))
|
54
59
|
end
|
55
60
|
end
|
61
|
+
|
56
62
|
class CompareV5
|
57
63
|
DEFAULTS = {
|
58
|
-
:
|
59
|
-
:
|
60
|
-
:
|
61
|
-
:
|
64
|
+
components: ["PRODUCTS", "LIMITATIONS", "ITEMS"],
|
65
|
+
fields_to_ignore: ["COMP", "DOSAGE_FORMF", "MEASUREF"],
|
66
|
+
fields_as_floats: ["PEXT", "PEXF", "PPUB"],
|
67
|
+
min_diff_for_floats: 0.01
|
62
68
|
}
|
63
69
|
def initialize(left, right, options = DEFAULTS.clone)
|
64
70
|
@options = options
|
@@ -68,37 +74,38 @@ module Oddb2xml
|
|
68
74
|
@occurrences = {}
|
69
75
|
@report = []
|
70
76
|
end
|
71
|
-
|
72
|
-
|
77
|
+
|
78
|
+
def get_keys(items, key = "GTIN")
|
79
|
+
items.collect { |item| item[key].first.to_i }
|
73
80
|
end
|
81
|
+
|
74
82
|
def get_names(items)
|
75
|
-
|
83
|
+
items.collect { |item| item.keys }.flatten.uniq.sort
|
76
84
|
end
|
85
|
+
|
77
86
|
def compare
|
78
87
|
show_header("Start comparing #{@left.filename} with #{@right.filename}")
|
79
88
|
(@left.components & @right.components).each do |name|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
(
|
96
|
-
compare_details(name, compare_names, id)
|
97
|
-
end
|
98
|
-
key_results_details(name, compare_names, l_keys, r_keys)
|
99
|
-
rescue => error
|
100
|
-
puts "Execution failed with #{error}"
|
89
|
+
puts "\n#{Time.now.strftime("%H:%M:%S")}: Comparing #{name} in #{@left.basename} with #{@right.basename}"
|
90
|
+
key = StammXML.get_component_key_name(name)
|
91
|
+
left_items = @left.get_items(name)
|
92
|
+
next unless left_items
|
93
|
+
right_items = @right.get_items(name)
|
94
|
+
next unless right_items
|
95
|
+
@diff_stat[name] = {}
|
96
|
+
@occurrences[name] = {}
|
97
|
+
@diff_stat[name][NR_COMPARED] = 0
|
98
|
+
l_names = get_names(left_items)
|
99
|
+
r_names = get_names(right_items)
|
100
|
+
compare_names = l_names & r_names
|
101
|
+
l_keys = get_keys(left_items, key)
|
102
|
+
r_keys = get_keys(right_items, key)
|
103
|
+
(l_keys & r_keys).each do |id|
|
104
|
+
compare_details(name, compare_names, id)
|
101
105
|
end
|
106
|
+
key_results_details(name, compare_names, l_keys, r_keys)
|
107
|
+
rescue => error
|
108
|
+
puts "Execution failed with #{error}"
|
102
109
|
end
|
103
110
|
show_header("Summary comparing #{@left.filename} with #{@right.filename}")
|
104
111
|
puts "Ignored differences in #{@options[:fields_to_ignore]}. Signaled when differences in #{@options[:fields_as_floats]} were bigger than #{@options[:min_diff_for_floats]}"
|
@@ -116,28 +123,31 @@ module Oddb2xml
|
|
116
123
|
puts "Execution failed with #{error}"
|
117
124
|
raise error
|
118
125
|
end
|
126
|
+
|
119
127
|
private
|
120
|
-
|
121
|
-
|
128
|
+
|
129
|
+
NR_COMPARED = "NR_COMPARED"
|
130
|
+
COUNT = "_count"
|
122
131
|
def show_header(header)
|
123
132
|
text = Oddb2xml.log_timestamp(header)
|
124
133
|
pad = 5
|
125
134
|
puts
|
126
|
-
puts
|
127
|
-
puts
|
128
|
-
puts
|
135
|
+
puts "-" * (text.length + 2 * pad)
|
136
|
+
puts "".ljust(pad) + text
|
137
|
+
puts "-" * (text.length + 2 * pad)
|
129
138
|
puts
|
130
139
|
end
|
140
|
+
|
131
141
|
def compare_details(component_name, compare_names, id)
|
132
142
|
l_item = @left.get_item(component_name, id)
|
133
143
|
r_item = @right.get_item(component_name, id)
|
134
144
|
found_one = false
|
135
145
|
length = 32
|
136
146
|
found = false
|
137
|
-
detail_name = l_item[
|
147
|
+
detail_name = l_item["DSCR"] ? l_item["DSCR"].first[0..length - 1].rjust(length) : "".rjust(length)
|
138
148
|
details = "Diff in #{id.to_s.ljust(15)} #{detail_name}"
|
139
149
|
diff_name = component_name
|
140
|
-
diff_name +=
|
150
|
+
diff_name += "S" unless /S$/.match?(diff_name)
|
141
151
|
@diff_stat[diff_name] ||= {}
|
142
152
|
@occurrences[diff_name] ||= {}
|
143
153
|
@diff_stat[diff_name][NR_COMPARED] ||= 0
|
@@ -154,33 +164,34 @@ module Oddb2xml
|
|
154
164
|
r_float = r_value ? r_value.first.to_f : 0.0
|
155
165
|
next if (l_float - r_float).abs < @options[:min_diff_for_floats]
|
156
166
|
end
|
157
|
-
next if (r_value.is_a?(Array) &&
|
158
|
-
|
167
|
+
next if (r_value.is_a?(Array) && "--missing--".eql?(r_value.first)) || (l_value.is_a?(Array) && "--missing--".eql?(l_value.first))
|
168
|
+
# TODO: get_field_from_v5_product
|
159
169
|
next if r_value.to_s.eql?(l_value.to_s)
|
160
170
|
next if r_value.to_s.upcase.eql?(l_value.to_s.upcase) && @options[:case_insensitive]
|
161
171
|
details += " #{sub_key}: '#{l_value}' != '#{r_value}'"
|
162
172
|
found = found_one = true
|
163
173
|
@diff_stat[diff_name][sub_key] += 1
|
164
174
|
end
|
165
|
-
puts details.gsub(/[\[\]]/,
|
175
|
+
puts details.gsub(/[\[\]]/, "") if found
|
166
176
|
end
|
167
177
|
|
168
178
|
def show_keys(keys, batch_size = 20)
|
169
179
|
0.upto(keys.size) do |idx|
|
170
180
|
next unless idx % batch_size == 0
|
171
|
-
puts
|
181
|
+
puts " " + keys[idx..(idx + batch_size - 1)].join(" ")
|
172
182
|
end
|
173
183
|
end
|
184
|
+
|
174
185
|
def key_results_details(component_name, compare_names, l_keys, r_keys)
|
175
|
-
component_name +=
|
176
|
-
@report <<
|
186
|
+
component_name += "S" unless /S$/.match?(component_name)
|
187
|
+
@report << "#{component_name}: Found #{l_keys.size} items only in #{@left.basename} #{r_keys.size} items only in #{@right.basename}, compared #{@diff_stat[component_name][NR_COMPARED]} items"
|
177
188
|
keys = r_keys - l_keys
|
178
|
-
head = "#{component_name}: #{
|
189
|
+
head = "#{component_name}: #{keys.size} keys only in #{@right.basename}"
|
179
190
|
puts "#{head}: Keys were #{keys.size}"
|
180
191
|
show_keys(keys)
|
181
192
|
@report << head
|
182
193
|
keys = l_keys - r_keys
|
183
|
-
head = "#{component_name}: #{
|
194
|
+
head = "#{component_name}: #{keys.size} keys only in #{@left.basename}"
|
184
195
|
puts "#{head}: Keys were #{keys.size}"
|
185
196
|
show_keys(keys)
|
186
197
|
@report << head
|
@@ -1,554 +1,572 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
# This file is shared since oddb2xml 2.0.0 (lib/oddb2xml/parse_compositions.rb)
|
4
2
|
# with oddb.org src/plugin/parse_compositions.rb
|
5
3
|
#
|
6
4
|
# It allows an easy parsing of the column P Zusammensetzung of the swissmedic packages.xlsx file
|
7
5
|
#
|
8
6
|
|
9
|
-
require
|
10
|
-
require
|
11
|
-
include Parslet
|
7
|
+
require "parslet"
|
8
|
+
require "parslet/convenience"
|
12
9
|
|
13
10
|
class CompositionParser < Parslet::Parser
|
14
|
-
|
11
|
+
include Parslet
|
15
12
|
# Single character rules
|
16
|
-
rule(:lparen)
|
17
|
-
rule(:rparen)
|
18
|
-
rule(:comma)
|
13
|
+
rule(:lparen) { str("(") }
|
14
|
+
rule(:rparen) { str(")") }
|
15
|
+
rule(:comma) { str(",") }
|
19
16
|
|
20
|
-
rule(:space)
|
21
|
-
rule(:space?)
|
17
|
+
rule(:space) { match('\s').repeat(1) }
|
18
|
+
rule(:space?) { space.maybe }
|
22
19
|
|
23
20
|
# Things
|
24
|
-
rule(:digit) { match(
|
21
|
+
rule(:digit) { match("[0-9]") }
|
25
22
|
rule(:digits) { digit.repeat(1) }
|
26
23
|
rule(:number) {
|
27
24
|
(
|
28
|
-
str(
|
29
|
-
str(
|
25
|
+
str("-").maybe >> (
|
26
|
+
str("0") | (match("[1-9]") >> match("[0-9']").repeat)
|
30
27
|
) >> (
|
31
|
-
(str(
|
32
|
-
(match([
|
33
|
-
|
34
|
-
match(
|
28
|
+
(str("*") >> digit.repeat(1)).maybe >>
|
29
|
+
(match([".,^"]) >> digit.repeat(1)).repeat(1)
|
30
|
+
).maybe >> (
|
31
|
+
match("[eE]") >> (str("+") | str("-")).maybe >> digit.repeat(1)
|
35
32
|
).maybe
|
36
33
|
)
|
37
34
|
}
|
38
|
-
rule(:radio_isotop) {
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
rule(:radio_isotop) {
|
36
|
+
match["a-zA-Z"].repeat(1) >> lparen >> digits >> str("-") >> match["a-zA-Z"].repeat(1 - 3) >> rparen >>
|
37
|
+
((space? >> match["a-zA-Z"]).repeat(1)).repeat(0)
|
38
|
+
} # e.g. Xenonum (133-Xe) or yttrii(90-Y) chloridum zum Kalibrierungszeitpunkt
|
39
|
+
rule(:ratio_value) { match['0-9:\-\.,'].repeat(1) >> space? } # eg. ratio: 1:1, ratio: 1:1.5-2.4., ratio: 1:0.68-0.95, 1:4,1
|
42
40
|
|
43
41
|
# handle stuff like acidum 9,11-linolicum or 2,2'-methylen-bis(6-tert.-butyl-4-methyl-phenolum) specially. it must contain at least one a-z
|
44
|
-
rule(:umlaut) { match([
|
45
|
-
rule(:identifier_D12) { match[
|
42
|
+
rule(:umlaut) { match(["éàèèçïöäüâ"]) }
|
43
|
+
rule(:identifier_D12) { match["a-zA-Z"] >> match["0-9"].repeat(1) }
|
46
44
|
|
47
45
|
# TODO: why do we have to hard code these identifiers?
|
48
46
|
rule(:fix_coded_identifiers) {
|
49
47
|
str("2,2'-methylen-bis(6-tert.-butyl-4-methyl-phenolum)") |
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
48
|
+
str("A + B") |
|
49
|
+
str("CRM 197") |
|
50
|
+
str("F.E.I.B.A.") |
|
51
|
+
str("LA ") >> digit.repeat(1, 2) >> str("% TM") |
|
52
|
+
str("TM:") | str("&") |
|
53
|
+
str("ethanol.") |
|
54
|
+
str("poloxamerum 238") |
|
55
|
+
str("polysorbatum ") >> digit >> digit
|
58
56
|
}
|
59
57
|
|
60
58
|
# TODO: Sind diese Abkürzung wirklich Teil eines Substanznamens?
|
61
|
-
rule(:identifier_abbrev_with_comma)
|
62
|
-
str(
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
59
|
+
rule(:identifier_abbrev_with_comma) {
|
60
|
+
str("aquos") |
|
61
|
+
str("ca.") |
|
62
|
+
str("deklar.") |
|
63
|
+
str("spag.") |
|
64
|
+
str("spec.") |
|
65
|
+
str("spp.") |
|
66
|
+
str("ssp.") |
|
67
|
+
str("var.")
|
70
68
|
}
|
71
69
|
rule(:fix_coded_doses) {
|
72
|
-
digit >> digit.maybe >> space >> str(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
}
|
77
|
-
rule(:identifier)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
70
|
+
digit >> digit.maybe >> space >> str("per centum ") >> str("q.s.").maybe |
|
71
|
+
str("50/50") |
|
72
|
+
str("1g/9.6 cm²") |
|
73
|
+
str("9 g/L 5.4 ml")
|
74
|
+
}
|
75
|
+
rule(:identifier) {
|
76
|
+
fix_coded_identifiers |
|
77
|
+
identifier_abbrev_with_comma |
|
78
|
+
fix_coded_doses |
|
79
|
+
str("q.s.") |
|
80
|
+
identifier_D12 |
|
81
|
+
identifier_without_comma |
|
82
|
+
identifier_with_comma
|
83
|
+
}
|
85
84
|
|
86
85
|
rule(:identifier_with_comma) {
|
87
|
-
match['0-9,\-'].repeat(0) >> (match[
|
86
|
+
match['0-9,\-'].repeat(0) >> (match["a-zA-Z"] | umlaut) >> (match(["_,"]).maybe >> (match['0-9a-zA-Z\-\'\/'] | umlaut)).repeat(0)
|
88
87
|
}
|
89
88
|
|
90
89
|
rule(:identifier_without_comma) {
|
91
|
-
match['0-9\',\-'].repeat(0) >> (match[
|
92
|
-
|
90
|
+
match['0-9\',\-'].repeat(0) >> (match["a-zA-Z"] | umlaut) >> (match(["_"]).maybe >> (match['0-9a-zA-Z\-\'\/'] | umlaut)).repeat(0) >>
|
91
|
+
lparen >> (rparen.absent? >> any).repeat(1) >> rparen
|
93
92
|
}
|
94
|
-
rule(:one_word) { match[
|
93
|
+
rule(:one_word) { match["a-zA-Z"] >> match["0-9"].repeat(1) | match["a-zA-Z"].repeat(1) }
|
95
94
|
rule(:in_parent) { lparen >> one_word.repeat(1) >> rparen }
|
96
95
|
rule(:words_nested) { one_word.repeat(1) >> in_parent.maybe >> space? >> one_word.repeat(0) }
|
97
96
|
# dose
|
98
97
|
# 150 U.I. hFSH et 150 U.I. hLH
|
99
98
|
rule(:units) {
|
100
|
-
|
101
|
-
str(
|
102
|
-
str(
|
103
|
-
str(
|
104
|
-
str(
|
105
|
-
str(
|
106
|
-
str(
|
107
|
-
str(
|
108
|
-
str(
|
109
|
-
str(
|
110
|
-
str(
|
111
|
-
str(
|
112
|
-
str(
|
113
|
-
str(
|
114
|
-
str(
|
115
|
-
str(
|
116
|
-
str(
|
117
|
-
str(
|
118
|
-
str(
|
119
|
-
str(
|
120
|
-
str(
|
121
|
-
str(
|
122
|
-
str(
|
123
|
-
str(
|
124
|
-
str(
|
125
|
-
str(
|
126
|
-
str(
|
127
|
-
str(
|
128
|
-
str(
|
129
|
-
str(
|
130
|
-
str(
|
131
|
-
str(
|
132
|
-
str(
|
133
|
-
str(
|
134
|
-
str(
|
135
|
-
str(
|
136
|
-
str(
|
137
|
-
str(
|
138
|
-
str(
|
139
|
-
str(
|
140
|
-
str(
|
141
|
-
str(
|
142
|
-
str(
|
143
|
-
str(
|
144
|
-
str(
|
145
|
-
str(
|
146
|
-
str(
|
147
|
-
str(
|
148
|
-
str(
|
99
|
+
str("cm²") |
|
100
|
+
str("g/dm²") |
|
101
|
+
str("g/l") |
|
102
|
+
str("g/L") |
|
103
|
+
str("% V/V") |
|
104
|
+
str("µg/24 h") |
|
105
|
+
str("µg/g") |
|
106
|
+
str("µg") |
|
107
|
+
str("ng") |
|
108
|
+
str("guttae") |
|
109
|
+
str("mg/g") |
|
110
|
+
str("mg/ml") |
|
111
|
+
str("MBq/ml") |
|
112
|
+
str("MBq") |
|
113
|
+
str("CFU") |
|
114
|
+
str("mg") |
|
115
|
+
str("Mg") |
|
116
|
+
str("kJ") |
|
117
|
+
str("G") |
|
118
|
+
str("g") |
|
119
|
+
str("I.E.") |
|
120
|
+
str("l") |
|
121
|
+
str("µl") |
|
122
|
+
str("U. Ph. Eur.") |
|
123
|
+
str("ml") |
|
124
|
+
str("µmol") |
|
125
|
+
str("mmol/l") |
|
126
|
+
str("mmol") |
|
127
|
+
str("Mio CFU") |
|
128
|
+
str("Mio U.I.") |
|
129
|
+
str("Mio U.") |
|
130
|
+
str("Mio. U.I.") |
|
131
|
+
str("Mio. U.") |
|
132
|
+
str("Mia. U.I.") |
|
133
|
+
str("Mia. U.") |
|
134
|
+
str("S.U.") |
|
135
|
+
str("U. Botox") |
|
136
|
+
str("U.I. hFSH") |
|
137
|
+
str("U.I. hCG") |
|
138
|
+
str("U.I. hLH") |
|
139
|
+
str("U.I.") |
|
140
|
+
str("U./ml") |
|
141
|
+
str("U.K.I.") |
|
142
|
+
str("U.") |
|
143
|
+
str("Mia.") |
|
144
|
+
str("Mrd.") |
|
145
|
+
str("% m/m") |
|
146
|
+
str("% m/m") |
|
147
|
+
str("%")
|
149
148
|
}
|
150
149
|
rule(:dose_unit) { units.as(:unit) }
|
151
|
-
rule(:qty_range)
|
152
|
-
rule(:qty_unit)
|
153
|
-
rule(:dose_qty)
|
154
|
-
rule(:min_max)
|
150
|
+
rule(:qty_range) { (number >> space? >> (str("+/-") | str(" - ") | str(" -") | str("-") | str("±")) >> space? >> number).as(:qty_range) }
|
151
|
+
rule(:qty_unit) { dose_qty >> (space >> dose_unit).maybe }
|
152
|
+
rule(:dose_qty) { number.as(:qty) }
|
153
|
+
rule(:min_max) { (str("min.") | str("max.") | str("ca.") | str("<")) >> space? }
|
155
154
|
# 75 U.I. hFSH et 75 U.I. hLH
|
156
|
-
rule(:dose_fsh) { qty_unit >> space >> str(
|
157
|
-
rule(:dose_per) { (digits >> str(
|
158
|
-
rule(:dose)
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
155
|
+
rule(:dose_fsh) { qty_unit >> space >> str("et") >> space >> qty_unit.as(:dose_right) }
|
156
|
+
rule(:dose_per) { (digits >> str("/") >> digits).as(:qty) }
|
157
|
+
rule(:dose) {
|
158
|
+
dose_fsh |
|
159
|
+
dose_per |
|
160
|
+
(min_max.maybe >>
|
161
|
+
((qty_range >> (space >> dose_unit).maybe) | (qty_unit | dose_qty | dose_unit)) >> space?) >>
|
162
|
+
str("pro dosi").maybe >> space?
|
163
|
+
}
|
164
|
+
rule(:dose_with_unit) {
|
165
|
+
min_max.maybe >>
|
166
|
+
dose_fsh |
|
167
|
+
(qty_range >> space >> dose_unit |
|
168
|
+
dose_qty >> space >> dose_unit
|
169
|
+
) >>
|
170
|
+
space?
|
171
|
+
}
|
172
|
+
rule(:operator) { match("[+]") >> space? }
|
172
173
|
|
173
174
|
# Grammar parts
|
174
|
-
rule(:useage) {
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
175
|
+
rule(:useage) {
|
176
|
+
(any >> str("berzug:")) | # match Überzug
|
177
|
+
str("antiox.:") |
|
178
|
+
str("arom.:") |
|
179
|
+
str("conserv.:") |
|
180
|
+
str("color.:")
|
181
|
+
}
|
180
182
|
|
181
183
|
# Match Wirkstoffe like E 270
|
182
|
-
rule(:lebensmittel_zusatz) {
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
rule(:der_identifier) { str(
|
184
|
+
rule(:lebensmittel_zusatz) {
|
185
|
+
str("E").as(:lebensmittel_zusatz) >> space >>
|
186
|
+
(digits >> match["(a-z)"].repeat(0, 3)).as(:digits) >>
|
187
|
+
(space >> dose.as(:dose_lebensmittel_zusatz)).maybe >> space?
|
188
|
+
}
|
189
|
+
# DER: 1:4 or DER: 3.5:1 or DER: 6-8:1 or DER: 4.0-9.0:1'
|
190
|
+
rule(:der_identifier) { str("DER:") >> space >> digit >> match['0-9\.\-:'].repeat }
|
189
191
|
rule(:der) { (der_identifier).as(:substance_name) >> space? >> dose.maybe.as(:dose) }
|
190
192
|
rule(:forbidden_in_substance_name) {
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
193
|
+
min_max |
|
194
|
+
useage |
|
195
|
+
excipiens_identifiers |
|
196
|
+
pro_identifiers |
|
197
|
+
corresp_substance_label |
|
198
|
+
(digits.repeat(1) >> space >> str(":")) | # match 50 %
|
199
|
+
str(", corresp.") |
|
200
|
+
str("Beutel: ") |
|
201
|
+
str("Mio ") |
|
202
|
+
str("ad emulsionem") |
|
203
|
+
str("ad globulos") |
|
204
|
+
str("ad pulverem") |
|
205
|
+
str("ad q.s. ") |
|
206
|
+
str("ad solutionem") |
|
207
|
+
str("ad suspensionem") |
|
208
|
+
str("ana partes") |
|
209
|
+
str("ana ") |
|
210
|
+
str("aqua ad ") |
|
211
|
+
str("aqua q.s. ") |
|
212
|
+
str("corresp. ca.,") |
|
213
|
+
str("et ") |
|
214
|
+
str("excipiens") |
|
215
|
+
str("partes") |
|
216
|
+
str("pro capsula") |
|
217
|
+
str("pro dosi") |
|
218
|
+
str("pro vitroe") |
|
219
|
+
str("q.s. ad ") |
|
220
|
+
str("q.s. pro ") |
|
221
|
+
str("ratio:") |
|
222
|
+
str("ut alia: ") |
|
223
|
+
str("ut ")
|
224
|
+
}
|
223
225
|
rule(:name_without_parenthesis) {
|
224
226
|
(
|
225
|
-
(str(
|
226
|
-
(radio_isotop | str(
|
227
|
+
(str("(") | forbidden_in_substance_name).absent? >>
|
228
|
+
(radio_isotop | str("> 1000") | str("> 500") | identifier.repeat(1) >> str(".").maybe) >>
|
227
229
|
space?
|
228
230
|
).repeat(1)
|
229
231
|
}
|
230
232
|
|
231
|
-
rule(:part_with_parenthesis) {
|
232
|
-
|
233
|
-
|
233
|
+
rule(:part_with_parenthesis) {
|
234
|
+
lparen >> ((lparen | rparen).absent? >> any).repeat(1) >>
|
235
|
+
(part_with_parenthesis | rparen >> str("-like:") | rparen) >> space?
|
236
|
+
}
|
234
237
|
rule(:name_with_parenthesis) {
|
235
238
|
forbidden_in_substance_name.absent? >>
|
236
|
-
|
237
|
-
|
239
|
+
((comma | lparen).absent? >> any).repeat(0) >> part_with_parenthesis >>
|
240
|
+
(forbidden_in_substance_name.absent? >> (identifier.repeat(1) | part_with_parenthesis | rparen) >> space?).repeat(0)
|
238
241
|
}
|
239
|
-
rule(:substance_name) {
|
242
|
+
rule(:substance_name) {
|
243
|
+
(
|
240
244
|
name_with_parenthesis |
|
241
245
|
name_without_parenthesis
|
242
246
|
) >>
|
243
|
-
|
244
|
-
|
245
|
-
rule(:simple_substance) { substance_name.as(:substance_name) >> space? >> dose.maybe.as(:dose)}
|
246
|
-
rule(:simple_subtance_with_digits_in_name_and_dose)
|
247
|
+
str("pro dosi").maybe >> space?
|
248
|
+
}
|
249
|
+
rule(:simple_substance) { substance_name.as(:substance_name) >> space? >> dose.maybe.as(:dose) }
|
250
|
+
rule(:simple_subtance_with_digits_in_name_and_dose) {
|
247
251
|
substance_lead.maybe.as(:more_info) >> space? >>
|
248
|
-
|
249
|
-
|
252
|
+
(name_without_parenthesis >> space? >> ((digits.repeat(1) >> (str(" %") | str("%")) | digits.repeat(1)))).as(:substance_name) >>
|
253
|
+
space >> dose_with_unit.as(:dose)
|
250
254
|
}
|
251
255
|
|
252
|
-
|
253
256
|
rule(:substance_more_info) { # e.g. "acari allergeni extractum 5000 U.:
|
254
|
-
|
255
|
-
|
257
|
+
(str("ratio:").absent? >> (identifier | digits) >> space?).repeat(1) >> space? >> (str("U.:") | str(":") | str(".:")) >> space?
|
258
|
+
}
|
256
259
|
|
257
260
|
rule(:pro_identifiers) {
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
}
|
272
|
-
rule(:excipiens_dose) {
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
261
|
+
str("ut aqua ad iniectabilia q.s. ad emulsionem pro ") |
|
262
|
+
str("aqua ").maybe >> str("ad iniectabilia q.s. ad solutionem pro ") |
|
263
|
+
str("aqua ").maybe >> str("ad solutionem pro ") |
|
264
|
+
str("aqua ").maybe >> str("q.s. ad emulsionem pro ") |
|
265
|
+
str("aqua ").maybe >> str("q.s. ad gelatume pro ") |
|
266
|
+
str("aqua ").maybe >> str("q.s. ad solutionem pro ") |
|
267
|
+
str("aqua ").maybe >> str("q.s. ad suspensionem pro ") |
|
268
|
+
str("doses pro vase ") |
|
269
|
+
str("excipiens ad emulsionem pro ") |
|
270
|
+
str("excipiens ad pulverem pro ") |
|
271
|
+
str("excipiens ad solutionem pro ") |
|
272
|
+
str("pro vase ") |
|
273
|
+
str("q.s. ad pulverem pro ")
|
274
|
+
}
|
275
|
+
rule(:excipiens_dose) {
|
276
|
+
pro_identifiers.as(:excipiens_description) >> space? >> dose.as(:dose) >> space? >> ratio.maybe.as(:ratio) >>
|
277
|
+
space? >> str("corresp.").maybe >> space? >> dose.maybe.as(:dose_corresp)
|
278
|
+
}
|
279
|
+
|
280
|
+
rule(:excipiens_identifiers) {
|
281
|
+
str("ad globulos") |
|
282
|
+
str("ad pulverem") |
|
283
|
+
str("ad solutionem") |
|
284
|
+
str("aether q.s.") |
|
285
|
+
str("ana partes") |
|
286
|
+
str("aqua ad iniectabilia q.s. ad solutionem") |
|
287
|
+
str("aqua ad iniectabilia") |
|
288
|
+
str("aqua q.s. ad") |
|
289
|
+
str("excipiens pro compresso obducto") |
|
290
|
+
str("excipiens pro compresso") |
|
291
|
+
str("excipiens pro praeparatione") |
|
292
|
+
str("excipiens") |
|
293
|
+
str("pro charta") |
|
294
|
+
str("pro praeparatione") |
|
295
|
+
str("pro vitro") |
|
296
|
+
str("q.s. ad") |
|
297
|
+
str("q.s. pro praeparatione") |
|
298
|
+
str("saccharum ad") |
|
299
|
+
str("solvens (i.v.): aqua ad iniectabilia")
|
300
|
+
}
|
301
|
+
|
302
|
+
rule(:excipiens) {
|
303
|
+
substance_lead.maybe.as(:more_info) >> space? >>
|
304
|
+
(excipiens_dose | (excipiens_identifiers >> any.repeat(0)).as(:excipiens_description)) >>
|
305
|
+
any.repeat(0)
|
306
|
+
}
|
307
|
+
|
308
|
+
rule(:substance_lead) {
|
309
|
+
useage >> space? |
|
310
|
+
str("Beutel:") >> space? |
|
311
|
+
str("residui:") >> space? |
|
312
|
+
str("mineralia:") >> str(":") >> space? |
|
313
|
+
str("Solvens:") >> space? |
|
314
|
+
substance_more_info
|
315
|
+
}
|
310
316
|
rule(:corresp_substance_label) {
|
311
|
-
|
312
|
-
str(
|
313
|
-
str(
|
314
|
-
str(
|
315
|
-
str(
|
316
|
-
|
317
|
-
rule(:ratio) { str(
|
318
|
-
|
319
|
-
rule(:solvens) {
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
317
|
+
str(", corresp. ca.,") |
|
318
|
+
str("corresp. ca.,") |
|
319
|
+
str("corresp.,") |
|
320
|
+
str("corresp.") |
|
321
|
+
str(", corresp.")
|
322
|
+
}
|
323
|
+
rule(:ratio) { str("ratio:") >> space >> ratio_value }
|
324
|
+
|
325
|
+
rule(:solvens) {
|
326
|
+
(str("Solvens:") | str("Solvens (i.m.):")) >> space >> (any.repeat).as(:more_info) >> space? >>
|
327
|
+
(substance.as(:substance) >> str("/L").maybe).maybe >>
|
328
|
+
any.maybe
|
329
|
+
}
|
330
|
+
# Perhaps we could have some syntax sugar to make this more easy?
|
331
|
+
#
|
332
|
+
def tag(opts = {})
|
333
|
+
opts[:close] || false
|
334
|
+
end
|
335
|
+
|
336
|
+
# TODO: what does ut alia: impl?
|
330
337
|
rule(:substance_ut) {
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
338
|
+
(space? >> (str("pro dosi ut ") | str("ut ")) >>
|
339
|
+
space? >> str("alia:").absent? >> substance
|
340
|
+
) >>
|
341
|
+
space?
|
342
|
+
}
|
336
343
|
|
337
344
|
rule(:corresp_substance) {
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
345
|
+
(corresp_substance_label) >> space? >>
|
346
|
+
(
|
347
|
+
substance |
|
348
|
+
dose.as(:dose_corresp_2) |
|
349
|
+
excipiens.as(:excipiens)
|
350
|
+
)
|
344
351
|
}
|
345
352
|
|
346
353
|
rule(:substance) {
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
(space? >> str(
|
354
|
+
(
|
355
|
+
simple_subtance_with_digits_in_name_and_dose |
|
356
|
+
der |
|
357
|
+
substance_lead.maybe.as(:more_info) >> space? >> lebensmittel_zusatz |
|
358
|
+
substance_lead.maybe.as(:more_info) >> space? >> simple_substance >> str("pro dosi").maybe
|
359
|
+
).as(:substance) >>
|
360
|
+
(space? >> str(", ").maybe >> ratio.maybe).as(:ratio) >>
|
354
361
|
space? >> corresp_substance.maybe.as(:chemical_substance) >>
|
355
362
|
space? >> substance_ut.repeat(0).as(:substance_ut)
|
356
363
|
}
|
357
|
-
rule(:histamin) { str(
|
358
|
-
rule(:praeparatio){
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
rule(:
|
365
|
-
rule(:
|
366
|
-
rule(:
|
364
|
+
rule(:histamin) { str("U = Histamin Equivalent Prick").as(:histamin) }
|
365
|
+
rule(:praeparatio) {
|
366
|
+
((one_word >> space?).repeat(1).as(:description) >> str(":") >> space?).maybe >>
|
367
|
+
(name_with_parenthesis | name_without_parenthesis).repeat(1).as(:substance_name) >>
|
368
|
+
number.as(:qty) >> space >> str("U.:") >> space? >>
|
369
|
+
((identifier >> space?).repeat(1).as(:more_info) >> space?).maybe
|
370
|
+
}
|
371
|
+
rule(:substance_separator) { (str(", et ") | comma | str("et ") | str("ut alia: ")) >> space? }
|
372
|
+
rule(:one_substance) { (praeparatio | histamin | substance) >> space? >> ratio.as(:ratio).maybe >> space? }
|
373
|
+
rule(:all_substances) { (one_substance >> substance_separator.maybe).repeat(1) >> space? >> excipiens.as(:excipiens).maybe }
|
374
|
+
rule(:composition) { all_substances }
|
367
375
|
rule(:long_labels) {
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
376
|
+
str("Praeparatio sicca cum solvens: praeparatio sicca:") |
|
377
|
+
str("Praeparatio cryodesiccata") >> (str(":").absent? >> any).repeat(0) >> str(":") |
|
378
|
+
str("Tela cum praeparatione (Panel ") >> digit >> str("):")
|
379
|
+
}
|
372
380
|
rule(:label_id) {
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
381
|
+
(
|
382
|
+
str("V") |
|
383
|
+
str("IV") |
|
384
|
+
str("III") |
|
385
|
+
str("II") |
|
386
|
+
str("I") |
|
387
|
+
str("A") |
|
388
|
+
str("B") |
|
389
|
+
str("C") |
|
390
|
+
str("D") |
|
391
|
+
str("E")
|
384
392
|
)
|
385
393
|
}
|
386
|
-
rule(:label_separator) {
|
387
|
-
rule(:label) {
|
388
|
-
|
389
|
-
|
394
|
+
rule(:label_separator) { (str("):") | str(")")) }
|
395
|
+
rule(:label) {
|
396
|
+
label_id.as(:label) >> space? >>
|
397
|
+
label_separator >> str(",").absent? >>
|
398
|
+
(space? >> (match(/[^:]/).repeat(0)).as(:label_description) >> str(":") >> space).maybe
|
399
|
+
}
|
400
|
+
rule(:leading_label) {
|
401
|
+
label_id >> label_separator >> (str(" et ") | str(", ") | str(" pro usu: ") | space) >>
|
402
|
+
label_id >> label_separator >> any.repeat(1) |
|
403
|
+
long_labels.as(:label) |
|
404
|
+
label
|
390
405
|
}
|
391
|
-
rule(:leading_label) { label_id >> label_separator >> (str(' et ') | str(', ') | str(' pro usu: ') | space) >>
|
392
|
-
label_id >> label_separator >> any.repeat(1) |
|
393
|
-
long_labels.as(:label) |
|
394
|
-
label
|
395
|
-
}
|
396
406
|
rule(:corresp_label) {
|
397
|
-
str(
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
}
|
407
|
-
rule(:corresp_line) {
|
408
|
-
|
409
|
-
|
407
|
+
str("aqua ") |
|
408
|
+
str("excipiens ") |
|
409
|
+
str("doses ") |
|
410
|
+
str("Pulver: ") |
|
411
|
+
str("Diluens: ") |
|
412
|
+
str("Solutio reconstituta:") |
|
413
|
+
str("Corresp., ") |
|
414
|
+
str("Corresp. ") |
|
415
|
+
str("corresp. ")
|
416
|
+
}
|
417
|
+
rule(:corresp_line) {
|
418
|
+
corresp_label >> any.repeat(1).as(:corresp) |
|
419
|
+
((label_id >> label_separator >> space? >> str("et ").maybe).repeat(1) >> any.repeat(1)).as(:corresp)
|
420
|
+
}
|
410
421
|
rule(:corresp_line_neu) { corresp_label >> any.repeat(1).as(:corresp) }
|
411
422
|
|
412
423
|
rule(:solvens_label) {
|
413
|
-
str(
|
414
|
-
|
415
|
-
|
424
|
+
str("Solvens (i.v.)") |
|
425
|
+
str("Solvens (i.m.)") |
|
426
|
+
str("Solvens")
|
416
427
|
}
|
417
|
-
rule(:solvens_line) { solvens_label.as(:label) >> (str(
|
428
|
+
rule(:solvens_line) { solvens_label.as(:label) >> (str(": ") >> composition.repeat(1).as(:composition) | any.repeat(1).as(:corresp)) }
|
418
429
|
|
419
430
|
rule(:multiple_et_line) {
|
420
|
-
|
431
|
+
((label_id >> label_separator >> space? >> (str("pro usu") | str("et "))).repeat(1) >> any.repeat(1)).as(:corresp)
|
421
432
|
}
|
422
433
|
|
423
|
-
rule(:polvac) { label_id.as(:label) >> label_separator >> space? >> composition.as(:composition) >> space? >> str(
|
434
|
+
rule(:polvac) { label_id.as(:label) >> label_separator >> space? >> composition.as(:composition) >> space? >> str(".").maybe >> space? }
|
424
435
|
|
425
|
-
rule(:label_comment_excipiens) { label >> space? >> excipiens.as(:excipiens) >> space? >> str(
|
436
|
+
rule(:label_comment_excipiens) { label >> space? >> excipiens.as(:excipiens) >> space? >> str(".").maybe >> space? }
|
426
437
|
|
427
|
-
rule(:label_id_composition) {
|
428
|
-
|
429
|
-
|
438
|
+
rule(:label_id_composition) {
|
439
|
+
label_id.as(:label) >> label_separator >>
|
440
|
+
((space >> identifier).repeat(1) >> str(":")).maybe.as(:label_description) >>
|
441
|
+
composition.as(:composition)
|
442
|
+
}
|
430
443
|
|
431
444
|
rule(:expression_comp) {
|
432
445
|
solvens_line |
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
446
|
+
multiple_et_line |
|
447
|
+
label_id_composition |
|
448
|
+
corresp_line_neu |
|
449
|
+
leading_label.maybe >> space? >> composition.as(:composition) >> space? >> str(".").maybe >> space? |
|
450
|
+
polvac |
|
451
|
+
label_comment_excipiens |
|
452
|
+
excipiens.as(:composition) |
|
453
|
+
space.repeat(3)
|
441
454
|
}
|
442
455
|
|
443
456
|
rule(:expression_compD) {
|
444
457
|
solvens_line |
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
458
|
+
multiple_et_line.as("multiple_et_line") |
|
459
|
+
label_id_composition.as("label_id_composition") |
|
460
|
+
corresp_line_neu.as("corresp_line_neu") |
|
461
|
+
leading_label.maybe >> space? >> composition.as(:composition) >> space? >> str(".").maybe >> space? |
|
462
|
+
polvac.as("polvac") |
|
463
|
+
label_comment_excipiens.as("label_comment_excipiens") |
|
464
|
+
excipiens.as(:composition) |
|
465
|
+
space.repeat(3)
|
453
466
|
}
|
454
467
|
|
455
468
|
root :expression_comp
|
456
469
|
end
|
457
470
|
|
458
471
|
class GalenicFormParser < CompositionParser
|
472
|
+
rule(:prepation_separator) { str(", ") | str("\n") }
|
459
473
|
|
460
|
-
rule(:
|
461
|
-
|
462
|
-
|
463
|
-
}
|
474
|
+
rule(:prepation_name) {
|
475
|
+
((prepation_separator | lparen).absent? >> any).repeat(1)
|
476
|
+
}
|
464
477
|
rule(:dose_with_pro) {
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
478
|
+
(match("[0-9a-zA-Z]").repeat(1) >>
|
479
|
+
str("/") >>
|
480
|
+
match("[0-9a-zA-Z'%]").repeat(1)
|
481
|
+
).maybe
|
469
482
|
}
|
470
483
|
|
471
484
|
rule(:gal_form) {
|
472
485
|
qty_unit_silent.maybe >>
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
486
|
+
(((str("\n") # |
|
487
|
+
str(",") >> space? >> qty_unit_silent |
|
488
|
+
digits >> str("%")
|
489
|
+
).absent? >> any).repeat(1) >>
|
490
|
+
(lparen >> (rparen.absent? >> any).repeat(1) >> rparen).maybe
|
491
|
+
).as(:galenic_form) >>
|
492
|
+
space?
|
481
493
|
}
|
482
494
|
|
483
495
|
rule(:standard_galenic) {
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
rule(:qty_unit_silent) { number >> space >> units
|
493
|
-
rule(:name_then_dose) {
|
496
|
+
prepation_name.as(:prepation_name) >> space? >>
|
497
|
+
prepation_separator >> space? >>
|
498
|
+
(name_without_parenthesis >> qty_unit_silent >> prepation_separator).maybe >>
|
499
|
+
(qty_unit_silent >> space?).maybe >>
|
500
|
+
(dose_with_pro >> space? >> str(",") >> space?).maybe >>
|
501
|
+
gal_form >> space?
|
502
|
+
}
|
503
|
+
|
504
|
+
rule(:qty_unit_silent) { number >> space >> units }
|
505
|
+
rule(:name_then_dose) {
|
506
|
+
((space.absent? >> any).repeat(1) >>
|
494
507
|
space >> qty_unit_silent).as(:prepation_name) >> space?.as(:galenic_form)
|
495
|
-
|
496
|
-
|
497
|
-
rule(:only_name) {
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
508
|
+
}
|
509
|
+
|
510
|
+
rule(:only_name) {
|
511
|
+
any.repeat(1).as(:prepation_name) >> space?.as(:galenic_form)
|
512
|
+
}
|
513
|
+
|
514
|
+
rule(:name_comma_gal_form) {
|
515
|
+
(space.absent? >> any).repeat(1).as(:prepation_name) >>
|
516
|
+
comma >> space >>
|
517
|
+
any.repeat(1).as(:galenic_form)
|
518
|
+
}
|
519
|
+
rule(:simple_name) {
|
520
|
+
((match(["a-zA-Z0-9,%"]) | str("-") | umlaut).repeat(1)) >>
|
521
|
+
(lparen >> (rparen.absent? >> any.repeat(1)) >> rparen).maybe
|
522
|
+
}
|
507
523
|
rule(:name_gal_form) { # e.g. Dicloabak 0,1% Augentropfen or 35 Clear-Flex 3,86 % Peritonealdialyselösung
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
524
|
+
(simple_name >> space).repeat(1).as(:prepation_name) >>
|
525
|
+
space? >>
|
526
|
+
(dose_with_pro >> space?).maybe >>
|
527
|
+
(digits.absent? >> gal_form) >> space?
|
512
528
|
}
|
513
529
|
|
514
530
|
rule(:name_without_comma_gal_form) { # Sulfure de Rhénium (186Re)-RE-186-MM-1 Cis bio International
|
515
|
-
((str(
|
516
|
-
|
517
|
-
|
518
|
-
}
|
519
|
-
|
520
|
-
# Phytopharma foie et bile capsules/Leber-Galle Kapseln
|
521
|
-
rule(:leber_gallen_kapseln) {
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
531
|
+
((str(", ") | str(",") >> match(["A-Z"])).absent? >> any).repeat(1).as(:prepation_name) >>
|
532
|
+
comma >> space? >>
|
533
|
+
gal_form >> space?
|
534
|
+
}
|
535
|
+
|
536
|
+
# Phytopharma foie et bile capsules/Leber-Galle Kapseln
|
537
|
+
rule(:leber_gallen_kapseln) {
|
538
|
+
((str("/Leber-Galle Kapseln").absent? >> any).repeat(1)).as(:prepation_name) >>
|
539
|
+
str("/") >> any.repeat(1).as(:galenic_form) >>
|
540
|
+
space?
|
541
|
+
}
|
542
|
+
# Plak-out Spray 0,1 %
|
543
|
+
rule(:plak_out_spray) {
|
544
|
+
str("Plak-out Spray 0,1 %").as(:prepation_name) >>
|
545
|
+
space? >> str("dummy").maybe.as(:galenic_form) >> space?
|
546
|
+
}
|
547
|
+
rule(:galenic) {
|
548
|
+
plak_out_spray |
|
549
|
+
leber_gallen_kapseln |
|
550
|
+
standard_galenic |
|
551
|
+
name_comma_gal_form |
|
552
|
+
name_then_dose >> space? |
|
553
|
+
name_without_comma_gal_form |
|
554
|
+
name_gal_form |
|
555
|
+
only_name >> space? |
|
556
|
+
space?
|
557
|
+
}
|
540
558
|
|
541
559
|
# for debugging purposes only
|
542
|
-
rule(:galenicD)
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
560
|
+
rule(:galenicD) {
|
561
|
+
plak_out_spray.as(:plak_out_spray) |
|
562
|
+
leber_gallen_kapseln.as(:leber_gallen_kapseln) |
|
563
|
+
standard_galenic.as(:standard_galenic) |
|
564
|
+
name_comma_gal_form.as(:name_comma_gal_form) |
|
565
|
+
name_then_dose.as(:name_then_dose) >> space? |
|
566
|
+
name_without_comma_gal_form.as(:name_without_comma_gal_form) |
|
567
|
+
name_gal_form.as(:name_gal_form) |
|
568
|
+
only_name.as(:only_name) >> space? |
|
569
|
+
space?
|
570
|
+
}
|
553
571
|
root :galenic
|
554
|
-
end
|
572
|
+
end
|