Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
13 changes: 5 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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!
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -673,4 +670,4 @@ DEPENDENCIES
wicked_pdf (~> 2.1)

BUNDLED WITH
1.16.1
1.17.3
52 changes: 22 additions & 30 deletions app/models/manifest_field_mapping.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require_relative "./xml_message_serializer"

class ManifestFieldMapping
include DateDistanceHelper

Expand Down Expand Up @@ -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,
Expand Down
62 changes: 62 additions & 0 deletions app/models/xml_message_serializer.rb
Original file line number Diff line number Diff line change
@@ -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
20 changes: 18 additions & 2 deletions spec/models/manifest_operations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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: %(
<Message>
<Patient name="Socrates" age="27"/>
</Message>
Comment on lines +134 to +136
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example this XML document is serialized as:

{
  "name": "Message",
  "children": [
    {
      "name": "Patient",
      "attributes": [{"name": "Socrates", "age": "17"}]
    }
  ]
}

)},
"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"}
}
), %(
{
Expand Down