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
1 change: 1 addition & 0 deletions dcc.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ Gem::Specification.new do |spec|
spec.add_dependency "lutaml-model", "~> 0.8"
spec.add_dependency "moxml", "~> 0.1.26"
spec.add_dependency "mml", "~> 2.4.0"
spec.add_dependency "liquid"
spec.add_dependency "thor"
end
1 change: 1 addition & 0 deletions lib/dcc/cli/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def convert(format, file)
case format
when "json" then ::Dcc::Convert::Json.call(dcc)
when "yaml" then ::Dcc::Convert::Yaml.call(dcc)
when "html" then ::Dcc::Convert::Html.call(dcc)
else
abort "Format not yet implemented: #{format}"
end
Expand Down
120 changes: 12 additions & 108 deletions lib/dcc/convert/html.rb
Original file line number Diff line number Diff line change
@@ -1,127 +1,31 @@
# frozen_string_literal: true

require "liquid"

module Dcc
module Convert
# `Dcc::Convert::Html` renders a parsed DCC into a human-readable HTML
# report with collapsible sections per administrative section and
# measurement result.
module Html
class << self
# @param dcc [Lutaml::Model::Serializable]
# @return [Dcc::Convert::Result]
def call(dcc)
payload = <<~HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DCC Report #{escape(unique_identifier_of(dcc))}</title>
<style>
body { font-family: -apple-system, system-ui, sans-serif; margin: 2rem; }
h1, h2, h3 { color: #333; }
section { margin-bottom: 1.5rem; padding: 1rem; border: 1px solid #eee; border-radius: 4px; }
table { border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 0.5rem 1rem; text-align: left; }
th { background: #f5f5f5; }
</style>
</head>
<body>
<h1>Digital Calibration Certificate</h1>
#{summary_table(dcc)}
#{administrative_section(dcc)}
#{measurement_section(dcc)}
</body>
</html>
HTML
TEMPLATE_PATH = File.expand_path("templates/report.liquid", __dir__).freeze

class << self
def call(dcc, lang: nil)
hash = ::Dcc::Convert::Json.xml_to_hash(dcc.to_xml)
rendered = render_template(hash, lang: lang)
::Dcc::Convert::Result.new(
format: :html,
payload: payload,
payload: rendered,
source_class: dcc.class.name,
schema_version: dcc.schema_version.to_s,
)
end

private

def summary_table(dcc)
<<~HTML
<section>
<h2>Identity</h2>
<table>
<tr><th>Schema version</th><td>#{escape(dcc.schema_version)}</td></tr>
<tr><th>Unique identifier</th><td>#{escape(unique_identifier_of(dcc))}</td></tr>
<tr><th>Country</th><td>#{escape(country_of(dcc))}</td></tr>
</table>
</section>
HTML
end

def administrative_section(dcc)
return "" unless Dcc::TypeGuards.has_attribute?(dcc, :administrative_data) && dcc.administrative_data

admin = dcc.administrative_data
items = Dcc::TypeGuards.has_attribute?(admin, :items) && admin.items ? Array(admin.items.item) : []
<<~HTML
<section>
<h2>Items (#{items.size})</h2>
<ul>
#{items.map { |i| "<li>#{escape(name_of(i))}</li>" }.join("\n")}
</ul>
</section>
HTML
end

def measurement_section(dcc)
return "" unless Dcc::TypeGuards.has_attribute?(dcc, :measurement_results) && dcc.measurement_results

mrs = Array(dcc.measurement_results.measurement_result)
<<~HTML
<section>
<h2>Measurement Results (#{mrs.size})</h2>
<ul>
#{mrs.map { |mr| "<li>#{escape(name_of(mr))}</li>" }.join("\n")}
</ul>
</section>
HTML
end

def unique_identifier_of(dcc)
Dcc::TypeGuards.has_attribute?(dcc, :administrative_data) &&
Dcc::TypeGuards.has_attribute?(dcc.administrative_data, :core_data) &&
dcc.administrative_data.core_data &&
dcc.administrative_data.core_data.unique_identifier.to_s
end

def country_of(dcc)
Dcc::TypeGuards.has_attribute?(dcc, :administrative_data) &&
Dcc::TypeGuards.has_attribute?(dcc.administrative_data, :core_data) &&
dcc.administrative_data.core_data &&
dcc.administrative_data.core_data.country_code_iso_3166_1.to_s
end

def name_of(node)
return "" unless node && Dcc::TypeGuards.has_attribute?(node, :name)
return "" unless node.name

content = Dcc::TypeGuards.has_attribute?(node.name, :content) ? node.name.content : []
first = Array(content).first
return "" unless first

vals = Dcc::TypeGuards.has_attribute?(first, :value) ? first.value : []
Array(vals).first.to_s
end

def escape(s)
return "" if s.nil?

s.to_s
.gsub("&", "&amp;")
.gsub("<", "&lt;")
.gsub(">", "&gt;")
.gsub('"', "&quot;")
def render_template(hash, lang:)
template = ::Liquid::Template.parse(File.read(TEMPLATE_PATH))
template.render("dcc" => hash, "page_lang" => lang)
end
end
end
end
end
end
Loading
Loading