docx 0.13.1 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/docx/containers/paragraph.rb +43 -18
- data/lib/docx/elements/text.rb +2 -0
- data/lib/docx/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db1fd167dc6ddfecc81518f27cd78fc203d74738ffe52b2877251b2ed291c297
|
|
4
|
+
data.tar.gz: 7c2f230a44f18ceffffcbbfa55d212ebdd2a58651823aa78d303cc64493aa461
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e1af62546207b59b6b2bcd193c83aaafc11258dcb50d7c52bcd2251b77552704b39d5667a709cdc9fdd005c37e00e3530acd9fb88df565ba10db361d6032d98
|
|
7
|
+
data.tar.gz: 7d753f33adc0795e12cea763d32e01d4cf76777d1cf7f5c8d815991efccfd0ede2b513a7a99c5558824e972e17ffd86460f661d87f7003dd916cca2cdbc06498
|
|
@@ -70,9 +70,13 @@ module Docx
|
|
|
70
70
|
# such as "{{fi", "rst_na", "me}}"). The per-run TextRun#substitute cannot
|
|
71
71
|
# match those, but this can, because it joins the runs first.
|
|
72
72
|
#
|
|
73
|
-
# The
|
|
74
|
-
#
|
|
75
|
-
#
|
|
73
|
+
# The substitution works on the paragraph's w:t nodes, so it also handles
|
|
74
|
+
# runs holding several w:t (LibreOffice writes "text<w:br/>text" in one
|
|
75
|
+
# run) and placeholders that start inside a hyperlink. The replacement
|
|
76
|
+
# goes into the w:t holding the first matched character, so that run's
|
|
77
|
+
# formatting is used; the rest of the match is cut from the following
|
|
78
|
+
# w:t, and line breaks and tabs inside the match are dropped. Text,
|
|
79
|
+
# breaks and tabs outside the match stay where they are.
|
|
76
80
|
#
|
|
77
81
|
# +pattern+ may be a String or a Regexp; +replacement+ follows String#sub
|
|
78
82
|
# semantics, so capture-group backreferences (e.g. '\1') work with a Regexp.
|
|
@@ -81,20 +85,19 @@ module Docx
|
|
|
81
85
|
# paragraph.substitute('{{first_name}}', 'Jane') # => "Hello Jane!"
|
|
82
86
|
# paragraph.substitute(/\{\{(\w+)\}\}/, 'value of \1')
|
|
83
87
|
#
|
|
84
|
-
# See https://github.com/ruby-docx/docx/issues/147
|
|
88
|
+
# See https://github.com/ruby-docx/docx/issues/147 and #187
|
|
85
89
|
def substitute(pattern, replacement)
|
|
86
90
|
search_from = 0
|
|
87
91
|
loop do
|
|
88
|
-
|
|
89
|
-
break if runs.empty?
|
|
92
|
+
texts = @node.xpath(TEXT_XPATH).map { |t_node| Elements::Text.new(t_node) }
|
|
90
93
|
|
|
91
94
|
offsets = []
|
|
92
95
|
cursor = 0
|
|
93
|
-
|
|
96
|
+
texts.each do |text|
|
|
94
97
|
offsets << cursor
|
|
95
|
-
cursor +=
|
|
98
|
+
cursor += text.content.length
|
|
96
99
|
end
|
|
97
|
-
full_text =
|
|
100
|
+
full_text = texts.map(&:content).join
|
|
98
101
|
|
|
99
102
|
match = full_text.match(pattern, search_from)
|
|
100
103
|
break unless match
|
|
@@ -102,15 +105,21 @@ module Docx
|
|
|
102
105
|
|
|
103
106
|
match_start = match.begin(0)
|
|
104
107
|
match_end = match.end(0) # exclusive
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
# the w:t holding the first and the last matched character
|
|
109
|
+
first = texts.each_index.find { |i| offsets[i] + texts[i].content.length > match_start }
|
|
110
|
+
last = texts.each_index.find { |i| offsets[i] + texts[i].content.length >= match_end }
|
|
111
|
+
|
|
112
|
+
replaced = match[0].sub(pattern, replacement)
|
|
113
|
+
head = texts[first].content[0...(match_start - offsets[first])]
|
|
114
|
+
tail = texts[last].content[(match_end - offsets[last])..]
|
|
115
|
+
if first == last
|
|
116
|
+
texts[first].content = head + replaced + tail
|
|
117
|
+
else
|
|
118
|
+
texts[first].content = head + replaced
|
|
119
|
+
texts[(first + 1)...last].each { |text| text.content = '' }
|
|
120
|
+
texts[last].content = tail
|
|
121
|
+
remove_separators_between(texts[first].node, texts[last].node)
|
|
122
|
+
end
|
|
114
123
|
|
|
115
124
|
# advance past the inserted replacement so it is not re-matched
|
|
116
125
|
search_from = match_start + replaced.length
|
|
@@ -165,6 +174,22 @@ module Docx
|
|
|
165
174
|
|
|
166
175
|
private
|
|
167
176
|
|
|
177
|
+
# The w:t read by #text (those of the runs and hyperlink runs), in
|
|
178
|
+
# document order.
|
|
179
|
+
TEXT_XPATH = 'w:r/w:t|w:hyperlink/w:r/w:t'
|
|
180
|
+
# Line breaks and tabs that sit between those w:t.
|
|
181
|
+
SEPARATOR_XPATH = %w[br tab cr].map { |tag| "w:r/w:#{tag}|w:hyperlink/w:r/w:#{tag}" }.join('|')
|
|
182
|
+
private_constant :TEXT_XPATH, :SEPARATOR_XPATH
|
|
183
|
+
|
|
184
|
+
# Remove the line breaks and tabs lying between two w:t of a match.
|
|
185
|
+
def remove_separators_between(first_t, last_t)
|
|
186
|
+
separators = @node.xpath(SEPARATOR_XPATH).to_a
|
|
187
|
+
nodes = @node.xpath("#{TEXT_XPATH}|#{SEPARATOR_XPATH}").to_a
|
|
188
|
+
from = nodes.index(first_t)
|
|
189
|
+
to = nodes.index(last_t)
|
|
190
|
+
nodes[(from + 1)...to].each { |node| node.remove if separators.include?(node) }
|
|
191
|
+
end
|
|
192
|
+
|
|
168
193
|
def style_property
|
|
169
194
|
properties&.at_xpath('w:pStyle') || properties&.add_child('<w:pStyle/>').first
|
|
170
195
|
end
|
data/lib/docx/elements/text.rb
CHANGED
data/lib/docx/version.rb
CHANGED