html2text 0.3.1 → 0.5.0
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/CHANGELOG.md +24 -1
- data/README.md +5 -5
- data/lib/html2text/version.rb +3 -1
- data/lib/html2text.rb +108 -116
- metadata +62 -110
- data/spec/examples/anchors.html +0 -12
- data/spec/examples/anchors.txt +0 -5
- data/spec/examples/basic.html +0 -21
- data/spec/examples/basic.txt +0 -15
- data/spec/examples/dom-processing.html +0 -8
- data/spec/examples/dom-processing.txt +0 -1
- data/spec/examples/empty.html +0 -0
- data/spec/examples/empty.txt +0 -0
- data/spec/examples/full_email.html +0 -220
- data/spec/examples/full_email.txt +0 -54
- data/spec/examples/huge-msoffice.html +0 -1
- data/spec/examples/huge-msoffice.txt +0 -25872
- data/spec/examples/images.html +0 -54
- data/spec/examples/images.txt +0 -27
- data/spec/examples/invalid.html +0 -4
- data/spec/examples/invalid.txt +0 -1
- data/spec/examples/lists.html +0 -24
- data/spec/examples/lists.txt +0 -17
- data/spec/examples/more-anchors.html +0 -14
- data/spec/examples/more-anchors.txt +0 -7
- data/spec/examples/msoffice.html +0 -1
- data/spec/examples/msoffice.txt +0 -12
- data/spec/examples/nbsp.html +0 -1
- data/spec/examples/nbsp.txt +0 -1
- data/spec/examples/nested-divs.html +0 -17
- data/spec/examples/nested-divs.txt +0 -12
- data/spec/examples/newlines.html +0 -50
- data/spec/examples/newlines.txt +0 -35
- data/spec/examples/non-breaking-spaces.html +0 -1
- data/spec/examples/non-breaking-spaces.txt +0 -1
- data/spec/examples/pre.html +0 -10
- data/spec/examples/pre.txt +0 -8
- data/spec/examples/table.html +0 -53
- data/spec/examples/table.txt +0 -7
- data/spec/examples/test3.html +0 -1
- data/spec/examples/test3.txt +0 -2
- data/spec/examples/test4.html +0 -1
- data/spec/examples/test4.txt +0 -5
- data/spec/examples/utf8-example.html +0 -4
- data/spec/examples/utf8-example.txt +0 -2
- data/spec/examples/windows-1252-example.html +0 -4
- data/spec/examples/windows-1252-example.txt +0 -2
- data/spec/examples/zero-width-non-joiners.html +0 -1
- data/spec/examples/zero-width-non-joiners.txt +0 -1
- data/spec/examples_spec.rb +0 -41
- data/spec/html2text_spec.rb +0 -58
- data/spec/spec_helper.rb +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a1e6fcee8973815b391ca28bd156a878e36594e0bfbece01f7ff8c67f6cad6f
|
|
4
|
+
data.tar.gz: f515c6edb112deeae87f59b717aa4a2aed5d37b4b42db151fc49359f2b98d0b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3972abc5f287146a5f43969fe3a37b83333559618b198ccc6ca3b105178c7cb37a03c9b44317428c8323c9b86e7e7a22a78276d9ddb7cccaab1819105e469a18
|
|
7
|
+
data.tar.gz: dcc2f39ebc4d772b47762cacb5de22ab16f386fbc4952c7fcf72ae66938fffcacad1fad7062714fdebc21a6b74c802224d02b796c099d2a00d1b3d13729f5d6d
|
data/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,30 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [Unreleased]
|
|
7
|
+
## [Unreleased](https://github.com/soundasleep/html2text_ruby/compare/v0.5.0...master)
|
|
8
|
+
|
|
9
|
+
## [0.5.0](https://github.com/soundasleep/html2text_ruby/compare/v0.4.0...v0.5.0) - 2026-07-08
|
|
10
|
+
### Added
|
|
11
|
+
- Add a `wrap_links:` option to `Html2Text.convert` and `Html2Text.new` (default `true`); when `false`, hyperlinks render as `text: href` instead of markdown-style `[text](href)`. Only affects `<a href>` links — images and named anchors keep their `[bracketed]` format ([#40](https://github.com/soundasleep/html2text_ruby/pull/40))
|
|
12
|
+
- Specs covering subclass overrides of `prefix_whitespace`, `suffix_whitespace`, `iterate_over`, `wrap_link` and `image_text`, plus unit specs for `fix_newlines` and `replace_entities` ([#41](https://github.com/soundasleep/html2text_ruby/pull/41))
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Require Ruby 3.3+, since all earlier Rubies are EOL; CI now tests Ruby 3.3, 3.4 and 4.0 ([#42](https://github.com/soundasleep/html2text_ruby/pull/42))
|
|
16
|
+
- Reduce object allocations in `iterate_over` for ~1.6x faster conversion of large documents ([#41](https://github.com/soundasleep/html2text_ruby/pull/41))
|
|
17
|
+
- Update development dependencies (rubocop 1.88, nokogiri 1.19 in the lockfile), removing the transitive `rexml` dependency ([#42](https://github.com/soundasleep/html2text_ruby/pull/42))
|
|
18
|
+
|
|
19
|
+
## [0.4.0](https://github.com/soundasleep/html2text_ruby/compare/0.3.1...v0.4.0) - 2024-06-08
|
|
20
|
+
### Added
|
|
21
|
+
- Switch from Travis to Github Actions for Build and Test
|
|
22
|
+
- Add rubocop for linting and cleanup existing violations ([#36](https://github.com/soundasleep/html2text_ruby/pull/36))
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Add support for Ruby 3.x, removed support for Ruby < 3.0 since it is EOL
|
|
26
|
+
- Allow subclassing of `Html2Text` to override the default behaviour ([#30](https://github.com/soundasleep/html2text_ruby/pull/30))
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Loosen nokogiri dependency to allow for nokogiri < 2.0 ([#17](https://github.com/soundasleep/html2text_ruby/pull/17))
|
|
30
|
+
- Fix `NoMethodError` when parsing nodes with no name ([#15](https://github.com/soundasleep/html2text_ruby/pull/15))
|
|
8
31
|
|
|
9
32
|
## [0.3.1] - 2019-06-12
|
|
10
33
|
### Security
|
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
html2text
|
|
2
|
-
|
|
1
|
+
html2text  [](https://badge.fury.io/rb/html2text)
|
|
2
|
+
---
|
|
3
3
|
|
|
4
4
|
`html2text` is a very simple gem that uses DOM methods to convert HTML into a format similar to what would be
|
|
5
5
|
rendered by a browser - perfect for places where you need a quick text representation. For example:
|
|
@@ -20,7 +20,7 @@ rendered by a browser - perfect for places where you need a quick text represent
|
|
|
20
20
|
<div>Another div</div>
|
|
21
21
|
<div>A div<div>within a div</div></div>
|
|
22
22
|
|
|
23
|
-
<a href="
|
|
23
|
+
<a href="https://foo.com">A link</a>
|
|
24
24
|
|
|
25
25
|
</body>
|
|
26
26
|
</html>
|
|
@@ -40,7 +40,7 @@ Another div
|
|
|
40
40
|
A div
|
|
41
41
|
within a div
|
|
42
42
|
|
|
43
|
-
[A link](
|
|
43
|
+
[A link](https://foo.com)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
See the [original blog post](http://journals.jevon.org/users/jevon-phd/entry/19818) or the related [StackOverflow answer](http://stackoverflow.com/a/2564472/39531).
|
|
@@ -63,7 +63,7 @@ text = Html2Text.convert(html)
|
|
|
63
63
|
|
|
64
64
|
## Tests
|
|
65
65
|
|
|
66
|
-
See all of the test cases defined in [spec/examples/](spec/examples/). These can be run with `bundle
|
|
66
|
+
See all of the test cases defined in [spec/examples/](spec/examples/). These can be run with `bundle exec rake`.
|
|
67
67
|
|
|
68
68
|
## License
|
|
69
69
|
|
data/lib/html2text/version.rb
CHANGED
data/lib/html2text.rb
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'nokogiri'
|
|
2
4
|
|
|
3
5
|
class Html2Text
|
|
4
6
|
attr_reader :doc
|
|
5
7
|
|
|
6
|
-
def initialize(doc)
|
|
8
|
+
def initialize(doc, wrap_links: true)
|
|
7
9
|
@doc = doc
|
|
10
|
+
@wrap_links = wrap_links
|
|
8
11
|
end
|
|
9
12
|
|
|
10
|
-
def self.convert(html)
|
|
13
|
+
def self.convert(html, wrap_links: true)
|
|
11
14
|
html = html.to_s
|
|
12
15
|
|
|
13
|
-
if
|
|
16
|
+
if office_document?(html)
|
|
14
17
|
# Emulate the CSS rendering of Office documents
|
|
15
|
-
html = html.gsub(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
html = html.gsub('<p class=MsoNormal>', '<br>')
|
|
19
|
+
.gsub('<o:p> </o:p>', '<br>')
|
|
20
|
+
.gsub('<o:p></o:p>', '')
|
|
18
21
|
end
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
unless html.include?('<html')
|
|
21
24
|
# Stop Nokogiri from inserting in <p> tags
|
|
22
25
|
html = "<div>#{html}</div>"
|
|
23
26
|
end
|
|
@@ -25,25 +28,30 @@ class Html2Text
|
|
|
25
28
|
html = fix_newlines(replace_entities(html))
|
|
26
29
|
doc = Nokogiri::HTML(html)
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
new(doc, wrap_links: wrap_links).convert
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def self.fix_newlines(text)
|
|
35
|
+
# rubocop:disable Performance/StringReplacement
|
|
32
36
|
text.gsub("\r\n", "\n").gsub("\r", "\n")
|
|
37
|
+
# rubocop:enable Performance/StringReplacement
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
def self.replace_entities(text)
|
|
36
|
-
|
|
41
|
+
# rubocop:disable Performance/StringReplacement
|
|
42
|
+
text.gsub(' ', ' ').gsub("\u00a0", ' ').gsub('‌', '')
|
|
43
|
+
# rubocop:enable Performance/StringReplacement
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
def convert
|
|
40
47
|
output = iterate_over(doc)
|
|
41
48
|
output = remove_leading_and_trailing_whitespace(output)
|
|
42
49
|
output = remove_unnecessary_empty_lines(output)
|
|
43
|
-
|
|
50
|
+
output.strip
|
|
44
51
|
end
|
|
45
52
|
|
|
46
|
-
DO_NOT_TOUCH_WHITESPACE =
|
|
53
|
+
DO_NOT_TOUCH_WHITESPACE = '<do-not-touch-whitespace>'
|
|
54
|
+
IGNORED_TAGS = Set.new(%w[style head title meta script]).freeze
|
|
47
55
|
|
|
48
56
|
def remove_leading_and_trailing_whitespace(text)
|
|
49
57
|
# ignore any <pre> blocks, which we don't want to interact with
|
|
@@ -51,211 +59,195 @@ class Html2Text
|
|
|
51
59
|
|
|
52
60
|
output = []
|
|
53
61
|
pre_blocks.each.with_index do |block, index|
|
|
54
|
-
if index
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
output << if index.even?
|
|
63
|
+
block.gsub(/[ \t]*\n[ \t]*/im, "\n").gsub(/ *\t */im, "\t")
|
|
64
|
+
else
|
|
65
|
+
block
|
|
66
|
+
end
|
|
59
67
|
end
|
|
60
68
|
|
|
61
|
-
output.join
|
|
69
|
+
output.join
|
|
62
70
|
end
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def self.is_office_document?(text)
|
|
67
|
-
text.include?("urn:schemas-microsoft-com:office")
|
|
72
|
+
private_class_method def self.office_document?(text)
|
|
73
|
+
text.include?('urn:schemas-microsoft-com:office')
|
|
68
74
|
end
|
|
69
75
|
|
|
76
|
+
private
|
|
77
|
+
|
|
70
78
|
def remove_unnecessary_empty_lines(text)
|
|
71
|
-
text.gsub(/\n
|
|
79
|
+
text.gsub(/\n{3,}/, "\n\n")
|
|
72
80
|
end
|
|
73
81
|
|
|
74
82
|
def trimmed_whitespace(text)
|
|
75
83
|
# Replace whitespace characters with a space (equivalent to \s)
|
|
76
84
|
# and force any text encoding into UTF-8
|
|
77
85
|
if text.valid_encoding?
|
|
78
|
-
text.gsub(/[\t\n\f\r ]
|
|
86
|
+
text.gsub(/[\t\n\f\r ]+/, ' ')
|
|
79
87
|
else
|
|
80
|
-
text.force_encoding(
|
|
81
|
-
|
|
88
|
+
text.force_encoding('WINDOWS-1252')
|
|
89
|
+
trimmed_whitespace(text.encode('UTF-16be', invalid: :replace, replace: '?').encode('UTF-8'))
|
|
82
90
|
end
|
|
83
91
|
end
|
|
84
92
|
|
|
85
93
|
def iterate_over(node)
|
|
86
|
-
return "\n" if node.name.downcase == "br" && next_node_is_text?(node)
|
|
87
|
-
|
|
88
94
|
return trimmed_whitespace(node.text) if node.text?
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
return ""
|
|
92
|
-
end
|
|
96
|
+
name = node.name&.downcase
|
|
93
97
|
|
|
94
|
-
if
|
|
95
|
-
return "\n#{DO_NOT_TOUCH_WHITESPACE}#{node.text}#{DO_NOT_TOUCH_WHITESPACE}"
|
|
96
|
-
end
|
|
98
|
+
return '' if name.nil? || IGNORED_TAGS.include?(name)
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
return "\n" if name == 'br' && next_node_is_text?(node)
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
output += node.children.map do |child|
|
|
102
|
-
iterate_over(child)
|
|
103
|
-
end
|
|
104
|
-
output << suffix_whitespace(node)
|
|
102
|
+
return "\n#{DO_NOT_TOUCH_WHITESPACE}#{node.text}#{DO_NOT_TOUCH_WHITESPACE}" if name == 'pre'
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
return image_text(node) if name == 'img'
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
output
|
|
106
|
+
output = +''
|
|
107
|
+
output << (prefix_whitespace(node) || '')
|
|
108
|
+
node.children.each do |child|
|
|
109
|
+
output << iterate_over(child)
|
|
112
110
|
end
|
|
111
|
+
output << (suffix_whitespace(node) || '')
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
output = wrap_link(node, output) if name == 'a'
|
|
114
|
+
|
|
115
|
+
output
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
def prefix_whitespace(node)
|
|
118
119
|
case node.name.downcase
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
when 'hr'
|
|
121
|
+
"\n---------------------------------------------------------------\n"
|
|
121
122
|
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p'
|
|
124
|
+
"\n\n"
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
when 'tr'
|
|
127
|
+
"\n"
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
when 'div'
|
|
130
|
+
if node.parent.name == 'div' && (node.parent.text.strip == node.text.strip)
|
|
131
|
+
''
|
|
132
|
+
else
|
|
129
133
|
"\n"
|
|
134
|
+
end
|
|
130
135
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
""
|
|
134
|
-
else
|
|
135
|
-
"\n"
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
when "td", "th"
|
|
139
|
-
"\t"
|
|
136
|
+
when 'td', 'th'
|
|
137
|
+
"\t"
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
when 'li'
|
|
140
|
+
'- '
|
|
143
141
|
end
|
|
144
142
|
end
|
|
145
143
|
|
|
146
144
|
def suffix_whitespace(node)
|
|
147
145
|
case node.name.downcase
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"\n\n"
|
|
146
|
+
when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'
|
|
147
|
+
"\n\n"
|
|
151
148
|
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
when 'br'
|
|
150
|
+
next_name = next_node_name(node)
|
|
151
|
+
"\n" if !next_name.nil? && next_name != 'div'
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"\n"
|
|
158
|
-
end
|
|
153
|
+
when 'li'
|
|
154
|
+
"\n"
|
|
159
155
|
|
|
160
|
-
|
|
156
|
+
when 'div'
|
|
157
|
+
if next_node_is_text?(node)
|
|
161
158
|
"\n"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
elsif next_node_name(node) != "div" && next_node_name(node) != nil
|
|
167
|
-
"\n"
|
|
168
|
-
end
|
|
159
|
+
else
|
|
160
|
+
next_name = next_node_name(node)
|
|
161
|
+
"\n" if !next_name.nil? && next_name != 'div'
|
|
162
|
+
end
|
|
169
163
|
end
|
|
170
164
|
end
|
|
171
165
|
|
|
172
|
-
# links are returned in [text](link) format
|
|
166
|
+
# links are returned in [text](link) format, or "text: link" with wrap_links: false
|
|
173
167
|
def wrap_link(node, output)
|
|
174
|
-
href = node.attribute(
|
|
175
|
-
name = node.attribute(
|
|
168
|
+
href = node.attribute('href')
|
|
169
|
+
name = node.attribute('name')
|
|
176
170
|
|
|
177
171
|
output = output.strip
|
|
178
172
|
|
|
179
173
|
# remove double [[ ]]s from linking images
|
|
180
|
-
if output[0] ==
|
|
174
|
+
if output[0] == '[' && output[-1] == ']'
|
|
181
175
|
output = output[1, output.length - 2]
|
|
182
176
|
|
|
183
177
|
# for linking images, the title of the <a> overrides the title of the <img>
|
|
184
|
-
if node.attribute(
|
|
185
|
-
output = node.attribute("title").to_s
|
|
186
|
-
end
|
|
178
|
+
output = node.attribute('title').to_s if node.attribute('title')
|
|
187
179
|
end
|
|
188
180
|
|
|
189
181
|
# if there is no link text, but a title attr
|
|
190
|
-
if output.empty? && node.attribute(
|
|
191
|
-
output = node.attribute("title").to_s
|
|
192
|
-
end
|
|
182
|
+
output = node.attribute('title').to_s if output.empty? && node.attribute('title')
|
|
193
183
|
|
|
194
184
|
if href.nil?
|
|
195
|
-
|
|
196
|
-
output = "[#{output}]"
|
|
197
|
-
end
|
|
185
|
+
output = "[#{output}]" unless name.nil?
|
|
198
186
|
else
|
|
199
187
|
href = href.to_s
|
|
200
188
|
|
|
201
189
|
if href != output && href != "mailto:#{output}" &&
|
|
202
|
-
|
|
203
|
-
if output.empty?
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
190
|
+
href != "http://#{output}" && href != "https://#{output}"
|
|
191
|
+
output = if output.empty?
|
|
192
|
+
href
|
|
193
|
+
elsif !@wrap_links
|
|
194
|
+
"#{output}: #{href}"
|
|
195
|
+
else
|
|
196
|
+
"[#{output}](#{href})"
|
|
197
|
+
end
|
|
208
198
|
end
|
|
209
199
|
end
|
|
210
200
|
|
|
211
201
|
case next_node_name(node)
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
|
|
203
|
+
output += "\n"
|
|
214
204
|
end
|
|
215
205
|
|
|
216
206
|
output
|
|
217
207
|
end
|
|
218
208
|
|
|
219
209
|
def image_text(node)
|
|
220
|
-
if node.attribute(
|
|
221
|
-
"[
|
|
222
|
-
elsif node.attribute(
|
|
223
|
-
"[
|
|
210
|
+
if node.attribute('title')
|
|
211
|
+
"[#{node.attribute('title')}]"
|
|
212
|
+
elsif node.attribute('alt')
|
|
213
|
+
"[#{node.attribute('alt')}]"
|
|
224
214
|
else
|
|
225
|
-
|
|
215
|
+
''
|
|
226
216
|
end
|
|
227
217
|
end
|
|
228
218
|
|
|
229
219
|
def next_node_name(node)
|
|
230
220
|
next_node = node.next_sibling
|
|
231
|
-
|
|
221
|
+
until next_node.nil?
|
|
232
222
|
break if next_node.element?
|
|
223
|
+
|
|
233
224
|
next_node = next_node.next_sibling
|
|
234
225
|
end
|
|
235
226
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
227
|
+
return unless next_node&.element?
|
|
228
|
+
|
|
229
|
+
next_node.name.downcase
|
|
239
230
|
end
|
|
240
231
|
|
|
241
232
|
def next_node_is_text?(node)
|
|
242
|
-
|
|
233
|
+
!node.next_sibling.nil? && node.next_sibling.text? && !node.next_sibling.text.strip.empty?
|
|
243
234
|
end
|
|
244
235
|
|
|
245
236
|
def previous_node_name(node)
|
|
246
237
|
previous_node = node.previous_sibling
|
|
247
|
-
|
|
238
|
+
until previous_node.nil?
|
|
248
239
|
break if previous_node.element?
|
|
240
|
+
|
|
249
241
|
previous_node = previous_node.previous_sibling
|
|
250
242
|
end
|
|
251
243
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
244
|
+
return unless previous_node&.element?
|
|
245
|
+
|
|
246
|
+
previous_node.name.downcase
|
|
255
247
|
end
|
|
256
248
|
|
|
257
249
|
def previous_node_is_text?(node)
|
|
258
|
-
|
|
250
|
+
!node.previous_sibling.nil? && node.previous_sibling.text? && !node.previous_sibling.text.strip.empty?
|
|
259
251
|
end
|
|
260
252
|
|
|
261
253
|
# def previous_node_is_not_text?(node)
|