Merge pull request #2899 from biosistemika/release/1.20.2

Release/1.20.2
This commit is contained in:
Miha Mencin 2020-10-16 09:01:45 +02:00 committed by GitHub
commit c8083f730c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 520 additions and 330 deletions

View file

@ -34,53 +34,5 @@ class Reports::Docx
end end
@docx @docx
end end
def self.link_prepare(scinote_url, link)
link[0] == '/' ? scinote_url + link : link
end
def self.render_p_element(docx, element, options = {})
scinote_url = options[:scinote_url]
link_style = options[:link_style]
docx.p do
element[:children].each do |text_el|
if text_el[:type] == 'text'
style = text_el[:style] || {}
text text_el[:value], style
text ' ' if text_el[:value] != ''
elsif text_el[:type] == 'br' && !options[:skip_br]
br
elsif text_el[:type] == 'a'
if text_el[:link]
link_url = Reports::Docx.link_prepare(scinote_url, text_el[:link])
link text_el[:value], link_url, link_style
else
text text_el[:value], link_style
end
text ' ' if text_el[:value] != ''
end
end
end
end
def self.render_img_element(docx, element, options = {})
style = element[:style]
if options[:table]
max_width = (style[:max_width] / options[:table][:columns].to_f)
if style[:width] > max_width
style[:height] = (max_width / style[:width].to_f) * style[:height]
style[:width] = max_width
end
end
docx.img element[:data] do
data element[:blob].download
width style[:width]
height style[:height]
align style[:align] || :left
end
end
end end
# rubocop:enable Style/ClassAndModuleChildren # rubocop:enable Style/ClassAndModuleChildren

View file

@ -22,7 +22,8 @@ module Reports::Docx::DrawExperiment
link_style link_style
end end
html = custom_auto_link(experiment.description, team: @report_team) html = custom_auto_link(experiment.description, team: @report_team)
html_to_word_converter(html) Reports::HtmlToWordConverter.new(@docx, { scinote_url: scinote_url,
link_style: link_style }).html_to_word_converter(html)
@docx.p @docx.p
subject['children'].each do |child| subject['children'].each do |child|
public_send("draw_#{child['type_of']}", child, experiment) public_send("draw_#{child['type_of']}", child, experiment)

View file

@ -66,7 +66,8 @@ module Reports::Docx::DrawMyModule
if my_module.description.present? if my_module.description.present?
html = custom_auto_link(my_module.description, team: @report_team) html = custom_auto_link(my_module.description, team: @report_team)
html_to_word_converter(html) Reports::HtmlToWordConverter.new(@docx, { scinote_url: scinote_url,
link_style: link_style }).html_to_word_converter(html)
else else
@docx.p I18n.t('projects.reports.elements.module.no_description') @docx.p I18n.t('projects.reports.elements.module.no_description')
end end

View file

@ -20,7 +20,7 @@ module Reports::Docx::DrawMyModuleActivity
sanitize_input(generate_activity_content(activity, true)) sanitize_input(generate_activity_content(activity, true))
end end
@docx.p I18n.l(activity_ts, format: :full), color: color[:gray] @docx.p I18n.l(activity_ts, format: :full), color: color[:gray]
html_to_word_converter(activity_text) Reports::HtmlToWordConverter.new(@docx).html_to_word_converter(activity_text)
@docx.p @docx.p
end end
end end

View file

@ -11,7 +11,7 @@ module Reports::Docx::DrawMyModuleProtocol
timestamp: I18n.l(protocol.created_at, format: :full) timestamp: I18n.l(protocol.created_at, format: :full)
@docx.hr @docx.hr
html = custom_auto_link(protocol.description, team: @report_team) html = custom_auto_link(protocol.description, team: @report_team)
html_to_word_converter(html) Reports::HtmlToWordConverter.new(@docx).html_to_word_converter(html)
@docx.p @docx.p
@docx.p @docx.p
end end

View file

@ -17,7 +17,7 @@ module Reports::Docx::DrawResultAsset
user: result.user.full_name, timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] user: result.user.full_name, timestamp: I18n.l(timestamp, format: :full)), color: color[:gray]
end end
asset_image_preparing(asset) if asset.image? Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.image?
subject['children'].each do |child| subject['children'].each do |child|
public_send("draw_#{child['type_of']}", child, result) public_send("draw_#{child['type_of']}", child, result)

View file

@ -17,7 +17,8 @@ module Reports::Docx::DrawResultComments
date: I18n.l(comment_ts, format: :full_date), date: I18n.l(comment_ts, format: :full_date),
time: I18n.l(comment_ts, format: :time)), italic: true time: I18n.l(comment_ts, format: :time)), italic: true
html = custom_auto_link(comment.message, team: @report_team) html = custom_auto_link(comment.message, team: @report_team)
html_to_word_converter(html) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url,
link_style: @link_style }).html_to_word_converter(html)
@docx.p @docx.p
end end
end end

View file

@ -17,7 +17,8 @@ module Reports::Docx::DrawResultText
timestamp: I18n.l(timestamp, format: :full), user: result.user.full_name), color: color[:gray] timestamp: I18n.l(timestamp, format: :full), user: result.user.full_name), color: color[:gray]
end end
html = custom_auto_link(result_text.text, team: @report_team) html = custom_auto_link(result_text.text, team: @report_team)
html_to_word_converter(html) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url,
link_style: @link_style }).html_to_word_converter(html)
subject['children'].each do |child| subject['children'].each do |child|
public_send("draw_#{child['type_of']}", child, result) public_send("draw_#{child['type_of']}", child, result)

View file

@ -27,7 +27,8 @@ module Reports::Docx::DrawStep
end end
if step.description.present? if step.description.present?
html = custom_auto_link(step.description, team: @report_team) html = custom_auto_link(step.description, team: @report_team)
html_to_word_converter(html) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url,
link_style: @link_style }).html_to_word_converter(html)
else else
@docx.p I18n.t 'projects.reports.elements.step.no_description' @docx.p I18n.t 'projects.reports.elements.step.no_description'
end end

View file

@ -15,6 +15,6 @@ module Reports::Docx::DrawStepAsset
timestamp: I18n.l(timestamp, format: :full)), color: color[:gray] timestamp: I18n.l(timestamp, format: :full)), color: color[:gray]
end end
asset_image_preparing(asset) if asset.image? Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.image?
end end
end end

View file

@ -17,7 +17,8 @@ module Reports::Docx::DrawStepComments
date: I18n.l(comment_ts, format: :full_date), date: I18n.l(comment_ts, format: :full_date),
time: I18n.l(comment_ts, format: :time)), italic: true time: I18n.l(comment_ts, format: :time)), italic: true
html = custom_auto_link(comment.message, team: @report_team) html = custom_auto_link(comment.message, team: @report_team)
html_to_word_converter(html) Reports::HtmlToWordConverter.new(@docx, { scinote_url: @scinote_url,
link_style: @link_style }).html_to_word_converter(html)
@docx.p @docx.p
end end
end end

View file

@ -3,222 +3,6 @@
module Reports::Docx::PrivateMethods module Reports::Docx::PrivateMethods
private private
# RTE fields support
def html_to_word_converter(text)
html = Nokogiri::HTML(text)
raw_elements = recursive_children(html.css('body').children, [])
# Combined raw text blocks in paragraphs
elements = combine_docx_elements(raw_elements)
# Draw elements
elements.each do |elem|
if elem[:type] == 'p'
Reports::Docx.render_p_element(@docx, elem, scinote_url: @scinote_url, link_style: @link_style)
elsif elem[:type] == 'table'
tiny_mce_table(elem[:data])
elsif elem[:type] == 'newline'
style = elem[:style] || {}
# print heading if its heading
# Mixing heading with other style setting causes problems for Word
if %w(h1 h2 h3 h4 h5).include?(style[:style])
@docx.public_send(style[:style], elem[:value])
else
@docx.p elem[:value] do
align style[:align]
color style[:color]
bold style[:bold]
italic style[:italic]
end
end
elsif elem[:type] == 'image'
Reports::Docx.render_img_element(@docx, elem)
end
end
end
def combine_docx_elements(raw_elements)
elements = []
temp_p = []
raw_elements.each do |elem|
if %w(image newline table).include? elem[:type]
unless temp_p.empty?
elements.push(type: 'p', children: temp_p)
temp_p = []
end
elements.push(elem)
elsif %w(br text a).include? elem[:type]
temp_p.push(elem)
end
end
elements.push(type: 'p', children: temp_p)
elements
end
# Convert HTML structure to plain text structure
def recursive_children(children, elements, options = {})
children.each do |elem|
if elem.class == Nokogiri::XML::Text
next if elem.text.strip == ' ' # Invisible symbol
style = paragraph_styling(elem.parent)
type = (style[:align] && style[:align] != :justify) || style[:style] ? 'newline' : 'text'
text = smart_annotation_check(elem)
elements.push(
type: type,
value: text.strip.delete(' '), # Invisible symbol
style: style
)
next
end
if elem.name == 'br'
elements.push(type: 'br')
next
end
if elem.name == 'img' && elem.attributes['data-mce-token']
image = TinyMceAsset.find_by(id: Base62.decode(elem.attributes['data-mce-token'].value))
next unless image
image_path = image_path(image.image)
dimension = FastImage.size(image_path)
next unless dimension
style = image_styling(elem, dimension)
elements.push(
type: 'image',
data: image_path.split('&')[0],
blob: image.blob,
style: style
)
next
end
if elem.name == 'a'
elements.push(link_element(elem))
next
end
if elem.name == 'table'
elem = tiny_mce_table(elem, nested_table: true) if options[:nested_tables]
elements.push(
type: 'table',
data: elem
)
next
end
elements = recursive_children(elem.children, elements) if elem.children
end
elements
end
def link_element(elem)
text = elem.text
link = elem.attributes['href'].value if elem.attributes['href']
if elem.attributes['class']&.value == 'record-info-link'
link = nil
text = "##{text}"
end
text = "##{text}" if elem.parent.attributes['class']&.value == 'atwho-inserted'
text = "@#{text}" if elem.attributes['class']&.value == 'atwho-user-popover'
{
type: 'a',
value: text,
link: link
}
end
def smart_annotation_check(elem)
return "[#{elem.text}]" if elem.parent.attributes['class']&.value == 'sa-type'
elem.text
end
# Prepare style for text
def paragraph_styling(elem)
style = elem.attributes['style']
result = {}
result[:style] = elem.name if elem.name.include? 'h'
result[:bold] = true if elem.name == 'strong'
result[:italic] = true if elem.name == 'em'
style_keys = %w(text-align color)
if style
style_keys.each do |key|
style_el = style.value.split(';').select { |i| (i.include? key) }[0]
next unless style_el
value = style_el.split(':')[1].strip if style_el
if key == 'text-align'
result[:align] = value.to_sym
elsif key == 'color' && calculate_color_hsp(value) < 190
result[:color] = value.delete('#')
end
end
end
result
end
# Prepare style for images
def image_styling(elem, dimension)
dimension[0] = elem.attributes['width'].value.to_i if elem.attributes['width']
dimension[1] = elem.attributes['height'].value.to_i if elem.attributes['height']
if elem.attributes['style']
align = if elem.attributes['style'].value.include? 'margin-right'
:center
elsif elem.attributes['style'].value.include? 'float: right'
:right
else
:left
end
end
margins = Constants::REPORT_DOCX_MARGIN_LEFT + Constants::REPORT_DOCX_MARGIN_RIGHT
max_width = (Constants::REPORT_DOCX_WIDTH - margins) / 20
if dimension[0] > max_width
x = max_width
y = dimension[1] * max_width / dimension[0]
else
x = dimension[0]
y = dimension[1]
end
{
width: x,
height: y,
align: align,
max_width: max_width
}
end
def asset_image_preparing(asset)
return unless asset
image_path = image_path(asset.file)
dimension = FastImage.size(image_path)
x = dimension[0]
y = dimension[1]
if x > 300
y = y * 300 / x
x = 300
end
@docx.img image_path.split('&')[0] do
data asset.blob.download
width x
height y
end
end
def initial_document_load def initial_document_load
@docx.page_size do @docx.page_size do
width Constants::REPORT_DOCX_WIDTH width Constants::REPORT_DOCX_WIDTH
@ -269,60 +53,4 @@ module Reports::Docx::PrivateMethods
green: '2dbe61' green: '2dbe61'
} }
end end
def tiny_mce_table(table_data, options = {})
docx_table = []
scinote_url = @scinote_url
link_style = @link_style
table_data.css('tbody').first.children.each do |row|
docx_row = []
next unless row.name == 'tr'
row.children.each do |cell|
next unless cell.name == 'td'
# Parse cell content
formated_cell = recursive_children(cell.children, [], nested_tables: true)
# Combine text elements to single paragraph
formated_cell = combine_docx_elements(formated_cell)
docx_cell = Caracal::Core::Models::TableCellModel.new do |c|
formated_cell.each do |cell_content|
if cell_content[:type] == 'p'
Reports::Docx.render_p_element(c, cell_content,
scinote_url: scinote_url, link_style: link_style, skip_br: true)
elsif cell_content[:type] == 'table'
c.table formated_cell_content[:data], border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE
elsif cell_content[:type] == 'image'
Reports::Docx.render_img_element(c, cell_content, table: { columns: row.children.length / 3 })
end
end
end
docx_row.push(docx_cell)
end
docx_table.push(docx_row)
end
if options[:nested_table]
docx_table
else
@docx.table docx_table, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE
end
end
def image_path(attachment)
attachment.service_url
end
def calculate_color_hsp(color)
return 255 if color.length != 7
color = color.delete('#').scan(/.{1,2}/)
rgb = color.map(&:hex)
Math.sqrt(
0.299 * (rgb[0]**2) +
0.587 * (rgb[1]**2) +
0.114 * (rgb[2]**2)
)
end
end end

View file

@ -0,0 +1,158 @@
# frozen_string_literal: true
module Reports
class DocxRenderer
def self.render_p_element(docx, element, options = {})
docx.p do
element[:children].each do |text_el|
if text_el[:type] == 'text'
style = text_el[:style] || {}
text text_el[:value], style
text ' ' if text_el[:value] != ''
elsif text_el[:type] == 'br' && !options[:skip_br]
br
elsif text_el[:type] == 'a'
Reports::DocxRenderer.render_link_element(self, text_el, options)
end
end
end
end
def self.render_link_element(node, link_item, options = {})
scinote_url = options[:scinote_url]
link_style = options[:link_style]
if link_item[:link]
link_url = Reports::Utils.link_prepare(scinote_url, link_item[:link])
node.link link_item[:value], link_url, link_style
else
node.text link_item[:value], link_style
end
node.text ' ' if link_item[:value] != ''
end
def self.render_img_element(docx, element, options = {})
style = element[:style]
if options[:table]
max_width = (style[:max_width] / options[:table][:columns].to_f)
if style[:width] > max_width
style[:height] = (max_width / style[:width].to_f) * style[:height]
style[:width] = max_width
end
end
docx.img element[:data] do
data element[:blob].download
width style[:width]
height style[:height]
align style[:align] || :left
end
end
def self.render_list_element(docx, element, options = {})
bookmark_items = Reports::DocxRenderer.recursive_list_items_renderer(docx, element)
bookmark_items.each_with_index do |(key, item), index|
if item[:type] == 'image'
docx.bookmark_start id: index, name: key
docx.p do
br
text item[:blob]&.filename.to_s
end
Reports::DocxRenderer.render_img_element(docx, item)
docx.bookmark_end id: index
elsif item[:type] == 'table'
docx.bookmark_start id: index, name: key
# Bookmark won't work with table only, empty p element added
docx.p do
br
text ''
end
Reports::DocxRenderer.render_table_element(docx, item, options)
docx.bookmark_end id: index
end
end
end
# rubocop:disable Metrics/BlockLength
def self.recursive_list_items_renderer(node, element, bookmark_items: {})
node.public_send(element[:type]) do
element[:data].each do |values_array|
li do
values_array.each do |item|
case item
when Hash
if %w(ul ol li).include?(item[:type])
Reports::DocxRenderer.recursive_list_items_renderer(self, item, bookmark_items: bookmark_items)
elsif %w(a).include?(item[:type])
Reports::DocxRenderer.render_link_element(self, item)
elsif %w(image).include?(item[:type])
bookmark_items[item[:bookmark_id]] = item
link I18n.t('projects.reports.renderers.lists.appended_image',
name: item[:blob]&.filename), item[:bookmark_id] do
internal true
end
elsif %w(table).include?(item[:type])
bookmark_items[item[:bookmark_id]] = item
link I18n.t('projects.reports.renderers.lists.appended_table'), item[:bookmark_id] do
internal true
end
end
else
text item
end
end
end
end
end
bookmark_items
end
# rubocop:enable Metrics/BlockLength
def self.render_table_element(docx, element, options = {})
docx_table = []
element[:data].each do |row|
docx_row = []
row[:data].each do |cell|
docx_cell = Caracal::Core::Models::TableCellModel.new do |c|
cell.each do |content|
if content[:type] == 'p'
Reports::DocxRenderer.render_p_element(c, content, options.merge({ skip_br: true }))
elsif content[:type] == 'table'
Reports::DocxRenderer.render_table_element(c, content, options)
elsif content[:type] == 'image'
Reports::DocxRenderer.render_img_element(c, content, table: { columns: row.children.length / 3 })
end
end
end
docx_row.push(docx_cell)
end
docx_table.push(docx_row)
end
docx.table docx_table, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE
end
def self.render_asset_image(docx, asset)
return unless asset
image_path = Reports::Utils.image_path(asset.file)
dimension = FastImage.size(image_path)
return unless dimension
x = dimension[0]
y = dimension[1]
if x > 300
y = y * 300 / x
x = 300
end
docx.img image_path.split('&')[0] do
data asset.blob.download
width x
height y
end
end
end
end

View file

@ -0,0 +1,254 @@
# frozen_string_literal: true
module Reports
class HtmlToWordConverter
def initialize(document, options = {})
@docx = document
@scinote_url = options[:scinote_url]
@link_style = options[:link_style]
end
def html_to_word_converter(text)
html = Nokogiri::HTML(text)
raw_elements = recursive_children(html.css('body').children, []).compact
# Combined raw text blocks in paragraphs
elements = combine_docx_elements(raw_elements)
# Draw elements
elements.each do |elem|
if elem[:type] == 'p'
Reports::DocxRenderer.render_p_element(@docx, elem, scinote_url: @scinote_url, link_style: @link_style)
elsif elem[:type] == 'table'
Reports::DocxRenderer.render_table_element(@docx, elem)
elsif elem[:type] == 'newline'
style = elem[:style] || {}
# print heading if its heading
# Mixing heading with other style setting causes problems for Word
if %w(h1 h2 h3 h4 h5).include?(style[:style])
@docx.public_send(style[:style], elem[:value])
else
@docx.p elem[:value] do
align style[:align]
color style[:color]
bold style[:bold]
italic style[:italic]
end
end
elsif elem[:type] == 'image'
Reports::DocxRenderer.render_img_element(@docx, elem)
elsif %w(ul ol).include?(elem[:type])
Reports::DocxRenderer.render_list_element(@docx, elem)
end
end
end
private
def combine_docx_elements(raw_elements)
# Word does not support some nested elements, move some elements to root level
elements = []
temp_p = []
raw_elements.each do |elem|
if %w(image newline table ol ul).include? elem[:type]
unless temp_p.empty?
elements.push(type: 'p', children: temp_p)
temp_p = []
end
elements.push(elem)
elsif %w(br text a).include? elem[:type]
temp_p.push(elem)
end
end
elements.push(type: 'p', children: temp_p)
elements
end
# Convert HTML structure to plain text structure
# rubocop:disable Metrics/BlockLength
def recursive_children(children, elements)
children.each do |elem|
if elem.class == Nokogiri::XML::Text
next if elem.text.strip == ' ' # Invisible symbol
style = paragraph_styling(elem.parent)
type = (style[:align] && style[:align] != :justify) || style[:style] ? 'newline' : 'text'
text = smart_annotation_check(elem)
elements.push(
type: type,
value: text.strip.delete(' '), # Invisible symbol
style: style
)
next
end
if elem.name == 'br'
elements.push(type: 'br')
next
end
if elem.name == 'img'
elements.push(img_element(elem))
next
end
if elem.name == 'a'
elements.push(link_element(elem))
next
end
if elem.name == 'table'
elements.push(tiny_mce_table_element(elem))
next
end
if %w(ul ol).include?(elem.name)
elements.push(list_element(elem))
next
end
elements = recursive_children(elem.children, elements) if elem.children
end
elements
end
# rubocop:enable Metrics/BlockLength
def img_element(elem)
return unless elem.attributes['data-mce-token']
image = TinyMceAsset.find_by(id: Base62.decode(elem.attributes['data-mce-token'].value))
return unless image
image_path = Reports::Utils.image_path(image.image)
dimension = FastImage.size(image_path)
return unless dimension
style = image_styling(elem, dimension)
{ type: 'image', data: image_path.split('&')[0], blob: image.blob, style: style }
end
def link_element(elem)
text = elem.text
link = elem.attributes['href'].value if elem.attributes['href']
if elem.attributes['class']&.value == 'record-info-link'
link = nil
text = "##{text}"
end
text = "##{text}" if elem.parent.attributes['class']&.value == 'atwho-inserted'
text = "@#{text}" if elem.attributes['class']&.value == 'atwho-user-popover'
{
type: 'a',
value: text,
link: link
}
end
def list_element(list_element)
data_array = list_element.children.select { |n| %w(li ul ol a img).include?(n.name) }.map do |li_child|
li_child.children.map do |item|
if item.is_a? Nokogiri::XML::Text
item.text.chomp
elsif %w(ul ol).include?(item.name)
list_element(item)
elsif %w(a).include?(item.name)
link_element(item)
elsif %w(img).include?(item.name)
img_element(item)&.merge(bookmark_id: SecureRandom.hex)
elsif %w(table).include?(item.name)
tiny_mce_table_element(item).merge(bookmark_id: SecureRandom.hex)
end
end.reject(&:blank?)
end
{ type: list_element.name, data: data_array }
end
def smart_annotation_check(elem)
return "[#{elem.text}]" if elem.parent.attributes['class']&.value == 'sa-type'
elem.text
end
# Prepare style for text
def paragraph_styling(elem)
style = elem.attributes['style']
result = {}
result[:style] = elem.name if elem.name.include? 'h'
result[:bold] = true if elem.name == 'strong'
result[:italic] = true if elem.name == 'em'
style_keys = %w(text-align color)
if style
style_keys.each do |key|
style_el = style.value.split(';').select { |i| (i.include? key) }[0]
next unless style_el
value = style_el.split(':')[1].strip if style_el
if key == 'text-align'
result[:align] = value.to_sym
elsif key == 'color' && Reports::Utils.calculate_color_hsp(value) < 190
result[:color] = value.delete('#')
end
end
end
result
end
# Prepare style for images
def image_styling(elem, dimension)
dimension[0] = elem.attributes['width'].value.to_i if elem.attributes['width']
dimension[1] = elem.attributes['height'].value.to_i if elem.attributes['height']
if elem.attributes['style']
align = if elem.attributes['style'].value.include? 'margin-right'
:center
elsif elem.attributes['style'].value.include? 'float: right'
:right
else
:left
end
end
margins = Constants::REPORT_DOCX_MARGIN_LEFT + Constants::REPORT_DOCX_MARGIN_RIGHT
max_width = (Constants::REPORT_DOCX_WIDTH - margins) / 20
if dimension[0] > max_width
x = max_width
y = dimension[1] * max_width / dimension[0]
else
x = dimension[0]
y = dimension[1]
end
{
width: x,
height: y,
align: align,
max_width: max_width
}
end
def tiny_mce_table_element(table_element)
# array of elements
rows = table_element.css('tbody').first.children.map do |row|
next unless row.name == 'tr'
cells = row.children.map do |cell|
next unless cell.name == 'td'
# Parse cell content
formated_cell = recursive_children(cell.children, [])
# Combine text elements to single paragraph
formated_cell = combine_docx_elements(formated_cell)
formated_cell
end.reject(&:blank?)
{ type: 'tr', data: cells }
end.reject(&:blank?)
{ type: 'table', data: rows }
end
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Reports
class Utils
def self.link_prepare(scinote_url, link)
link[0] == '/' ? scinote_url + link : link
end
def self.image_path(attachment)
attachment.service_url
end
def self.calculate_color_hsp(color)
return 255 if color.length != 7
color = color.delete('#').scan(/.{1,2}/)
rgb = color.map(&:hex)
Math.sqrt(
0.299 * (rgb[0]**2) +
0.587 * (rgb[1]**2) +
0.114 * (rgb[2]**2)
)
end
end
end

View file

@ -453,6 +453,10 @@ en:
nothing_selected: "Nothing selected" nothing_selected: "Nothing selected"
generate_PDF: generate_PDF:
generated_on: "Report generated by SciNote on: %{timestamp}" generated_on: "Report generated by SciNote on: %{timestamp}"
renderers:
lists:
appended_image: "Appended image - %{name}"
appended_table: "Appended table"
elements: elements:
modals: modals:
project_contents: project_contents:

View file

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'rails_helper'
describe Reports::HtmlToWordConverter do
let(:user) { create :user }
let(:team) { create :team }
let(:docx) { double('docx') }
let(:report) { described_class.new(docx) }
describe 'html_list' do
let(:text) do
'<body><ul><li>1</li><li>2<ul><li>one</li><li>two<ol><li>uno</li><li>due</li>'\
'</ol></li></ul></li><li>3</li><li>4</li><li>5</li></ul></body>'
end
let(:xml_elements) { Nokogiri::HTML(text).css('body').children.first }
let(:result) do
{
type: 'ul',
data: [%w(1),
['2', { type: 'ul', data: [%w(one), ['two', { type: 'ol', data: [%w(uno), %w(due)] }]] }],
%w(3), %w(4), %w(5)]
}
end
it '' do
expect(report.__send__(:list_element, xml_elements)).to be == result
end
end
describe '.tiny_mce_table_element' do
let(:text) do
# rubocop:disable Layout/LineLength
'<body><table style="border-collapse: collapse; width: 100%; height: 28px;" border="1" data-mce-style="border-collapse: collapse; width: 100%; height: 28px;"><tbody><tr style="height: 10px;"><td style="width: 50%; height: 10px;">1</td><td style="width: 50%; height: 10px;">2</td></tr><tr style="height: 18px;"><td style="width: 50%; height: 18px;">3</td><td style="width: 50%; height: 18px;">4</td></tr></tbody></table></body>'
# rubocop:enable Layout/LineLength
end
let(:xml_elements) { Nokogiri::HTML(text).css('body').children.first }
let(:result) do
{
data: [
{
data: [
[{ children: [{ style: {}, type: 'text', value: '1' }], type: 'p' }],
[{ children: [{ style: {}, type: 'text', value: '2' }], type: 'p' }]
],
type: 'tr'
},
{
data: [
[{ children: [{ style: {}, type: 'text', value: '3' }], type: 'p' }],
[{ children: [{ style: {}, type: 'text', value: '4' }], type: 'p' }]
],
type: 'tr'
}
],
type: 'table'
}
end
it '' do
expect(report.__send__(:tiny_mce_table_element, xml_elements)).to be == result
end
end
end