wovnrb 3.11.0 → 3.13.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.
@@ -1,222 +1,240 @@
1
- module Wovnrb
2
- class HtmlConverter
3
- def initialize(dom, store, headers, url_lang_switcher)
4
- @dom = dom
5
- @headers = headers
6
- @store = store
7
- @url_lang_switcher = url_lang_switcher
8
- end
9
-
10
- def build
11
- transform_html
12
- html
13
- end
14
-
15
- def build_api_compatible_html
16
- marker = HtmlReplaceMarker.new
17
- converted_html = replace_dom(marker)
18
- converted_html = remove_backend_wovn_ignore_comments(converted_html, marker)
19
- [converted_html, marker]
20
- end
21
-
22
- private
23
-
24
- def remove_backend_wovn_ignore_comments(html, marker)
25
- backend_ignore_regex = /(<!--\s*backend-wovn-ignore\s*-->)(.+?)(<!--\s*\/backend-wovn-ignore\s*-->)/m
26
- html.gsub(backend_ignore_regex) do |_match|
27
- comment_start = Regexp.last_match(1)
28
- ignored_content = Regexp.last_match(2)
29
- comment_end = Regexp.last_match(3)
30
- key = marker.add_comment_value(ignored_content)
31
- comment_start + key + comment_end
32
- end
33
- end
34
-
35
- def html
36
- # Ensure a Content-Type declaration in the header. This mimics Nokogumbo
37
- # 1.5.0 default serialization behavior.
38
- @dom.meta_encoding = 'UTF-8' if @dom.respond_to?(:meta_encoding=)
39
-
40
- @dom.to_html(save_with: 0).strip
41
- end
42
-
43
- def transform_html
44
- replace_snippet
45
- replace_hreflangs if @store.settings['insert_hreflangs']
46
- inject_lang_html_tag
47
- translate_canonical_tag if @store.settings['translate_canonical_tag']
48
- end
49
-
50
- def replace_snippet
51
- strip_snippet
52
- insert_snippet
53
- end
54
-
55
- def replace_dom(marker)
56
- strip_snippet
57
- strip_hreflangs if @store.settings['insert_hreflangs']
58
-
59
- @dom.traverse { |node| transform_node(node, marker) }
60
-
61
- insert_snippet(adds_backend_error_mark: true)
62
- insert_hreflang_tags if @store.settings['insert_hreflangs']
63
- inject_lang_html_tag
64
- translate_canonical_tag if @store.settings['translate_canonical_tag']
65
-
66
- html
67
- end
68
-
69
- def transform_node(node, marker)
70
- strip_wovn_ignore(node, marker)
71
- strip_custom_ignore(node, marker)
72
- strip_form(node, marker)
73
- strip_script(node, marker)
74
- end
75
-
76
- def strip_script(node, marker)
77
- put_replace_marker(node, marker) if node.name.casecmp('script').zero?
78
- end
79
-
80
- def strip_form(node, marker)
81
- if node.name.casecmp('form').zero?
82
- put_replace_marker(node, marker)
83
- return
84
- end
85
-
86
- if node.name.casecmp('input').zero? && node.get_attribute('type') == 'hidden'
87
- original_text = node.get_attribute('value')
88
- return if original_text.nil?
89
- return if original_text.include?(HtmlReplaceMarker::KEY_PREFIX)
90
-
91
- node.set_attribute('value', marker.add_value(original_text))
92
- end
93
- end
94
-
95
- def strip_custom_ignore(node, marker)
96
- classes = node.get_attribute('class')
97
- return unless classes.present?
98
-
99
- ignored_classes = @store.settings['ignore_class']
100
- should_be_ignored = (ignored_classes & classes.split).present?
101
-
102
- put_replace_marker(node, marker) if should_be_ignored
103
- end
104
-
105
- def strip_wovn_ignore(node, marker)
106
- put_replace_marker(node, marker) if node && (node.get_attribute('wovn-ignore') || node.get_attribute('data-wovn-ignore'))
107
- end
108
-
109
- def put_replace_marker(node, marker)
110
- original_text = node.inner_html
111
- return if original_text.include?(HtmlReplaceMarker::KEY_PREFIX)
112
-
113
- node.inner_html = marker.add_comment_value(original_text)
114
- end
115
-
116
- def strip_hreflangs
117
- supported_langs = @store.supported_langs
118
- @dom.xpath('//link') do |node|
119
- node.remove if node['hreflang'] && supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
120
- end
121
- end
122
-
123
- def inject_lang_html_tag
124
- root = @dom.at_css('html')
125
- return unless root
126
- return if root['lang']
127
-
128
- root['lang'] = @store.default_lang
129
- end
130
-
131
- def replace_hreflangs
132
- strip_hreflang_tags
133
- insert_hreflang_tags
134
- end
135
-
136
- def strip_hreflang_tags
137
- @dom.xpath('//link').each do |node|
138
- node.remove if node['hreflang'] && @store.supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
139
- end
140
- end
141
-
142
- def insert_hreflang_tags
143
- parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
144
- return unless parent_node
145
-
146
- @store.supported_langs.each do |lang_code|
147
- insert_node = Nokogiri::XML::Node.new('link', @dom)
148
- insert_node['rel'] = 'alternate'
149
- insert_node['hreflang'] = Lang.iso_639_1_normalization(lang_code)
150
- insert_node['href'] = @headers.redirect_location(lang_code)
151
-
152
- parent_node.add_child(insert_node.to_s)
153
- end
154
- end
155
-
156
- def translate_canonical_tag
157
- canonical_node = @dom.at_css('link[rel="canonical"]')
158
- return unless canonical_node
159
-
160
- lang_code = @headers.lang_code
161
- return if lang_code == @store.settings['default_lang'] && @store.settings['custom_lang_aliases'][lang_code].nil?
162
-
163
- canonical_url = canonical_node['href']
164
-
165
- translated_canonical_url = @url_lang_switcher.add_lang_code(canonical_url, lang_code, @headers)
166
- canonical_node['href'] = translated_canonical_url
167
- end
168
-
169
- # Remove wovn snippet code from dom
170
- def strip_snippet
171
- @dom.xpath('//script').each do |script_node|
172
- script_node.remove if (script_node['src'] && widget_urls.any? { |url| script_node['src'].include? url }) || script_node['data-wovnio'].present?
173
- end
174
- end
175
-
176
- def widget_urls
177
- ["#{@store.settings['api_url']}/widget", 'j.wovn.io', 'j.dev-wovn.io:3000']
178
- end
179
-
180
- def insert_snippet(adds_backend_error_mark: true)
181
- parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
182
- return unless parent_node
183
-
184
- insert_node = Nokogiri::XML::Node.new('script', @dom)
185
- insert_node['src'] = @store.widget_url
186
- insert_node['async'] = true
187
- insert_node['data-wovnio'] = data_wovnio
188
- insert_node['data-wovnio-type'] = 'fallback_snippet' if adds_backend_error_mark
189
- # do this so that there will be a closing tag (better compatibility with browsers)
190
- insert_node.content = ''
191
-
192
- if parent_node.children.empty?
193
- parent_node.add_child(insert_node)
194
- else
195
- parent_node.children.first.add_previous_sibling(insert_node)
196
- end
197
- end
198
-
199
- def data_wovnio
200
- token = @store.settings['project_token']
201
- current_lang = @headers.lang_code
202
- default_lang = @store.settings['default_lang']
203
- url_pattern = @store.settings['url_pattern']
204
- lang_code_aliases_json = JSON.generate(@store.settings['custom_lang_aliases'])
205
- lang_param_name = @store.settings['lang_param_name']
206
- custom_domain_langs = @store.custom_domain_langs.to_html_swapper_hash
207
-
208
- result = [
209
- "key=#{token}",
210
- 'backend=true',
211
- "currentLang=#{current_lang}",
212
- "defaultLang=#{default_lang}",
213
- "urlPattern=#{url_pattern}",
214
- "langCodeAliases=#{lang_code_aliases_json}",
215
- "langParamName=#{lang_param_name}",
216
- "version=WOVN.rb_#{VERSION}"
217
- ]
218
- result << "customDomainLangs=#{JSON.generate(custom_domain_langs)}" unless custom_domain_langs.empty?
219
- result.join('&')
220
- end
221
- end
222
- end
1
+ module Wovnrb
2
+ class HtmlConverter
3
+ def initialize(dom, store, headers, url_lang_switcher)
4
+ @dom = dom
5
+ @headers = headers
6
+ @store = store
7
+ @url_lang_switcher = url_lang_switcher
8
+ end
9
+
10
+ def build
11
+ transform_html
12
+ html
13
+ end
14
+
15
+ def build_api_compatible_html
16
+ marker = HtmlReplaceMarker.new
17
+ converted_html = replace_dom(marker)
18
+ converted_html = remove_backend_wovn_ignore_comments(converted_html, marker)
19
+ [converted_html, marker]
20
+ end
21
+
22
+ private
23
+
24
+ def remove_backend_wovn_ignore_comments(html, marker)
25
+ backend_ignore_regex = /(<!--\s*backend-wovn-ignore\s*-->)(.+?)(<!--\s*\/backend-wovn-ignore\s*-->)/m
26
+ html.gsub(backend_ignore_regex) do |_match|
27
+ comment_start = Regexp.last_match(1)
28
+ ignored_content = Regexp.last_match(2)
29
+ comment_end = Regexp.last_match(3)
30
+ key = marker.add_comment_value(ignored_content)
31
+ comment_start + key + comment_end
32
+ end
33
+ end
34
+
35
+ def html
36
+ # Ensure a Content-Type declaration in the header. This mimics Nokogumbo
37
+ # 1.5.0 default serialization behavior.
38
+ @dom.meta_encoding = 'UTF-8' if @dom.respond_to?(:meta_encoding=)
39
+
40
+ @dom.to_html(save_with: 0).strip
41
+ end
42
+
43
+ def transform_html
44
+ replace_snippet
45
+ replace_hreflangs if @store.settings['insert_hreflangs']
46
+ inject_lang_html_tag
47
+ translate_canonical_tag if @store.settings['translate_canonical_tag']
48
+ end
49
+
50
+ def replace_snippet
51
+ strip_snippet
52
+ insert_snippet
53
+ end
54
+
55
+ def replace_dom(marker)
56
+ strip_snippet
57
+ strip_hreflangs if @store.settings['insert_hreflangs']
58
+
59
+ @dom.traverse { |node| transform_node(node, marker) }
60
+
61
+ insert_snippet(adds_backend_error_mark: true)
62
+ insert_hreflang_tags if @store.settings['insert_hreflangs']
63
+ inject_lang_html_tag
64
+ translate_canonical_tag if @store.settings['translate_canonical_tag']
65
+
66
+ html
67
+ end
68
+
69
+ def transform_node(node, marker)
70
+ strip_wovn_ignore(node, marker)
71
+ strip_custom_ignore(node, marker)
72
+ strip_form(node, marker)
73
+ strip_script(node, marker)
74
+ end
75
+
76
+ def strip_script(node, marker)
77
+ put_replace_marker(node, marker) if node.name.casecmp('script').zero?
78
+ end
79
+
80
+ def strip_form(node, marker)
81
+ if node.name.casecmp('form').zero?
82
+ put_replace_marker(node, marker)
83
+ return
84
+ end
85
+
86
+ if node.name.casecmp('input').zero? && node.get_attribute('type') == 'hidden'
87
+ original_text = node.get_attribute('value')
88
+ return if original_text.nil?
89
+ return if original_text.include?(HtmlReplaceMarker::KEY_PREFIX)
90
+
91
+ node.set_attribute('value', marker.add_value(original_text))
92
+ end
93
+ end
94
+
95
+ def strip_custom_ignore(node, marker)
96
+ classes = node.get_attribute('class')
97
+ return unless classes.present?
98
+
99
+ ignored_classes = @store.settings['ignore_class']
100
+ should_be_ignored = (ignored_classes & classes.split).present?
101
+
102
+ put_replace_marker(node, marker) if should_be_ignored
103
+ end
104
+
105
+ def strip_wovn_ignore(node, marker)
106
+ put_replace_marker(node, marker) if node && (node.get_attribute('wovn-ignore') || node.get_attribute('data-wovn-ignore'))
107
+ end
108
+
109
+ def put_replace_marker(node, marker)
110
+ original_text = node.inner_html
111
+ return if original_text.include?(HtmlReplaceMarker::KEY_PREFIX)
112
+
113
+ node.inner_html = marker.add_comment_value(original_text)
114
+ end
115
+
116
+ def strip_hreflangs
117
+ supported_langs = @store.supported_langs
118
+ @dom.xpath('//link') do |node|
119
+ node.remove if node['hreflang'] && supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
120
+ end
121
+ end
122
+
123
+ def inject_lang_html_tag
124
+ root = @dom.at_css('html')
125
+ return unless root
126
+ return if root['lang']
127
+
128
+ root['lang'] = @store.default_lang
129
+ end
130
+
131
+ def replace_hreflangs
132
+ strip_hreflang_tags
133
+ insert_hreflang_tags
134
+ end
135
+
136
+ def strip_hreflang_tags
137
+ @dom.xpath('//link').each do |node|
138
+ node.remove if node['hreflang'] && @store.supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
139
+ end
140
+ end
141
+
142
+ def insert_hreflang_tags
143
+ parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
144
+ return unless parent_node
145
+
146
+ @store.supported_langs.each do |lang_code|
147
+ insert_node = Nokogiri::XML::Node.new('link', @dom)
148
+ insert_node['rel'] = 'alternate'
149
+ insert_node['hreflang'] = Lang.iso_639_1_normalization(lang_code)
150
+ insert_node['href'] = @headers.redirect_location(lang_code)
151
+
152
+ parent_node.add_child(insert_node.to_s)
153
+ end
154
+
155
+ unless existing_x_default_hreflang?
156
+ insert_node = Nokogiri::XML::Node.new('link', @dom)
157
+ insert_node['rel'] = 'alternate'
158
+ insert_node['hreflang'] = 'x-default'
159
+ insert_node['data-wovn'] = 'true'
160
+ x_default_lang_code = @store.hreflang_x_default_lang_or_default
161
+ insert_node['href'] = @headers.redirect_location(x_default_lang_code)
162
+ parent_node.add_child(insert_node.to_s)
163
+ end
164
+ end
165
+
166
+ def existing_x_default_hreflang?
167
+ @dom.xpath('//link').each do |node|
168
+ return true if node['rel'].casecmp('alternate').zero? && node['hreflang'] && node['hreflang'].casecmp('x-default').zero?
169
+ end
170
+
171
+ false
172
+ end
173
+
174
+ def translate_canonical_tag
175
+ canonical_node = @dom.at_css('link[rel="canonical"]')
176
+ return unless canonical_node
177
+
178
+ lang_code = @headers.lang_code
179
+ return if lang_code == @store.settings['default_lang'] && @store.settings['custom_lang_aliases'][lang_code].nil?
180
+
181
+ canonical_url = canonical_node['href']
182
+
183
+ translated_canonical_url = @url_lang_switcher.add_lang_code(canonical_url, lang_code, @headers)
184
+ canonical_node['href'] = translated_canonical_url
185
+ end
186
+
187
+ # Remove wovn snippet code from dom
188
+ def strip_snippet
189
+ @dom.xpath('//script').each do |script_node|
190
+ script_node.remove if (script_node['src'] && widget_urls.any? { |url| script_node['src'].include? url }) || script_node['data-wovnio'].present?
191
+ end
192
+ end
193
+
194
+ def widget_urls
195
+ ["#{@store.settings['api_url']}/widget", 'j.wovn.io', 'j.dev-wovn.io:3000']
196
+ end
197
+
198
+ def insert_snippet(adds_backend_error_mark: true)
199
+ parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
200
+ return unless parent_node
201
+
202
+ insert_node = Nokogiri::XML::Node.new('script', @dom)
203
+ insert_node['src'] = @store.widget_url
204
+ insert_node['async'] = true
205
+ insert_node['data-wovnio'] = data_wovnio
206
+ insert_node['data-wovnio-type'] = 'fallback_snippet' if adds_backend_error_mark
207
+ # do this so that there will be a closing tag (better compatibility with browsers)
208
+ insert_node.content = ''
209
+
210
+ if parent_node.children.empty?
211
+ parent_node.add_child(insert_node)
212
+ else
213
+ parent_node.children.first.add_previous_sibling(insert_node)
214
+ end
215
+ end
216
+
217
+ def data_wovnio
218
+ token = @store.settings['project_token']
219
+ current_lang = @headers.lang_code
220
+ default_lang = @store.settings['default_lang']
221
+ url_pattern = @store.settings['url_pattern']
222
+ lang_code_aliases_json = JSON.generate(@store.settings['custom_lang_aliases'])
223
+ lang_param_name = @store.settings['lang_param_name']
224
+ custom_domain_langs = @store.custom_domain_langs.to_html_swapper_hash
225
+
226
+ result = [
227
+ "key=#{token}",
228
+ 'backend=true',
229
+ "currentLang=#{current_lang}",
230
+ "defaultLang=#{default_lang}",
231
+ "urlPattern=#{url_pattern}",
232
+ "langCodeAliases=#{lang_code_aliases_json}",
233
+ "langParamName=#{lang_param_name}",
234
+ "version=WOVN.rb_#{VERSION}"
235
+ ]
236
+ result << "customDomainLangs=#{JSON.generate(custom_domain_langs)}" unless custom_domain_langs.empty?
237
+ result.join('&')
238
+ end
239
+ end
240
+ end
@@ -1,48 +1,48 @@
1
- module Wovnrb
2
- class HtmlReplaceMarker
3
- KEY_PREFIX = '__wovn-backend-ignored-key-'.freeze
4
-
5
- def initialize
6
- @current_key_number = 0
7
- @mapped_values = []
8
- end
9
-
10
- # Add argument's value to mapping information with comment style key
11
- def add_comment_value(value)
12
- key = "<!-- #{generate_key} -->"
13
- @mapped_values << [key, value]
14
-
15
- key
16
- end
17
-
18
- def add_value(value)
19
- key = generate_key
20
- @mapped_values << [key, value]
21
-
22
- key
23
- end
24
-
25
- def revert(marked_html)
26
- i = @mapped_values.size
27
- while i > 0
28
- i -= 1
29
- key, value = @mapped_values[i]
30
- marked_html = marked_html.sub(key, value)
31
- end
32
- marked_html
33
- end
34
-
35
- def keys
36
- @mapped_values.map { |v| v[0] }
37
- end
38
-
39
- private
40
-
41
- def generate_key
42
- next_key = "#{KEY_PREFIX}#{@current_key_number}"
43
- @current_key_number += 1
44
-
45
- next_key
46
- end
47
- end
48
- end
1
+ module Wovnrb
2
+ class HtmlReplaceMarker
3
+ KEY_PREFIX = '__wovn-backend-ignored-key-'.freeze
4
+
5
+ def initialize
6
+ @current_key_number = 0
7
+ @mapped_values = []
8
+ end
9
+
10
+ # Add argument's value to mapping information with comment style key
11
+ def add_comment_value(value)
12
+ key = "<!-- #{generate_key} -->"
13
+ @mapped_values << [key, value]
14
+
15
+ key
16
+ end
17
+
18
+ def add_value(value)
19
+ key = generate_key
20
+ @mapped_values << [key, value]
21
+
22
+ key
23
+ end
24
+
25
+ def revert(marked_html)
26
+ i = @mapped_values.size
27
+ while i > 0
28
+ i -= 1
29
+ key, value = @mapped_values[i]
30
+ marked_html = marked_html.sub(key, value)
31
+ end
32
+ marked_html
33
+ end
34
+
35
+ def keys
36
+ @mapped_values.map { |v| v[0] }
37
+ end
38
+
39
+ private
40
+
41
+ def generate_key
42
+ next_key = "#{KEY_PREFIX}#{@current_key_number}"
43
+ @current_key_number += 1
44
+
45
+ next_key
46
+ end
47
+ end
48
+ end
@@ -1,7 +1,7 @@
1
1
  module Wovnrb
2
2
  class Settings < Hash
3
3
  def initialize(*args, **kwargs)
4
- super(*args, **kwargs)
4
+ super
5
5
  @dynamic_settings = {}
6
6
  end
7
7
 
@@ -9,7 +9,7 @@ module Wovnrb
9
9
  return @dynamic_settings[key] if @dynamic_settings.key?(key)
10
10
  return IgnoreGlobsWrapper.new(ignore_globs) if key == 'ignore_globs'
11
11
 
12
- super(key)
12
+ super
13
13
  end
14
14
 
15
15
  def ignore_globs