diff --git a/Gemfile b/Gemfile index 7362629bf..d36719329 100644 --- a/Gemfile +++ b/Gemfile @@ -85,10 +85,9 @@ gem 'nuntium_api', '~> 0.21' gem 'sentry-raven', '~> 2.13' # Assets -gem 'execjs', '< 2.8.0' # 2.8 removed support for therubyracer gem 'gon', '~> 6.0' gem 'sass-rails', '~> 5.0', '< 5.0.8' -gem 'therubyracer', '~> 0.12' # FIXME: deprecated for years +gem 'mini_racer' gem 'turbolinks', '~> 2.5' # TODO: upgrade to '~> 5' gem 'uglifier', '~> 2.7' diff --git a/Gemfile.lock b/Gemfile.lock index 6a246628a..2904a888c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -320,7 +320,7 @@ GEM leaflet-rails (0.7.7) letter_opener (1.8.0) launchy (>= 2.2, < 3) - libv8 (3.16.14.19) + libv8-node (15.14.0.1) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -336,6 +336,8 @@ GEM mime-types (2.99.3) mini_mime (1.1.2) mini_portile2 (2.4.0) + mini_racer (0.4.0) + libv8-node (~> 15.14.0.0) minitest (5.15.0) multi_json (1.15.0) multi_test (0.1.2) @@ -446,7 +448,6 @@ GEM recaptcha (4.9.0) json redis (3.3.5) - ref (2.0.0) request_store (1.5.1) rack (>= 1.4) responders (2.4.1) @@ -537,9 +538,6 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - therubyracer (0.12.3) - libv8 (~> 3.16.14.15) - ref thor (1.2.1) thread_safe (0.3.6) tilt (2.0.10) @@ -601,7 +599,6 @@ DEPENDENCIES dropzonejs-rails (~> 0.8.4) elasticsearch (~> 1.0) encryptor (~> 1.3) - execjs (< 2.8.0) faker (< 1.9.2) ffaker (< 2.12.0) geojson_import! @@ -619,6 +616,7 @@ DEPENDENCIES location_service! lodash-rails (~> 3.10.1) machinist (~> 2.0) + mini_racer mysql2 (~> 0.3) nokogiri (~> 1.6, < 1.10.0) nuntium_api (~> 0.21) @@ -662,7 +660,6 @@ DEPENDENCIES spring-commands-rspec spring-watcher-listen (~> 2.0.0) sprockets-rails (< 3.3.0) - therubyracer (~> 0.12) timecop (~> 0.8) turbolinks (~> 2.5) uglifier (~> 2.7) @@ -673,4 +670,4 @@ DEPENDENCIES wicked_pdf (~> 2.1) BUNDLED WITH - 1.16.1 + 1.17.3 diff --git a/app/models/manifest_field_mapping.rb b/app/models/manifest_field_mapping.rb index e48a16d12..c9891f75a 100644 --- a/app/models/manifest_field_mapping.rb +++ b/app/models/manifest_field_mapping.rb @@ -1,3 +1,5 @@ +require_relative "./xml_message_serializer" + class ManifestFieldMapping include DateDistanceHelper @@ -121,45 +123,35 @@ def check_op(node, name) end def run_script(script, data) - ctx = V8::Context.new - begin - ctx["message"] = data + ctx = MiniRacer::Context.new - if @device - ctx["device"] = script_device(@device) - if site = @device.site - ctx["site"] = script_site(site) - end - if location = site.try(&:location) - ctx["location"] = script_location(location) - end + if data.is_a?(Nokogiri::XML::Node) + ctx.eval "var message = #{data.cdx_serializable_hash.to_json};" + ctx.attach "message.xpath", ->(query) { data.xpath(query).cdx_serializable_hash } + else + ctx.eval "var message = #{data.to_json};" + end + + if @device + ctx.eval "var device = #{script_device(@device).to_json};" + + if site = @device.site + ctx.eval "var site = #{script_site(site).to_json};" end + if location = site.try(&:location) + ctx.eval "var location = #{script_location(location).to_json};" + end + end - result = ctx.eval(script) - result = to_ruby(result) - result - rescue V8::Error => e + begin + ctx.eval(script) + rescue MiniRacer::Error => e raise ManifestParsingError.script_error(@field.target_field, e.message) ensure ctx.dispose end end - def to_ruby(v8_object) - case v8_object - when V8::Array - array = v8_object.to_a - array.map! { |elem| to_ruby(elem) } - when V8::Object - hash = v8_object.to_h - hash.each do |key, value| - hash[key] = to_ruby(value) - end - else - v8_object - end - end - def script_device(device) { uuid: device.uuid, diff --git a/app/models/xml_message_serializer.rb b/app/models/xml_message_serializer.rb new file mode 100644 index 000000000..caf646e17 --- /dev/null +++ b/app/models/xml_message_serializer.rb @@ -0,0 +1,62 @@ +# Custom serialization of Nokogiri nodes to trivial data structures, that are +# easy to serialize to another language, such as JavaScript, as used in +# `ManifestFieldMapping#run_script` for example. + +class Nokogiri::XML::Attr + def cdx_name_with_nsprefix + if namespace + "#{namespace.prefix}:#{name}" + else + name + end + end + + def cdx_serializable_hash + { "name" => cdx_name_with_nsprefix, "value" => value } + end +end + +class Nokogiri::XML::CDATA + def cdx_serializable_hash + content + end +end + +class Nokogiri::XML::Document + def cdx_serializable_hash + root.cdx_serializable_hash + end +end + +class Nokogiri::XML::Element + def cdx_serializable_hash + hsh = { "name" => name } + + unless attribute_nodes.empty? + hsh["attributes"] = attribute_nodes + .each_with_object({}) { |attr, h| h[attr.cdx_name_with_nsprefix] = attr.value } + end + + unless children.empty? + hsh["children"] = children + .map { |node| node.cdx_serializable_hash if node.respond_to?(:cdx_serializable_hash) } + .compact + end + + hsh + end +end + +class Nokogiri::XML::NodeSet + def cdx_serializable_hash + to_a + .map { |node| node.cdx_serializable_hash if node.respond_to?(:cdx_serializable_hash) } + .compact + end +end + +class Nokogiri::XML::Text + def cdx_serializable_hash + content + end +end diff --git a/spec/models/manifest_operations_spec.rb b/spec/models/manifest_operations_spec.rb index f9412b5f9..c38b68092 100644 --- a/spec/models/manifest_operations_spec.rb +++ b/spec/models/manifest_operations_spec.rb @@ -122,10 +122,26 @@ {"sample" => {"custom" => {"fields" => "#{loc.name},#{loc.lat.to_i},#{loc.lng.to_i}"}, "pii" => {}, "core" => {}}}, device end - it "loads xml in javascript" do + it "loads xml in javascript (serialized hash)" do assert_manifest_application %( { - "patient.name": {"script": "message.xpath('Patient/@name').first().value"} + "patient.name": {"script": "message.children.find(function (node) { return node.name === 'Patient' }).attributes.name"} + } + ), %( + { + } + ), {xml: %( + + + + )}, + "patient" => {"custom" => {}, "pii" => {"name" => "Socrates"}, "core" => {}} + end + + it "loads xml in javascript (xpath)" do + assert_manifest_application %( + { + "patient.name": {"script": "message.xpath('Patient/@name')[0].value"} } ), %( {