premailer 1.12.0 → 1.29.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d3e8552d269ddef67b722b192f58c2afe242f6a824a07e4e7fd4e5bbc3f57f2
4
- data.tar.gz: 6c6b611237ffe68b655654c75c21c7a8f17bc73ca9502e06891f26ab5b1bb583
3
+ metadata.gz: fa4f38aaadca592e45c38537b54ad6e88bd15c0c43c07efed0beb25af8194766
4
+ data.tar.gz: cde54fad4ac9397bd09b88509aa2b5a22daaa1721172a7c98e0e78c94e8707ad
5
5
  SHA512:
6
- metadata.gz: 1c963172a6abcb9e71824c07e54a80ea89c58fda395349d68ea0567a5fa06cf160663bd1e8a169a81532f5ad3b7c97d8ed0ad2c09a4182c3d281982a878348da
7
- data.tar.gz: 758dae24fbd064a1ee44fe4ab6f0537c191826474dc045a8af5354f28a17e4d20e8a56e93d2ea3ebab83ab4c9c9b2376aa0b53467ff50338773e78e815b8ccbe
6
+ metadata.gz: bc0358601f237a6ec2cf74764dfb28e5d800cf1ca486d88ca44a7e4d3913ade8ff00b76b98017fa6f565f5812bd6f9c3e66be819708a2764a70fca051458f09f
7
+ data.tar.gz: 9ea81979f54bd5fd9d8edb2e390e5900d53b7462064bc1465999dba639218db5a479c74e93e6e179d6d6a51abbf066685a21b033d5003899ab037a021db385f7
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # Premailer README [![Build Status](https://travis-ci.org/premailer/premailer.png?branch=master)](https://travis-ci.org/premailer/premailer) [![Gem Version](https://badge.fury.io/rb/premailer.svg)](https://badge.fury.io/rb/premailer)
1
+ # Premailer README [![CI](https://github.com/premailer/premailer/actions/workflows/actions.yml/badge.svg)](https://github.com/premailer/premailer/actions/workflows/actions.yml) [![Gem Version](https://badge.fury.io/rb/premailer.svg)](https://badge.fury.io/rb/premailer)
2
2
 
3
3
  ## What is this?
4
4
 
5
5
  For the best HTML e-mail delivery results, CSS should be inline. This is a
6
6
  huge pain and a simple newsletter becomes un-managable very quickly. This
7
- script is my solution.
7
+ gem is a solution.
8
8
 
9
9
  * CSS styles are converted to inline style attributes
10
10
  - Checks `style` and `link[rel=stylesheet]` tags and preserves existing inline attributes
@@ -12,35 +12,26 @@ script is my solution.
12
12
  - Checks links in `href`, `src` and CSS `url('')`
13
13
  * CSS properties are checked against e-mail client capabilities
14
14
  - Based on the Email Standards Project's guides
15
- * A [plain text version](https://premailer.github.io/premailer/HtmlToPlainText.html) is created (optional)
15
+ * A [plain text version](#plain-text-version) is created (optional)
16
16
 
17
17
  ## Installation
18
18
 
19
- Install the Premailer gem from RubyGems.
20
-
21
19
  ```bash
22
20
  gem install premailer
23
21
  ```
24
22
 
25
- or add it to your `Gemfile` and run `bundle`.
26
-
27
23
  ## Example
28
24
 
29
25
  ```ruby
30
26
  require 'premailer'
31
27
 
32
- premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)
28
+ premailer = Premailer.new('http://example.com/myfile.html', warn_level: Premailer::Warnings::SAFE)
33
29
 
34
- # Write the plain-text output
35
- # This must come before to_inline_css (https://github.com/premailer/premailer/issues/201)
36
- File.open("output.txt", "w") do |fout|
37
- fout.puts premailer.to_plain_text
38
- end
30
+ # Write the plain-text output (must come before to_inline_css)
31
+ File.write "output.txt", premailer.to_plain_text
39
32
 
40
33
  # Write the HTML output
41
- File.open("output.html", "w") do |fout|
42
- fout.puts premailer.to_inline_css
43
- end
34
+ File.write "output.html", premailer.to_inline_css
44
35
 
45
36
  # Output any CSS warnings
46
37
  premailer.warnings.each do |w|
@@ -50,17 +41,13 @@ end
50
41
 
51
42
  ## Adapters
52
43
 
53
- Premailer's default adapter is nokogiri if both nokogiri and nokogumbo are included in the Gemfile list. However, if you want to use a different adapter, you can choose to.
54
-
55
- There are three adapters in total (as of premailer 1.10.0)
56
-
57
44
  1. nokogiri (default)
58
- 2. nokogiri_fast
45
+ 2. nokogiri_fast (20x speed, more memory)
59
46
  3. nokogumbo
60
47
 
61
- hpricot adapter removed due to its EOL, please use `~>1.9.0` version if You still need it..
48
+ (hpricot adapter removed, use `~>1.9.0` version if you need it)
62
49
 
63
- `NokogiriFast` adapter improves the Algorithmic complexity of the running time by 20x with a slight compensation on memory. To switch to any of these adapters, add the following line. For example, if you want to include the `NokogiriFast` adapter,
50
+ Picking an adapter:
64
51
 
65
52
  ```ruby
66
53
  Premailer::Adapter.use = :nokogiri_fast
@@ -68,7 +55,7 @@ Premailer::Adapter.use = :nokogiri_fast
68
55
 
69
56
  ## Ruby Compatibility
70
57
 
71
- Premailer is tested on Ruby 2.1 and above. JRuby support is close; contributors are welcome. Checkout the latest build status on the [Travis CI dashboard](https://travis-ci.org/#!/premailer/premailer).
58
+ See .github/workflows/actions.yml for which ruby versions are tested. JRuby support is close, contributors are welcome.
72
59
 
73
60
  ## Premailer-specific CSS
74
61
 
@@ -97,9 +84,125 @@ will result in
97
84
  <table cellspacing='5' width='500'>
98
85
  ```
99
86
 
87
+ ## Plain text version
88
+
89
+ Premailer can generate a plain text version of your HTML. Links and images will be inlined.
90
+
91
+ For example
92
+
93
+ ```html
94
+ <a href="https://example.com" >
95
+ <img src="https://github.com/premailer.png" alt="Premailer Logo" />
96
+ </a>
97
+ ```
98
+
99
+ will become
100
+
101
+ ```text
102
+ Premailer Logo ( https://example.com )
103
+ ```
104
+
105
+ To ignore/omit a section of HTML content from the plain text version, wrap it with the following comments.
106
+
107
+ ```html
108
+ <!-- start text/html -->
109
+ <p>This will be omitted from the plain text version.</p>
110
+ <p>
111
+ This is extremely helpful for <strong>removing email headers and footers</strong>
112
+ that aren't needed in the text version.
113
+ </p>
114
+ <!-- end text/html -->
115
+ ```
116
+
117
+ ## Configuration options
118
+
119
+ For example:
120
+ ```ruby
121
+ Premailer.new(
122
+ html, # html as string
123
+ with_html_string: true,
124
+ drop_unmergeable_css_rules: true
125
+ )
126
+ ```
127
+
128
+ [available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method)
129
+
130
+ ## Support for CSS variables
131
+
132
+ The gem does not automatically replace CSS variables with their static values.
133
+
134
+ For example, if a variable is used to set the `font-weight` of an `h1` element, the result will be
135
+ ```html
136
+ <h1 style="
137
+ font-size:3rem;
138
+ font-weight:var(--bulma-content-heading-weight);">
139
+ Title</h1>
140
+ ```
141
+
142
+ This causes the `font-weight` value to be the CSS variable call `var(--bulma-content-heading-weight);` instead of its static value.
143
+
144
+ ### Replace CSS variable calls with their static values
145
+
146
+ The following section instructs how to replace CSS variables with their static value in the context of a Ruby on Rails application.
147
+
148
+ Install the `postcss-css-variables` plugin for PostCSS to process the CSS variables.
149
+
150
+ ```shell
151
+ yarn add postcss postcss-cli postcss-css-variables
152
+ ```
153
+
154
+ To configure the plugin, create the file `postcss.config.js` in the root directory with the content:
155
+
156
+ ```javascript
157
+ module.exports = {
158
+ plugins: [
159
+ // https://github.com/MadLittleMods/postcss-css-variables to transform the css
160
+ require("postcss-css-variables")({
161
+ preserve: false, // Set to false to replace variables with static values
162
+ }),
163
+ ],
164
+ };
165
+ ```
166
+
167
+ In the `package.json` file, add the new "build:emails" to the scripts.<br>Replace `./app/assets/stylesheets/emails.css` with your file path:
168
+ ```json
169
+ "scripts": {
170
+ "build:emails": "postcss ./app/assets/stylesheets/emails.css -o ./app/assets/builds/emails.css"
171
+ }
172
+ ```
173
+
174
+ The previous script processes and overwrites the file at `./app/assets/stylesheets/emails.css` with PostCSS using its `postcss-css-variables` plugin, replacing the CSS variables with their static value.
175
+
176
+ If the file to be processed is not `.css`, but `.scss`, it needs to be converted first to `.css`, then have its variables replaced. The script would then be
177
+
178
+ ```json
179
+ "scripts": {
180
+ "build:emails": "sass ./app/assets/stylesheets/emails.scss:./app/assets/builds/emails.css --no-source-map --load-path=node_modules && postcss ./app/assets/builds/emails.css -o ./app/assets/builds/emails.css"
181
+ }
182
+ ```
183
+
184
+ Next, to execute the script when running `bin/dev`, add the following line in the file `Procfile.dev`
185
+
186
+ ```
187
+ emails_css: yarn build:emails --watch
188
+ ```
189
+
190
+ The srcipt can also be executed separately with the command
191
+
192
+ ```shell
193
+ yarn build:emails
194
+ ```
195
+
196
+ ### Caveat
197
+
198
+ The variables must be declared before use. Otherwise, their values when called will be set to `undefined`.
199
+
100
200
  ## Contributions
101
201
 
102
- Contributions are most welcome. Premailer was rotting away in a private SVN repository for too long and could use some TLC. Fork and patch to your heart's content. Please don't increment the version numbers, though.
202
+ Contributions are most welcome.
203
+ Premailer was rotting away in a private SVN repository for too long and could use some TLC.
204
+ Fork and patch to your heart's content.
205
+ Please don't increment the version numbers.
103
206
 
104
207
  A few areas that are particularly in need of love:
105
208
 
@@ -116,4 +219,3 @@ and to [Campaign Monitor](https://www.campaignmonitor.com/) for supporting the w
116
219
  The source code can be found on [GitHub](https://github.com/premailer/premailer).
117
220
 
118
221
  Copyright by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2007-2017. See [LICENSE.md](https://github.com/premailer/premailer/blob/master/LICENSE.md) for license details.
119
-
data/bin/premailer CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # This binary used in rubygems environment only as part of installed gem
4
5
 
5
6
  require 'premailer/executor'
6
-
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri'
2
3
 
3
4
  class Premailer
4
5
  module Adapter
5
6
  # Nokogiri adapter
6
7
  module Nokogiri
8
+ WIDTH_AND_HIGHT = ['width', 'height'].freeze
7
9
 
8
10
  include AdapterHelper::RgbToHex
9
11
  # Merge CSS into the HTML document.
@@ -21,19 +23,19 @@ class Premailer
21
23
  # Iterate through the rules and merge them into the HTML
22
24
  @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
23
25
  # Save un-mergable rules separately
24
- selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
26
+ selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
25
27
 
26
28
  # Convert element names to lower case
27
- selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
29
+ selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
28
30
 
29
- if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
30
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
31
+ if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
32
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
31
33
  else
32
34
  begin
33
- if selector =~ Premailer::RE_RESET_SELECTORS
35
+ if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
34
36
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
35
37
  # however, this doesn't mean for testing pur
36
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
38
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
37
39
  end
38
40
 
39
41
  # Change single ID CSS selectors into xpath so that we can match more
@@ -41,52 +43,71 @@ class Premailer
41
43
  selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
42
44
 
43
45
  doc.search(selector).each do |el|
44
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
46
+ if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
45
47
  # Add a style attribute or append to the existing one
46
48
  block = "[SPEC=#{specificity}[#{declaration}]]"
47
49
  el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
48
50
  end
49
51
  end
50
52
  rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
51
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
53
+ warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
52
54
  next
53
55
  end
54
56
  end
55
57
  end
56
58
 
57
59
  # Remove script tags
58
- if @options[:remove_scripts]
59
- doc.search("script").remove
60
- end
60
+ doc.search("script").remove if @options[:remove_scripts]
61
61
 
62
62
  # Read STYLE attributes and perform folding
63
63
  doc.search("*[@style]").each do |el|
64
64
  style = el.attributes['style'].to_s
65
65
 
66
- declarations = []
67
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
68
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
69
- declarations << rs
66
+ declarations = style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).filter_map do |declaration|
67
+ rs = Premailer::CachedRuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
68
+ rs.expand_shorthand!
69
+ rs
70
+ rescue ArgumentError => e
71
+ raise e if @options[:rule_set_exceptions]
70
72
  end
71
73
 
72
74
  # Perform style folding
73
75
  merged = CssParser.merge(declarations)
74
- merged.expand_shorthand!
76
+ begin
77
+ merged.expand_shorthand!
78
+ rescue ArgumentError => e
79
+ raise e if @options[:rule_set_exceptions]
80
+ end
75
81
 
76
82
  # Duplicate CSS attributes as HTML attributes
77
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
78
- Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
79
- if el[html_att].nil? and not merged[css_att].empty?
80
- new_html_att = merged[css_att].gsub(/url\(['"](.*)['"]\)/, '\1').gsub(/;$|\s*!important/, '').strip
81
- el[html_att] = css_att.end_with?('color') && @options[:rgb_to_hex_attributes] ? ensure_hex(new_html_att) : new_html_att
83
+ if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
84
+ Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
85
+ if el[html_attr].nil? && !merged[css_attr].empty?
86
+ new_val = merged[css_attr].dup
87
+
88
+ # Remove url() function wrapper
89
+ new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2')
90
+
91
+ # Remove !important, trailing semi-colon, and leading/trailing whitespace
92
+ new_val.gsub!(/;$|\s*!important/, '').strip!
93
+
94
+ # For width and height tags, remove px units
95
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HIGHT.include?(html_attr)
96
+
97
+ # For color-related tags, convert RGB to hex if specified by options
98
+ new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
99
+
100
+ el[html_attr] = new_val
82
101
  end
102
+
83
103
  unless @options[:preserve_style_attribute]
84
- merged.instance_variable_get("@declarations").tap do |declarations|
85
- declarations.delete(css_att)
104
+ merged.instance_variable_get(:@declarations).tap do |declarations|
105
+ declarations.delete(css_attr)
86
106
  end
87
107
  end
88
108
  end
89
109
  end
110
+
90
111
  # Collapse multiple rules into one as much as possible.
91
112
  merged.create_shorthand! if @options[:create_shorthands]
92
113
 
@@ -96,9 +117,9 @@ class Premailer
96
117
 
97
118
  doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
98
119
 
99
- if @options[:remove_classes] or @options[:remove_comments]
120
+ if @options[:remove_classes] || @options[:remove_comments]
100
121
  doc.traverse do |el|
101
- if el.comment? and @options[:remove_comments]
122
+ if el.comment? && @options[:remove_comments]
102
123
  el.remove
103
124
  elsif el.element?
104
125
  el.remove_attribute('class') if @options[:remove_classes]
@@ -110,15 +131,15 @@ class Premailer
110
131
  # find all anchor's targets and hash them
111
132
  targets = []
112
133
  doc.search("a[@href^='#']").each do |el|
113
- target = el.get_attribute('href')[1..-1]
134
+ target = el.get_attribute('href')[1..]
114
135
  targets << target
115
- el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
136
+ el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
116
137
  end
117
138
  # hash ids that are links target, delete others
118
139
  doc.search("*[@id]").each do |el|
119
140
  id = el.get_attribute('id')
120
141
  if targets.include?(id)
121
- el.set_attribute('id', Digest::MD5.hexdigest(id))
142
+ el.set_attribute('id', Digest::SHA256.hexdigest(id))
122
143
  else
123
144
  el.remove_attribute('id')
124
145
  end
@@ -132,7 +153,7 @@ class Premailer
132
153
  end
133
154
 
134
155
  @processed_doc = doc
135
- if is_xhtml?
156
+ if xhtml?
136
157
  # we don't want to encode carriage returns
137
158
  @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
138
159
  else
@@ -154,17 +175,16 @@ class Premailer
154
175
  style_tag.content = styles
155
176
  doc.add_child(style_tag)
156
177
  else
157
- style_tag = doc.create_element "style", "#{styles}"
178
+ style_tag = doc.create_element "style", styles.to_s
158
179
  head = doc.at_css('head')
159
- head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child
160
- head ||= doc.add_child(doc.create_element "head")
180
+ head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element("head")) if doc.root&.first_element_child
181
+ head ||= doc.add_child(doc.create_element("head"))
161
182
  head << style_tag
162
183
  end
163
184
  end
164
185
  doc
165
186
  end
166
187
 
167
-
168
188
  # Converts the HTML document to a format suitable for plain-text e-mail.
169
189
  #
170
190
  # If present, uses the <body> element as its base; otherwise uses the whole document.
@@ -174,17 +194,17 @@ class Premailer
174
194
  html_src = ''
175
195
  begin
176
196
  html_src = @doc.at("body").inner_html
177
- rescue;
197
+ rescue StandardError
178
198
  end
179
199
 
180
- html_src = @doc.to_html unless html_src and not html_src.empty?
200
+ html_src = @doc.to_html unless html_src && !html_src.empty?
181
201
  convert_to_text(html_src, @options[:line_length], @html_encoding)
182
202
  end
183
203
 
184
204
  # Gets the original HTML as a string.
185
205
  # @return [String] HTML.
186
206
  def to_s
187
- if is_xhtml?
207
+ if xhtml?
188
208
  @doc.to_xhtml(:encoding => nil)
189
209
  else
190
210
  @doc.to_html(:encoding => nil)
@@ -198,13 +218,13 @@ class Premailer
198
218
  thing = nil
199
219
 
200
220
  # TODO: duplicate options
201
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
221
+ if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
202
222
  thing = input
203
223
  elsif @is_local_file
204
224
  @base_dir = File.dirname(input)
205
225
  thing = File.open(input, 'r')
206
226
  else
207
- thing = open(input)
227
+ thing = URI.parse(input).open
208
228
  end
209
229
 
210
230
  if thing.respond_to?(:read)
@@ -215,37 +235,30 @@ class Premailer
215
235
  doc = nil
216
236
 
217
237
  # Handle HTML entities
218
- if @options[:replace_html_entities] == true and thing.is_a?(String)
238
+ if (@options[:replace_html_entities] == true) && thing.is_a?(String)
239
+ thing = +thing
219
240
  HTML_ENTITIES.map do |entity, replacement|
220
241
  thing.gsub! entity, replacement
221
242
  end
222
243
  end
223
- # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
224
- # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
225
- encoding = if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
226
- thing = thing.force_encoding(@options[:input_encoding]).encode!
227
- @options[:input_encoding]
228
- else
229
- @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
230
- end
244
+ encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
231
245
  doc = if @options[:html_fragment]
232
246
  ::Nokogiri::HTML.fragment(thing, encoding)
233
247
  else
234
- ::Nokogiri::HTML(thing, nil, encoding) { |c| c.recover }
248
+ ::Nokogiri::HTML(thing, nil, encoding, &:recover)
235
249
  end
236
250
 
237
251
  # Fix for removing any CDATA tags from both style and script tags inserted per
238
252
  # https://github.com/sparklemotion/nokogiri/issues/311 and
239
253
  # https://github.com/premailer/premailer/issues/199
240
- %w(style script).each do |tag|
254
+ ['style', 'script'].each do |tag|
241
255
  doc.search(tag).children.each do |child|
242
- child.swap(child.text()) if child.cdata?
256
+ child.swap(child.text) if child.cdata?
243
257
  end
244
258
  end
245
259
 
246
260
  doc
247
261
  end
248
-
249
262
  end
250
263
  end
251
264
  end