scinote-web/app/services/reports/docx/private_methods.rb
2020-10-09 09:43:00 +02:00

379 lines
10 KiB
Ruby
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# frozen_string_literal: true
module Reports::Docx::PrivateMethods
# private #Commented out just for testing.
# 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)
elsif %w(ul ol).include?(elem[:type])
Reports::Docx.render_list_element(@docx, elem)
end
end
end
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
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
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
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
# rubocop:disable Metrics/BlockLength
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)
# this will be extracted to new method with code from line 85
next unless item.attributes['data-mce-token']
image = TinyMceAsset.find_by(id: Base62.decode(item.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(item, dimension)
{
type: 'image',
data: image_path.split('&')[0],
blob: image.blob,
style: style,
bookmark_id: SecureRandom.hex
}
elsif %w(table).include?(item.name)
item = tiny_mce_table(item, nested_table: true)
{ type: 'table', data: item, bookmark_id: SecureRandom.hex }
end
end.reject(&:blank?)
end
{ type: list_element.name, data: data_array }
end
# rubocop:enable Metrics/BlockLength
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
@docx.page_size do
width Constants::REPORT_DOCX_WIDTH
height Constants::REPORT_DOCX_HEIGHT
end
@docx.page_margins do
left Constants::REPORT_DOCX_MARGIN_LEFT
right Constants::REPORT_DOCX_MARGIN_RIGHT
top Constants::REPORT_DOCX_MARGIN_TOP
bottom Constants::REPORT_DOCX_MARGIN_BOTTOM
end
@docx.page_numbers true, align: :right
path = Rails.root.join('app', 'assets', 'images', 'logo.png')
@docx.img path.to_s do
height 20
width 100
align :left
end
@docx.p do
text I18n.t('projects.reports.new.generate_PDF.generated_on', timestamp: I18n.l(Time.zone.now, format: :full))
br
end
generate_html_styles
end
def generate_html_styles
@docx.style do
id 'Heading1'
name 'heading 1'
font 'Arial'
size 36
bottom 120
bold true
end
@link_style = {
color: '37a0d9',
bold: true
}
@color = {
gray: 'a0a0a0',
green: '2dbe61'
}
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