wovnrb 3.9.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,226 +1,226 @@
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
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 add_hreflang
58
-
59
- @dom.traverse { |node| transform_node(node, marker) }
60
-
61
- insert_snippet(adds_backend_error_mark: true)
62
- insert_hreflang_tags
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 add_hreflang
124
- !!(@store && @headers)
125
- end
126
-
127
- def inject_lang_html_tag
128
- root = @dom.at_css('html')
129
- return unless root
130
- return if root['lang']
131
-
132
- root['lang'] = @store.default_lang
133
- end
134
-
135
- def replace_hreflangs
136
- strip_hreflang_tags
137
- insert_hreflang_tags
138
- end
139
-
140
- def strip_hreflang_tags
141
- @dom.xpath('//link').each do |node|
142
- node.remove if node['hreflang'] && @store.supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
143
- end
144
- end
145
-
146
- def insert_hreflang_tags
147
- parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
148
- return unless parent_node
149
-
150
- @store.supported_langs.each do |lang_code|
151
- insert_node = Nokogiri::XML::Node.new('link', @dom)
152
- insert_node['rel'] = 'alternate'
153
- insert_node['hreflang'] = Lang.iso_639_1_normalization(lang_code)
154
- insert_node['href'] = @headers.redirect_location(lang_code)
155
-
156
- parent_node.add_child(insert_node.to_s)
157
- end
158
- end
159
-
160
- def translate_canonical_tag
161
- canonical_node = @dom.at_css('link[rel="canonical"]')
162
- return unless canonical_node
163
-
164
- lang_code = @headers.lang_code
165
- return if lang_code == @store.settings['default_lang'] && @store.settings['custom_lang_aliases'][lang_code].nil?
166
-
167
- canonical_url = canonical_node['href']
168
-
169
- translated_canonical_url = @url_lang_switcher.add_lang_code(canonical_url, lang_code, @headers)
170
- canonical_node['href'] = translated_canonical_url
171
- end
172
-
173
- # Remove wovn snippet code from dom
174
- def strip_snippet
175
- @dom.xpath('//script').each do |script_node|
176
- script_node.remove if (script_node['src'] && widget_urls.any? { |url| script_node['src'].include? url }) || script_node['data-wovnio'].present?
177
- end
178
- end
179
-
180
- def widget_urls
181
- ["#{@store.settings['api_url']}/widget", 'j.wovn.io', 'j.dev-wovn.io:3000']
182
- end
183
-
184
- def insert_snippet(adds_backend_error_mark: true)
185
- parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
186
- return unless parent_node
187
-
188
- insert_node = Nokogiri::XML::Node.new('script', @dom)
189
- insert_node['src'] = @store.widget_url
190
- insert_node['async'] = true
191
- insert_node['data-wovnio'] = data_wovnio
192
- insert_node['data-wovnio-type'] = 'fallback_snippet' if adds_backend_error_mark
193
- # do this so that there will be a closing tag (better compatibility with browsers)
194
- insert_node.content = ''
195
-
196
- if parent_node.children.empty?
197
- parent_node.add_child(insert_node)
198
- else
199
- parent_node.children.first.add_previous_sibling(insert_node)
200
- end
201
- end
202
-
203
- def data_wovnio
204
- token = @store.settings['project_token']
205
- current_lang = @headers.lang_code
206
- default_lang = @store.settings['default_lang']
207
- url_pattern = @store.settings['url_pattern']
208
- lang_code_aliases_json = JSON.generate(@store.settings['custom_lang_aliases'])
209
- lang_param_name = @store.settings['lang_param_name']
210
- custom_domain_langs = @store.custom_domain_langs.to_html_swapper_hash
211
-
212
- result = [
213
- "key=#{token}",
214
- 'backend=true',
215
- "currentLang=#{current_lang}",
216
- "defaultLang=#{default_lang}",
217
- "urlPattern=#{url_pattern}",
218
- "langCodeAliases=#{lang_code_aliases_json}",
219
- "langParamName=#{lang_param_name}",
220
- "version=WOVN.rb_#{VERSION}"
221
- ]
222
- result << "customDomainLangs=#{JSON.generate(custom_domain_langs)}" unless custom_domain_langs.empty?
223
- result.join('&')
224
- end
225
- end
226
- 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
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 add_hreflang
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 add_hreflang
124
+ !!(@store && @headers)
125
+ end
126
+
127
+ def inject_lang_html_tag
128
+ root = @dom.at_css('html')
129
+ return unless root
130
+ return if root['lang']
131
+
132
+ root['lang'] = @store.default_lang
133
+ end
134
+
135
+ def replace_hreflangs
136
+ strip_hreflang_tags
137
+ insert_hreflang_tags
138
+ end
139
+
140
+ def strip_hreflang_tags
141
+ @dom.xpath('//link').each do |node|
142
+ node.remove if node['hreflang'] && @store.supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
143
+ end
144
+ end
145
+
146
+ def insert_hreflang_tags
147
+ parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
148
+ return unless parent_node
149
+
150
+ @store.supported_langs.each do |lang_code|
151
+ insert_node = Nokogiri::XML::Node.new('link', @dom)
152
+ insert_node['rel'] = 'alternate'
153
+ insert_node['hreflang'] = Lang.iso_639_1_normalization(lang_code)
154
+ insert_node['href'] = @headers.redirect_location(lang_code)
155
+
156
+ parent_node.add_child(insert_node.to_s)
157
+ end
158
+ end
159
+
160
+ def translate_canonical_tag
161
+ canonical_node = @dom.at_css('link[rel="canonical"]')
162
+ return unless canonical_node
163
+
164
+ lang_code = @headers.lang_code
165
+ return if lang_code == @store.settings['default_lang'] && @store.settings['custom_lang_aliases'][lang_code].nil?
166
+
167
+ canonical_url = canonical_node['href']
168
+
169
+ translated_canonical_url = @url_lang_switcher.add_lang_code(canonical_url, lang_code, @headers)
170
+ canonical_node['href'] = translated_canonical_url
171
+ end
172
+
173
+ # Remove wovn snippet code from dom
174
+ def strip_snippet
175
+ @dom.xpath('//script').each do |script_node|
176
+ script_node.remove if (script_node['src'] && widget_urls.any? { |url| script_node['src'].include? url }) || script_node['data-wovnio'].present?
177
+ end
178
+ end
179
+
180
+ def widget_urls
181
+ ["#{@store.settings['api_url']}/widget", 'j.wovn.io', 'j.dev-wovn.io:3000']
182
+ end
183
+
184
+ def insert_snippet(adds_backend_error_mark: true)
185
+ parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
186
+ return unless parent_node
187
+
188
+ insert_node = Nokogiri::XML::Node.new('script', @dom)
189
+ insert_node['src'] = @store.widget_url
190
+ insert_node['async'] = true
191
+ insert_node['data-wovnio'] = data_wovnio
192
+ insert_node['data-wovnio-type'] = 'fallback_snippet' if adds_backend_error_mark
193
+ # do this so that there will be a closing tag (better compatibility with browsers)
194
+ insert_node.content = ''
195
+
196
+ if parent_node.children.empty?
197
+ parent_node.add_child(insert_node)
198
+ else
199
+ parent_node.children.first.add_previous_sibling(insert_node)
200
+ end
201
+ end
202
+
203
+ def data_wovnio
204
+ token = @store.settings['project_token']
205
+ current_lang = @headers.lang_code
206
+ default_lang = @store.settings['default_lang']
207
+ url_pattern = @store.settings['url_pattern']
208
+ lang_code_aliases_json = JSON.generate(@store.settings['custom_lang_aliases'])
209
+ lang_param_name = @store.settings['lang_param_name']
210
+ custom_domain_langs = @store.custom_domain_langs.to_html_swapper_hash
211
+
212
+ result = [
213
+ "key=#{token}",
214
+ 'backend=true',
215
+ "currentLang=#{current_lang}",
216
+ "defaultLang=#{default_lang}",
217
+ "urlPattern=#{url_pattern}",
218
+ "langCodeAliases=#{lang_code_aliases_json}",
219
+ "langParamName=#{lang_param_name}",
220
+ "version=WOVN.rb_#{VERSION}"
221
+ ]
222
+ result << "customDomainLangs=#{JSON.generate(custom_domain_langs)}" unless custom_domain_langs.empty?
223
+ result.join('&')
224
+ end
225
+ end
226
+ 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