From d023f96aa9c05f19bc135f483c8003bea9f9efb3 Mon Sep 17 00:00:00 2001
From: Zardshard <101750307+Zardshard@users.noreply.github.com>
Date: Thu, 17 Mar 2022 07:13:55 -0400
Subject: [PATCH] Added support for highlighted text
---
lib/docx/containers/text_run.rb | 15 +++-
spec/docx/document_spec.rb | 131 +++++++++++++++++++-------------
spec/fixtures/formatting.docx | Bin 12477 -> 11080 bytes
3 files changed, 92 insertions(+), 54 deletions(-)
diff --git a/lib/docx/containers/text_run.rb b/lib/docx/containers/text_run.rb
index 59be9d3..7fb4dc7 100755
--- a/lib/docx/containers/text_run.rb
+++ b/lib/docx/containers/text_run.rb
@@ -10,7 +10,8 @@ class TextRun
DEFAULT_FORMATTING = {
italic: false,
bold: false,
- underline: false
+ underline: false,
+ highlight: 'none'
}
def self.tag
@@ -60,7 +61,8 @@ def parse_formatting
{
italic: !@node.xpath('.//w:i').empty?,
bold: !@node.xpath('.//w:b').empty?,
- underline: !@node.xpath('.//w:u').empty?
+ underline: !@node.xpath('.//w:u').empty?,
+ highlight: @node.xpath('.//w:highlight').first&.[]('w:val') || 'none'
}
end
@@ -75,6 +77,7 @@ def to_html
html = html_tag(:strong, content: html) if bolded?
styles = {}
styles['text-decoration'] = 'underline' if underlined?
+ styles['background-color'] = highlight unless highlight == 'none'
# No need to be granular with font size down to the span level if it doesn't vary.
styles['font-size'] = "#{font_size}pt" if font_size != @font_size
html = html_tag(:span, content: html, styles: styles) unless styles.empty?
@@ -94,6 +97,14 @@ def underlined?
@formatting[:underline]
end
+ def highlighted?
+ @formatting[:highlight] != 'none'
+ end
+
+ def highlight
+ @formatting[:highlight]
+ end
+
def hyperlink?
@node.name == 'hyperlink'
end
diff --git a/spec/docx/document_spec.rb b/spec/docx/document_spec.rb
index 0828cd2..a242235 100755
--- a/spec/docx/document_spec.rb
+++ b/spec/docx/document_spec.rb
@@ -7,7 +7,7 @@
describe Docx::Document do
before(:all) do
@fixtures_path = 'spec/fixtures'
- @formatting_line_count = 13 # number of lines the formatting.docx file has
+ @formatting_line_count = 14 # number of lines the formatting.docx file has
end
describe '#open' do
@@ -189,7 +189,8 @@
@only_italic = @default_formatting.merge italic: true
@only_bold = @default_formatting.merge bold: true
@only_underline = @default_formatting.merge underline: true
- @all_formatted = @default_formatting.merge italic: true, bold: true, underline: true
+ @only_highlight = @default_formatting.merge highlight: 'yellow'
+ @all_formatted = @default_formatting.merge italic: true, bold: true, underline: true, highlight: 'yellow'
end
it 'should have the correct text' do
@@ -198,26 +199,28 @@
expect(@doc.paragraphs[1].text).to eq('Italic')
expect(@doc.paragraphs[2].text).to eq('Bold')
expect(@doc.paragraphs[3].text).to eq('Underline')
- expect(@doc.paragraphs[4].text).to eq('Normal')
- expect(@doc.paragraphs[5].text).to eq('This is a sentence with all formatting options in the middle of the sentence.')
- expect(@doc.paragraphs[6].text).to eq('This is a centered paragraph.')
- expect(@doc.paragraphs[7].text).to eq('This paragraph is aligned left.')
- expect(@doc.paragraphs[8].text).to eq('This paragraph is aligned right.')
- expect(@doc.paragraphs[9].text).to eq('This paragraph is 14 points.')
- expect(@doc.paragraphs[10].text).to eq('This paragraph has a word at 16 points.')
- expect(@doc.paragraphs[11].text).to eq('This sentence has different formatting in different places.')
- expect(@doc.paragraphs[12].text).to eq('This sentence has a hyperlink.')
+ expect(@doc.paragraphs[4].text).to eq('Highlight')
+ expect(@doc.paragraphs[5].text).to eq('Normal')
+ expect(@doc.paragraphs[6].text).to eq('This is a sentence with all formatting options in the middle of the sentence.')
+ expect(@doc.paragraphs[7].text).to eq('This is a centered paragraph.')
+ expect(@doc.paragraphs[8].text).to eq('This paragraph is aligned left.')
+ expect(@doc.paragraphs[9].text).to eq('This paragraph is aligned right.')
+ expect(@doc.paragraphs[10].text).to eq('This paragraph is 14 points.')
+ expect(@doc.paragraphs[11].text).to eq('This paragraph has a word at 16 points.')
+ expect(@doc.paragraphs[12].text).to eq('This sentence has different formatting in different places.')
+ expect(@doc.paragraphs[13].text).to eq('This sentence has a hyperlink.')
end
it 'should contain a paragraph with multiple text runs' do
end
it 'should detect normal formatting' do
- [0, 4].each do |i|
+ [0, 5].each do |i|
expect(@formatting[i][0]).to eq(@default_formatting)
expect(@doc.paragraphs[i].text_runs[0].italicized?).to eq(false)
expect(@doc.paragraphs[i].text_runs[0].bolded?).to eq(false)
expect(@doc.paragraphs[i].text_runs[0].underlined?).to eq(false)
+ expect(@doc.paragraphs[i].text_runs[0].highlighted?).to eq(false)
end
end
@@ -226,6 +229,7 @@
expect(@doc.paragraphs[1].text_runs[0].italicized?).to eq(true)
expect(@doc.paragraphs[1].text_runs[0].bolded?).to eq(false)
expect(@doc.paragraphs[1].text_runs[0].underlined?).to eq(false)
+ expect(@doc.paragraphs[1].text_runs[0].highlighted?).to eq(false)
end
it 'should detect bold formatting' do
@@ -233,6 +237,7 @@
expect(@doc.paragraphs[2].text_runs[0].italicized?).to eq(false)
expect(@doc.paragraphs[2].text_runs[0].bolded?).to eq(true)
expect(@doc.paragraphs[2].text_runs[0].underlined?).to eq(false)
+ expect(@doc.paragraphs[2].text_runs[0].highlighted?).to eq(false)
end
it 'should detect underline formatting' do
@@ -240,41 +245,54 @@
expect(@doc.paragraphs[3].text_runs[0].italicized?).to eq(false)
expect(@doc.paragraphs[3].text_runs[0].bolded?).to eq(false)
expect(@doc.paragraphs[3].text_runs[0].underlined?).to eq(true)
+ expect(@doc.paragraphs[3].text_runs[0].highlighted?).to eq(false)
+ end
+
+ it 'should detect highlight formatting' do
+ expect(@formatting[4][0]).to eq(@only_highlight)
+ expect(@doc.paragraphs[4].text_runs[0].italicized?).to eq(false)
+ expect(@doc.paragraphs[4].text_runs[0].bolded?).to eq(false)
+ expect(@doc.paragraphs[4].text_runs[0].underlined?).to eq(false)
+ expect(@doc.paragraphs[4].text_runs[0].highlighted?).to eq(true)
+ expect(@doc.paragraphs[4].text_runs[0].highlight).to eq('yellow')
end
it 'should detect mixed formatting' do
- expect(@formatting[5][0]).to eq(@default_formatting)
- expect(@doc.paragraphs[5].text_runs[0].italicized?).to eq(false)
- expect(@doc.paragraphs[5].text_runs[0].bolded?).to eq(false)
- expect(@doc.paragraphs[5].text_runs[0].underlined?).to eq(false)
+ expect(@formatting[6][0]).to eq(@default_formatting)
+ expect(@doc.paragraphs[6].text_runs[0].italicized?).to eq(false)
+ expect(@doc.paragraphs[6].text_runs[0].bolded?).to eq(false)
+ expect(@doc.paragraphs[6].text_runs[0].underlined?).to eq(false)
+ expect(@doc.paragraphs[6].text_runs[0].highlighted?).to eq(false)
- expect(@formatting[5][1]).to eq(@all_formatted)
- expect(@doc.paragraphs[5].text_runs[1].italicized?).to eq(true)
- expect(@doc.paragraphs[5].text_runs[1].bolded?).to eq(true)
- expect(@doc.paragraphs[5].text_runs[1].underlined?).to eq(true)
+ expect(@formatting[6][1]).to eq(@all_formatted)
+ expect(@doc.paragraphs[6].text_runs[1].italicized?).to eq(true)
+ expect(@doc.paragraphs[6].text_runs[1].bolded?).to eq(true)
+ expect(@doc.paragraphs[6].text_runs[1].underlined?).to eq(true)
+ expect(@doc.paragraphs[6].text_runs[1].highlight).to eq('yellow')
- expect(@formatting[5][2]).to eq(@default_formatting)
- expect(@doc.paragraphs[5].text_runs[2].italicized?).to eq(false)
- expect(@doc.paragraphs[5].text_runs[2].bolded?).to eq(false)
- expect(@doc.paragraphs[5].text_runs[2].underlined?).to eq(false)
+ expect(@formatting[6][2]).to eq(@default_formatting)
+ expect(@doc.paragraphs[6].text_runs[2].italicized?).to eq(false)
+ expect(@doc.paragraphs[6].text_runs[2].bolded?).to eq(false)
+ expect(@doc.paragraphs[6].text_runs[2].underlined?).to eq(false)
+ expect(@doc.paragraphs[6].text_runs[2].highlighted?).to eq(false)
end
it 'should detect centered paragraphs' do
- expect(@doc.paragraphs[5].aligned_center?).to eq(false)
- expect(@doc.paragraphs[6].aligned_center?).to eq(true)
- expect(@doc.paragraphs[7].aligned_center?).to eq(false)
+ expect(@doc.paragraphs[6].aligned_center?).to eq(false)
+ expect(@doc.paragraphs[7].aligned_center?).to eq(true)
+ expect(@doc.paragraphs[8].aligned_center?).to eq(false)
end
it 'should detect left justified paragraphs' do
- expect(@doc.paragraphs[6].aligned_left?).to eq(false)
- expect(@doc.paragraphs[7].aligned_left?).to eq(true)
- expect(@doc.paragraphs[8].aligned_left?).to eq(false)
+ expect(@doc.paragraphs[7].aligned_left?).to eq(false)
+ expect(@doc.paragraphs[8].aligned_left?).to eq(true)
+ expect(@doc.paragraphs[9].aligned_left?).to eq(false)
end
it 'should detect right justified paragraphs' do
- expect(@doc.paragraphs[7].aligned_right?).to eq(false)
- expect(@doc.paragraphs[8].aligned_right?).to eq(true)
- expect(@doc.paragraphs[9].aligned_right?).to eq(false)
+ expect(@doc.paragraphs[8].aligned_right?).to eq(false)
+ expect(@doc.paragraphs[9].aligned_right?).to eq(true)
+ expect(@doc.paragraphs[10].aligned_right?).to eq(false)
end
# ECMA-376 Office Open XML spec (4th edition), 17.3.2.38, size is
@@ -282,15 +300,15 @@
# http://www.ecma-international.org/publications/standards/Ecma-376.htm
it 'should return proper font size for paragraphs' do
expect(@doc.font_size).to eq 11
- expect(@doc.paragraphs[5].font_size).to eq 11
- paragraph = @doc.paragraphs[9]
+ expect(@doc.paragraphs[6].font_size).to eq 11
+ paragraph = @doc.paragraphs[10]
expect(paragraph.font_size).to eq 14
expect(paragraph.text_runs[0].font_size).to eq 14
end
it 'should return proper font size for runs' do
expect(@doc.font_size).to eq 11
- paragraph = @doc.paragraphs[10]
+ paragraph = @doc.paragraphs[11]
expect(paragraph.font_size).to eq 11
text_runs = paragraph.text_runs
expect(text_runs[0].font_size).to eq 11
@@ -301,7 +319,7 @@
end
it 'should return changed value for runs' do
- paragraph = @doc.paragraphs[10]
+ paragraph = @doc.paragraphs[11]
text_runs = paragraph.text_runs
tr = text_runs[0]
@@ -362,9 +380,9 @@
describe 'outputting html' do
before do
@doc = Docx::Document.open(@fixtures_path + '/formatting.docx')
- @formatted_line = @doc.paragraphs[5]
+ @formatted_line = @doc.paragraphs[6]
@p_regex = /(^\
)\w+)(\<\/p>$)/
- @span_regex = /(\)\w+)(<\/span>)/
+ @span_regex = /(\)\w+)(<\/span>)/
@em_regex = /(\)\w+)(\<\/em\>)/
@strong_regex = /(\)\w+)(\<\/strong\>)/
@anchor_tag_regex = /\(.+)\<\/a>/
@@ -396,16 +414,21 @@
expect(scan.first).to eq 'style="text-decoration:underline;"'
end
+ it 'should highlight highlighted text' do
+ scan = @doc.paragraphs[4].to_html.scan(/\]+)/).flatten
+ expect(scan.first).to eq 'style="background-color:yellow;"'
+ end
+
it 'should justify paragraphs' do
regex = /^]+style\=\"([^\"]+).+(<\/p>)/
- scan = @doc.paragraphs[9].to_html.scan(regex).flatten
+ scan = @doc.paragraphs[10].to_html.scan(regex).flatten
expect(scan.first).to eq '
'
expect(scan[1].split(';').include?('font-size:14pt')).to eq(true)
@@ -413,14 +436,14 @@
it 'should set font size on styled text runs' do
regex = /(\]+style\=\"([^\"]+)[^\<]+(<\/span>)/
- scan = @doc.paragraphs[10].to_html.scan(regex).flatten
+ scan = @doc.paragraphs[11].to_html.scan(regex).flatten
expect(scan.first).to eq ''
expect(scan[1].split(';').include?('font-size:16pt')).to eq(true)
end
it 'should properly highlight different text in different places in a sentence' do
- paragraph = @doc.paragraphs[11]
+ paragraph = @doc.paragraphs[12]
scan = paragraph.to_html.scan(@em_regex).flatten
expect(scan.first).to eq ''
@@ -429,12 +452,16 @@
expect(scan.first).to eq ''
expect(scan[1]).to eq 'formatting'
- scan = paragraph.to_html.scan(@span_regex).flatten
- expect(scan.first).to eq ''
- expect(scan[1]).to eq 'different'
- scan = paragraph.to_html.scan(/\]+)/).flatten
- expect(scan.first).to eq 'style="text-decoration:underline;"'
+ scan = paragraph.to_html.scan(@span_regex)
+ expect(scan[0].first).to eq ''
+ expect(scan[1].first).to eq ''
+ expect(scan[0][1]).to eq 'different'
+ expect(scan[1][1]).to eq 'places'
+ scan = paragraph.to_html.scan(/\]+)/)
+ expect(scan[0].first).to eq 'style="text-decoration:underline;"'
+ expect(scan[1].first).to eq 'style="background-color:yellow;"'
end
it 'should output an entire document as html fragment' do
@@ -442,7 +469,7 @@
end
it 'should output styled html' do
- expect(@formatted_line.to_html.scan('all').size).to eq 1
+ expect(@formatted_line.to_html.scan('all').size).to eq 1
end
it 'should join paragraphs with newlines' do
diff --git a/spec/fixtures/formatting.docx b/spec/fixtures/formatting.docx
index 53e21d9dc3a62215a521f01c4778315eadc4e2db..3afc21e320ebf929270b942bab76ce3b18218374 100644
GIT binary patch
literal 11080
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
XO%(*Fdi*TFLj!a}6DD`2pTGVe?~P=U
literal 12477
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
a&ly@l8U}10KPsRR0qx*jf^DWBzy1%)Ef_xl