diff --git a/app/services/reports/docx.rb b/app/services/reports/docx.rb
index 0d337f9cc..b55d1f062 100644
--- a/app/services/reports/docx.rb
+++ b/app/services/reports/docx.rb
@@ -37,6 +37,49 @@ class Reports::Docx
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
# rubocop:enable Style/ClassAndModuleChildren
diff --git a/app/services/reports/docx/private_methods.rb b/app/services/reports/docx/private_methods.rb
index 292c6f00f..053ec5060 100644
--- a/app/services/reports/docx/private_methods.rb
+++ b/app/services/reports/docx/private_methods.rb
@@ -5,49 +5,18 @@ module Reports::Docx::PrivateMethods
# RTE fields support
def html_to_word_converter(text)
- link_style = @link_style
- scinote_url = @scinote_url
html = Nokogiri::HTML(text)
raw_elements = recursive_children(html.css('body').children, [])
- elements = []
- temp_p = []
-
# Combined raw text blocks in paragraphs
- raw_elements.each do |elem|
- if elem[:type] == 'image' || elem[:type] == 'newline'
- 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 = combine_docx_elements(raw_elements)
+
# Draw elements
elements.each do |elem|
if elem[:type] == 'p'
- @docx.p do
- elem[: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'
- 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
+ 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] || {}
@docx.p elem[:value] do
@@ -58,19 +27,31 @@ module Reports::Docx::PrivateMethods
style style[:style] if style[:style]
end
elsif elem[:type] == 'image'
- style = elem[:style]
- @docx.img elem[:data] do
- data elem[:blob].download
- width style[:width]
- height style[:height]
- align style[:align] || :left
- end
+ 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)
+ def recursive_children(children, elements, options = {})
children.each do |elem|
if elem.class == Nokogiri::XML::Text
next if elem.text.strip == ' ' # Invisible symbol
@@ -100,6 +81,9 @@ module Reports::Docx::PrivateMethods
image_path = image_path(image.image)
dimension = FastImage.size(image_path)
+
+ next unless dimension
+
style = image_styling(elem, dimension)
elements.push(
@@ -112,7 +96,16 @@ module Reports::Docx::PrivateMethods
end
if elem.name == 'a'
- elements.push(link_prepare(elem))
+ 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
@@ -121,7 +114,7 @@ module Reports::Docx::PrivateMethods
elements
end
- def link_prepare(elem)
+ def link_element(elem)
text = elem.text
link = elem.attributes['href'].value if elem.attributes['href']
if elem.attributes['class']&.value == 'record-info-link'
@@ -197,7 +190,8 @@ module Reports::Docx::PrivateMethods
{
width: x,
height: y,
- align: align
+ align: align,
+ max_width: max_width
}
end
@@ -292,6 +286,46 @@ module Reports::Docx::PrivateMethods
}
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]
+ 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
+ end
+ end
+
def image_path(attachment)
attachment.service_url
end
diff --git a/app/views/reports/elements/_my_module_element.html.erb b/app/views/reports/elements/_my_module_element.html.erb
index 88144b42d..b2a184416 100644
--- a/app/views/reports/elements/_my_module_element.html.erb
+++ b/app/views/reports/elements/_my_module_element.html.erb
@@ -48,6 +48,7 @@
<% if my_module.description.present? %>
<%= custom_auto_link(my_module.prepare_for_report(:description, for_export_all),
team: current_team,
+ simple_format: false,
base64_encoded_imgs: for_export_all) %>
<% else %>
<%=t "projects.reports.elements.module.no_description" %>
diff --git a/app/views/reports/elements/_my_module_protocol_element.html.erb b/app/views/reports/elements/_my_module_protocol_element.html.erb
index 7174c63e9..eb9ef3154 100644
--- a/app/views/reports/elements/_my_module_protocol_element.html.erb
+++ b/app/views/reports/elements/_my_module_protocol_element.html.erb
@@ -16,6 +16,7 @@
<% if protocol.description.present? %>
<%= custom_auto_link(protocol.prepare_for_report(:description, for_export_all),
team: current_team,
+ simple_format: false,
base64_encoded_imgs: for_export_all) %>
<% else %>
<%= t('my_modules.protocols.protocol_status_bar.no_description') %>