mirror of
				https://github.com/scinote-eln/scinote-web.git
				synced 2025-11-04 21:09:13 +08:00 
			
		
		
		
	Merge pull request #2899 from biosistemika/release/1.20.2
Release/1.20.2
This commit is contained in:
		
						commit
						c8083f730c
					
				
					 17 changed files with 520 additions and 330 deletions
				
			
		| 
						 | 
				
			
			@ -34,53 +34,5 @@ class Reports::Docx
 | 
			
		|||
    end
 | 
			
		||||
    @docx
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
# rubocop:enable  Style/ClassAndModuleChildren
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,8 @@ module Reports::Docx::DrawExperiment
 | 
			
		|||
            link_style
 | 
			
		||||
    end
 | 
			
		||||
    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
 | 
			
		||||
    subject['children'].each do |child|
 | 
			
		||||
      public_send("draw_#{child['type_of']}", child, experiment)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,7 +66,8 @@ module Reports::Docx::DrawMyModule
 | 
			
		|||
 | 
			
		||||
    if my_module.description.present?
 | 
			
		||||
      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
 | 
			
		||||
      @docx.p I18n.t('projects.reports.elements.module.no_description')
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ module Reports::Docx::DrawMyModuleActivity
 | 
			
		|||
                        sanitize_input(generate_activity_content(activity, true))
 | 
			
		||||
                      end
 | 
			
		||||
      @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
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ module Reports::Docx::DrawMyModuleProtocol
 | 
			
		|||
                   timestamp: I18n.l(protocol.created_at, format: :full)
 | 
			
		||||
    @docx.hr
 | 
			
		||||
    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
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ module Reports::Docx::DrawResultAsset
 | 
			
		|||
                        user: result.user.full_name, timestamp: I18n.l(timestamp, format: :full)), color: color[:gray]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    asset_image_preparing(asset) if asset.image?
 | 
			
		||||
    Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.image?
 | 
			
		||||
 | 
			
		||||
    subject['children'].each do |child|
 | 
			
		||||
      public_send("draw_#{child['type_of']}", child, result)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,8 @@ module Reports::Docx::DrawResultComments
 | 
			
		|||
                     date: I18n.l(comment_ts, format: :full_date),
 | 
			
		||||
                     time: I18n.l(comment_ts, format: :time)), italic: true
 | 
			
		||||
      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
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,8 @@ module Reports::Docx::DrawResultText
 | 
			
		|||
                  timestamp: I18n.l(timestamp, format: :full), user: result.user.full_name), color: color[:gray]
 | 
			
		||||
    end
 | 
			
		||||
    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|
 | 
			
		||||
      public_send("draw_#{child['type_of']}", child, result)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,8 @@ module Reports::Docx::DrawStep
 | 
			
		|||
    end
 | 
			
		||||
    if step.description.present?
 | 
			
		||||
      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
 | 
			
		||||
      @docx.p I18n.t 'projects.reports.elements.step.no_description'
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,6 @@ module Reports::Docx::DrawStepAsset
 | 
			
		|||
                  timestamp: I18n.l(timestamp, format: :full)), color: color[:gray]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    asset_image_preparing(asset) if asset.image?
 | 
			
		||||
    Reports::DocxRenderer.render_asset_image(@docx, asset) if asset.image?
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,8 @@ module Reports::Docx::DrawStepComments
 | 
			
		|||
                     date: I18n.l(comment_ts, format: :full_date),
 | 
			
		||||
                     time: I18n.l(comment_ts, format: :time)), italic: true
 | 
			
		||||
      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
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,222 +3,6 @@
 | 
			
		|||
module Reports::Docx::PrivateMethods
 | 
			
		||||
  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
 | 
			
		||||
    @docx.page_size do
 | 
			
		||||
      width   Constants::REPORT_DOCX_WIDTH
 | 
			
		||||
| 
						 | 
				
			
			@ -269,60 +53,4 @@ module Reports::Docx::PrivateMethods
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										158
									
								
								app/services/reports/docx_renderer.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								app/services/reports/docx_renderer.rb
									
										
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										254
									
								
								app/services/reports/html_to_word_converter.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								app/services/reports/html_to_word_converter.rb
									
										
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										25
									
								
								app/services/reports/utils.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/services/reports/utils.rb
									
										
									
									
									
										Normal 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
 | 
			
		||||
| 
						 | 
				
			
			@ -453,6 +453,10 @@ en:
 | 
			
		|||
          nothing_selected: "Nothing selected"
 | 
			
		||||
        generate_PDF:
 | 
			
		||||
          generated_on: "Report generated by SciNote on: %{timestamp}"
 | 
			
		||||
      renderers:
 | 
			
		||||
        lists:
 | 
			
		||||
          appended_image: "Appended image - %{name}"
 | 
			
		||||
          appended_table: "Appended table"
 | 
			
		||||
      elements:
 | 
			
		||||
        modals:
 | 
			
		||||
          project_contents:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										63
									
								
								spec/services/reports/html_to_word_converter_spec.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								spec/services/reports/html_to_word_converter_spec.rb
									
										
									
									
									
										Normal 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
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue