wovnrb 3.8.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,212 +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
-
19
- [converted_html, marker]
20
- end
21
-
22
- private
23
-
24
- def html
25
- # Ensure a Content-Type declaration in the header. This mimics Nokogumbo
26
- # 1.5.0 default serialization behavior.
27
- @dom.meta_encoding = 'UTF-8' if @dom.respond_to?(:meta_encoding=)
28
-
29
- @dom.to_html(save_with: 0).strip
30
- end
31
-
32
- def transform_html
33
- replace_snippet
34
- replace_hreflangs
35
- inject_lang_html_tag
36
- translate_canonical_tag if @store.settings['translate_canonical_tag']
37
- end
38
-
39
- def replace_snippet
40
- strip_snippet
41
- insert_snippet
42
- end
43
-
44
- def replace_dom(marker)
45
- strip_snippet
46
- strip_hreflangs if add_hreflang
47
-
48
- @dom.traverse { |node| transform_node(node, marker) }
49
-
50
- insert_snippet(adds_backend_error_mark: true)
51
- insert_hreflang_tags
52
- inject_lang_html_tag
53
- translate_canonical_tag if @store.settings['translate_canonical_tag']
54
-
55
- html
56
- end
57
-
58
- def transform_node(node, marker)
59
- strip_wovn_ignore(node, marker)
60
- strip_custom_ignore(node, marker)
61
- strip_form(node, marker)
62
- strip_script(node, marker)
63
- end
64
-
65
- def strip_script(node, marker)
66
- put_replace_marker(node, marker) if node.name.casecmp('script').zero?
67
- end
68
-
69
- def strip_form(node, marker)
70
- if node.name.casecmp('form').zero?
71
- put_replace_marker(node, marker)
72
- return
73
- end
74
-
75
- if node.name.casecmp('input').zero? && node.get_attribute('type') == 'hidden'
76
- original_text = node.get_attribute('value')
77
- return if original_text.nil?
78
- return if original_text.include?(HtmlReplaceMarker::KEY_PREFIX)
79
-
80
- node.set_attribute('value', marker.add_value(original_text))
81
- end
82
- end
83
-
84
- def strip_custom_ignore(node, marker)
85
- classes = node.get_attribute('class')
86
- return unless classes.present?
87
-
88
- ignored_classes = @store.settings['ignore_class']
89
- should_be_ignored = (ignored_classes & classes.split).present?
90
-
91
- put_replace_marker(node, marker) if should_be_ignored
92
- end
93
-
94
- def strip_wovn_ignore(node, marker)
95
- put_replace_marker(node, marker) if node && (node.get_attribute('wovn-ignore') || node.get_attribute('data-wovn-ignore'))
96
- end
97
-
98
- def put_replace_marker(node, marker)
99
- original_text = node.inner_html
100
- return if original_text.include?(HtmlReplaceMarker::KEY_PREFIX)
101
-
102
- node.inner_html = marker.add_comment_value(original_text)
103
- end
104
-
105
- def strip_hreflangs
106
- supported_langs = @store.supported_langs
107
- @dom.xpath('//link') do |node|
108
- node.remove if node['hreflang'] && supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
109
- end
110
- end
111
-
112
- def add_hreflang
113
- !!(@store && @headers)
114
- end
115
-
116
- def inject_lang_html_tag
117
- root = @dom.at_css('html')
118
- return unless root
119
- return if root['lang']
120
-
121
- root['lang'] = @store.default_lang
122
- end
123
-
124
- def replace_hreflangs
125
- strip_hreflang_tags
126
- insert_hreflang_tags
127
- end
128
-
129
- def strip_hreflang_tags
130
- @dom.xpath('//link').each do |node|
131
- node.remove if node['hreflang'] && @store.supported_langs.include?(Lang.iso_639_1_normalization(node['hreflang']))
132
- end
133
- end
134
-
135
- def insert_hreflang_tags
136
- parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
137
- return unless parent_node
138
-
139
- @store.supported_langs.each do |lang_code|
140
- insert_node = Nokogiri::XML::Node.new('link', @dom)
141
- insert_node['rel'] = 'alternate'
142
- insert_node['hreflang'] = Lang.iso_639_1_normalization(lang_code)
143
- insert_node['href'] = @headers.redirect_location(lang_code)
144
-
145
- parent_node.add_child(insert_node.to_s)
146
- end
147
- end
148
-
149
- def translate_canonical_tag
150
- canonical_node = @dom.at_css('link[rel="canonical"]')
151
- return unless canonical_node
152
-
153
- lang_code = @headers.lang_code
154
- return if lang_code == @store.settings['default_lang'] && @store.settings['custom_lang_aliases'][lang_code].nil?
155
-
156
- canonical_url = canonical_node['href']
157
-
158
- translated_canonical_url = @url_lang_switcher.add_lang_code(canonical_url, lang_code, @headers)
159
- canonical_node['href'] = translated_canonical_url
160
- end
161
-
162
- # Remove wovn snippet code from dom
163
- def strip_snippet
164
- @dom.xpath('//script').each do |script_node|
165
- script_node.remove if (script_node['src'] && widget_urls.any? { |url| script_node['src'].include? url }) || script_node['data-wovnio'].present?
166
- end
167
- end
168
-
169
- def widget_urls
170
- ["#{@store.settings['api_url']}/widget", 'j.wovn.io', 'j.dev-wovn.io:3000']
171
- end
172
-
173
- def insert_snippet(adds_backend_error_mark: true)
174
- parent_node = @dom.at_css('head') || @dom.at_css('body') || @dom.at_css('html')
175
- return unless parent_node
176
-
177
- insert_node = Nokogiri::XML::Node.new('script', @dom)
178
- insert_node['src'] = @store.widget_url
179
- insert_node['async'] = true
180
- insert_node['data-wovnio'] = data_wovnio
181
- insert_node['data-wovnio-type'] = 'fallback_snippet' if adds_backend_error_mark
182
- # do this so that there will be a closing tag (better compatibility with browsers)
183
- insert_node.content = ''
184
-
185
- if parent_node.children.empty?
186
- parent_node.add_child(insert_node)
187
- else
188
- parent_node.children.first.add_previous_sibling(insert_node)
189
- end
190
- end
191
-
192
- def data_wovnio
193
- token = @store.settings['project_token']
194
- current_lang = @headers.lang_code
195
- default_lang = @store.settings['default_lang']
196
- url_pattern = @store.settings['url_pattern']
197
- lang_code_aliases_json = JSON.generate(@store.settings['custom_lang_aliases'])
198
- lang_param_name = @store.settings['lang_param_name']
199
-
200
- [
201
- "key=#{token}",
202
- 'backend=true',
203
- "currentLang=#{current_lang}",
204
- "defaultLang=#{default_lang}",
205
- "urlPattern=#{url_pattern}",
206
- "langCodeAliases=#{lang_code_aliases_json}",
207
- "langParamName=#{lang_param_name}",
208
- "version=WOVN.rb_#{VERSION}"
209
- ].join('&')
210
- end
211
- end
212
- 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,44 +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
- private
36
-
37
- def generate_key
38
- next_key = "#{KEY_PREFIX}#{@current_key_number}"
39
- @current_key_number += 1
40
-
41
- next_key
42
- end
43
- end
44
- 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