govspeak 6.5.1 → 6.5.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1153c71bd7cd224750aa162c1ea63c89a902309cd08238a888d07a60f079d89
4
- data.tar.gz: a7efca4529a1c6017224a77965e45994d30eba3a3e05f6d209dadff85a516cd9
3
+ metadata.gz: cd25c3749cd867192b6243a3d607d83b4b53def14cfb81f9cfaec0f9194052fe
4
+ data.tar.gz: 9ba6512c5ff9c8a410ea424ea880baef90d0a90e1cc8b660755d3428148bb899
5
5
  SHA512:
6
- metadata.gz: 33209bc51e340ccc5a2a0dc3b43f887eddbd464b687232fb4a17c6c34ae0a3097321f7f51da3ba17762e6ad1307424204872c3374dfa99eeb0100992641f4911
7
- data.tar.gz: ab00d13be257f7730b4b7c2a858a9a9eba3802148dfc851f60fdf91eb65acdb80510a5c67a1e599f2cedbf945502cf29d6567526d695872118025ebb384318f9
6
+ metadata.gz: adc39b260589d92b0ba86e2ee04a513677fece35b50e338ba7716a927b4706c7e2782e13b50a4f5158429621dd2248b40d4dda7804bc176e60d8e73ee3f348d2
7
+ data.tar.gz: 46a366da4e41699f7bf182967a71e1cf3cc92f06f477b655eadd939b8ca25dfd1088591dd8d1b83f563d9ebcc49b797726c59ce2c72d9ce6b63a8dc3992f7513
@@ -1,4 +1,25 @@
1
+ ## 6.5.6
2
+
3
+ * Update Kramdown version to 2.3.0 or greater
4
+
5
+ ## 6.5.5
6
+
7
+ * Prevent links in table headers being stripped (PR#187)
8
+
9
+ ## 6.5.4
10
+
11
+ * Require Sanitize to be at least 5.2.1 due to https://nvd.nist.gov/vuln/detail/CVE-2020-4054
12
+
13
+ ## 6.5.3
14
+
15
+ * Use button component for buttons (PR#176)
16
+
17
+ ## 6.5.2
18
+
19
+ * Allow `data` attributes on `div` tags (PR#173)
20
+
1
21
  ## 6.5.1
22
+
2
23
  * Change unicode testing characters after external gem change
3
24
  * Move from govuk-lint to rubocop-govuk
4
25
  * Allow version 6 of actionview
data/README.md CHANGED
@@ -619,8 +619,7 @@ will output
619
619
 
620
620
  An accessible way to add button links into content, that can also allow cross domain tracking with [Google Analytics](https://support.google.com/analytics/answer/7372977?hl=en)
621
621
 
622
- This button component is [extended from static](http://govuk-static.herokuapp.com/component-guide/button) for [use in govspeak](http://govuk-static.herokuapp.com/component-guide/govspeak/button)
623
- Note: Ideally we'd use the original component directly but this currently isnt possible
622
+ This button component uses the component from the [components gem](https://components.publishing.service.gov.uk/component-guide/button) for use in govspeak.
624
623
 
625
624
  You must use the [link](https://daringfireball.net/projects/markdown/syntax#link) syntax within the button tags.
626
625
 
@@ -635,7 +634,7 @@ To get the most basic button.
635
634
  which outputs
636
635
 
637
636
  ```html
638
- <a role="button" class="button" href="https://gov.uk/random">
637
+ <a role="button" class="gem-c-button govuk-button" href="https://gov.uk/random">
639
638
  Continue
640
639
  </a>
641
640
  ```
@@ -649,8 +648,9 @@ To turn a button into a ['Start now' button](https://www.gov.uk/service-manual/d
649
648
  which outputs
650
649
 
651
650
  ```html
652
- <a role="button" class="button button-start" href="https://gov.uk/random">
651
+ <a role="button" class="gem-c-button govuk-button govuk-button--start" href="https://gov.uk/random">
653
652
  Start Now
653
+ <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg>
654
654
  </a>
655
655
  ```
656
656
 
@@ -667,12 +667,13 @@ which outputs
667
667
  ```html
668
668
  <a
669
669
  role="button"
670
- class="button button-start"
670
+ class="gem-c-button govuk-button govuk-button--start"
671
671
  href="https://example.com/external-service/start-now"
672
672
  data-module="cross-domain-tracking"
673
673
  data-tracking-code="UA-XXXXXX-Y"
674
674
  data-tracking-name="govspeakButtonTracker"
675
675
  >
676
676
  Start Now
677
+ <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg>
677
678
  </a>
678
679
  ```
data/Rakefile CHANGED
@@ -1,15 +1,18 @@
1
1
  require "rake"
2
2
  require "rake/testtask"
3
+ require "rubocop/rake_task"
3
4
  require "bundler"
4
5
 
5
6
  Bundler::GemHelper.install_tasks
6
7
 
8
+ RuboCop::RakeTask.new
9
+
7
10
  desc "Run basic tests"
8
- Rake::TestTask.new("test") { |t|
11
+ Rake::TestTask.new("test") do |t|
9
12
  t.libs << "test"
10
13
  t.pattern = "test/*_test.rb"
11
14
  t.verbose = true
12
15
  t.warning = true
13
- }
16
+ end
14
17
 
15
- task default: [:test]
18
+ task default: %i[test rubocop]
@@ -57,7 +57,9 @@ module Govspeak
57
57
  @links = Array.wrap(options.delete(:links))
58
58
  @contacts = Array.wrap(options.delete(:contacts))
59
59
  @locale = options.fetch(:locale, "en")
60
- @options = { input: PARSER_CLASS_NAME, sanitize: true }.merge(options)
60
+ @options = { input: PARSER_CLASS_NAME,
61
+ sanitize: true,
62
+ syntax_highlighter: nil }.merge(options)
61
63
  @options[:entity_output] = :symbolic
62
64
  end
63
65
 
@@ -108,9 +110,9 @@ module Govspeak
108
110
  source = Govspeak::BlockquoteExtraQuoteRemover.remove(source)
109
111
  source = remove_forbidden_characters(source)
110
112
  self.class.extensions.each do |_, regexp, block|
111
- source.gsub!(regexp) {
113
+ source.gsub!(regexp) do
112
114
  instance_exec(*Regexp.last_match.captures, &block)
113
- }
115
+ end
114
116
  end
115
117
  source
116
118
  end
@@ -127,20 +129,20 @@ module Govspeak
127
129
  end
128
130
 
129
131
  def self.surrounded_by(open, close = nil)
130
- open = Regexp::escape(open)
132
+ open = Regexp.escape(open)
131
133
  if close
132
- close = Regexp::escape(close)
133
- %r+(?:\r|\n|^)#{open}(.*?)#{close} *(\r|\n|$)?+m
134
+ close = Regexp.escape(close)
135
+ %r{(?:\r|\n|^)#{open}(.*?)#{close} *(\r|\n|$)?}m
134
136
  else
135
- %r+(?:\r|\n|^)#{open}(.*?)#{open}? *(\r|\n|$)+m
137
+ %r{(?:\r|\n|^)#{open}(.*?)#{open}? *(\r|\n|$)}m
136
138
  end
137
139
  end
138
140
 
139
141
  def self.wrap_with_div(class_name, character, parser = Kramdown::Document)
140
- extension(class_name, surrounded_by(character)) { |body|
142
+ extension(class_name, surrounded_by(character)) do |body|
141
143
  content = parser ? parser.new("#{body.strip}\n").to_html : body.strip
142
- %{\n<div class="#{class_name}">\n#{content}</div>\n}
143
- }
144
+ %(\n<div class="#{class_name}">\n#{content}</div>\n)
145
+ end
144
146
  end
145
147
 
146
148
  def insert_strong_inside_p(body, parser = Govspeak::Document)
@@ -160,11 +162,11 @@ module Govspeak
160
162
  \s* # any whitespace between opening bracket and link
161
163
  {\/button} # match ending bracket
162
164
  (?:\r|\n|$) # non-capturing match to make sure end of line and linebreak
163
- }x) { |attributes, text, href|
164
- button_classes = "button"
165
- button_classes << " button-start" if attributes =~ /start/
165
+ }x) do |attributes, text, href|
166
+ button_classes = "govuk-button"
166
167
  /cross-domain-tracking:(?<cross_domain_tracking>.[^\s*]+)/ =~ attributes
167
168
  data_attribute = ""
169
+ data_attribute << " data-start='true'" if attributes =~ /start/
168
170
  if cross_domain_tracking
169
171
  data_attribute << " data-module='cross-domain-tracking'"
170
172
  data_attribute << " data-tracking-code='#{cross_domain_tracking.strip}'"
@@ -173,36 +175,36 @@ module Govspeak
173
175
  text = text.strip
174
176
  href = href.strip
175
177
 
176
- %{\n<a role="button" class="#{button_classes}" href="#{href}" #{data_attribute}>#{text}</a>\n}
177
- }
178
+ %(\n<a role="button" class="#{button_classes}" href="#{href}" #{data_attribute}>#{text}</a>\n)
179
+ end
178
180
 
179
- extension("highlight-answer") { |body|
180
- %{\n\n<div class="highlight-answer">
181
- #{Govspeak::Document.new(body.strip).to_html}</div>\n}
182
- }
181
+ extension("highlight-answer") do |body|
182
+ %(\n\n<div class="highlight-answer">
183
+ #{Govspeak::Document.new(body.strip).to_html}</div>\n)
184
+ end
183
185
 
184
- extension("stat-headline", %r${stat-headline}(.*?){/stat-headline}$m) { |body|
185
- %{\n\n<aside class="stat-headline">
186
- #{Govspeak::Document.new(body.strip).to_html}</aside>\n}
187
- }
186
+ extension("stat-headline", %r${stat-headline}(.*?){/stat-headline}$m) do |body|
187
+ %(\n\n<aside class="stat-headline">
188
+ #{Govspeak::Document.new(body.strip).to_html}</aside>\n)
189
+ end
188
190
 
189
191
  # FIXME: these surrounded_by arguments look dodgy
190
- extension("external", surrounded_by("x[", ")x")) { |body|
192
+ extension("external", surrounded_by("x[", ")x")) do |body|
191
193
  Kramdown::Document.new("[#{body.strip}){:rel='external'}").to_html
192
- }
194
+ end
193
195
 
194
- extension("informational", surrounded_by("^")) { |body|
195
- %{\n\n<div role="note" aria-label="Information" class="application-notice info-notice">
196
- #{Govspeak::Document.new(body.strip).to_html}</div>\n}
197
- }
196
+ extension("informational", surrounded_by("^")) do |body|
197
+ %(\n\n<div role="note" aria-label="Information" class="application-notice info-notice">
198
+ #{Govspeak::Document.new(body.strip).to_html}</div>\n)
199
+ end
198
200
 
199
- extension("important", surrounded_by("@")) { |body|
200
- %{\n\n<div role="note" aria-label="Important" class="advisory">#{insert_strong_inside_p(body)}</div>\n}
201
- }
201
+ extension("important", surrounded_by("@")) do |body|
202
+ %(\n\n<div role="note" aria-label="Important" class="advisory">#{insert_strong_inside_p(body)}</div>\n)
203
+ end
202
204
 
203
- extension("helpful", surrounded_by("%")) { |body|
204
- %{\n\n<div role="note" aria-label="Warning" class="application-notice help-notice">\n#{Govspeak::Document.new(body.strip).to_html}</div>\n}
205
- }
205
+ extension("helpful", surrounded_by("%")) do |body|
206
+ %(\n\n<div role="note" aria-label="Warning" class="application-notice help-notice">\n#{Govspeak::Document.new(body.strip).to_html}</div>\n)
207
+ end
206
208
 
207
209
  extension("barchart", /{barchart(.*?)}/) do |captures|
208
210
  stacked = ".mc-stacked" if captures.include? "stacked"
@@ -210,13 +212,13 @@ module Govspeak
210
212
  negative = ".mc-negative" if captures.include? "negative"
211
213
 
212
214
  [
213
- "{:",
214
- ".js-barchart-table",
215
- stacked,
216
- compact,
217
- negative,
218
- ".mc-auto-outdent",
219
- "}",
215
+ "{:",
216
+ ".js-barchart-table",
217
+ stacked,
218
+ compact,
219
+ negative,
220
+ ".mc-auto-outdent",
221
+ "}",
220
222
  ].join(" ")
221
223
  end
222
224
 
@@ -234,12 +236,12 @@ module Govspeak
234
236
 
235
237
  attachment = AttachmentPresenter.new(attachment)
236
238
 
237
- span_id = attachment.id ? %{ id="attachment_#{attachment.id}"} : ""
239
+ span_id = attachment.id ? %( id="attachment_#{attachment.id}") : ""
238
240
  # new lines inside our title cause problems with govspeak rendering as this is expected to be on one line.
239
241
  title = (attachment.title || "").tr("\n", " ")
240
242
  link = attachment.link(title, attachment.url)
241
243
  attributes = attachment.attachment_attributes.empty? ? "" : " (#{attachment.attachment_attributes})"
242
- %{<span#{span_id} class="attachment-inline">#{link}#{attributes}</span>}
244
+ %(<span#{span_id} class="attachment-inline">#{link}#{attributes}</span>)
243
245
  end
244
246
 
245
247
  # DEPRECATED: use 'Image:image-id' instead
@@ -261,10 +263,10 @@ module Govspeak
261
263
  #
262
264
  # This issue is not considered a bug by kramdown: https://github.com/gettalong/kramdown/issues/191
263
265
  def render_image(image)
264
- id_attr = image.id ? %{ id="attachment_#{image.id}"} : ""
266
+ id_attr = image.id ? %( id="attachment_#{image.id}") : ""
265
267
  lines = []
266
- lines << %{<figure#{id_attr} class="image embedded">}
267
- lines << %{<div class="img"><img src="#{encode(image.url)}" alt="#{encode(image.alt_text)}"></div>}
268
+ lines << %(<figure#{id_attr} class="image embedded">)
269
+ lines << %(<div class="img"><img src="#{encode(image.url)}" alt="#{encode(image.alt_text)}"></div>)
268
270
  lines << image.figcaption_html if image.figcaption?
269
271
  lines << "</figure>"
270
272
  lines.join
@@ -279,9 +281,9 @@ module Govspeak
279
281
  wrap_with_div("example", "$E", Govspeak::Document)
280
282
  wrap_with_div("call-to-action", "$CTA", Govspeak::Document)
281
283
 
282
- extension("address", surrounded_by("$A")) { |body|
283
- %{\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", '').gsub("\n", '<br />')}\n</p></div></div>\n}
284
- }
284
+ extension("address", surrounded_by("$A")) do |body|
285
+ %(\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", '').gsub("\n", '<br />')}\n</p></div></div>\n)
286
+ end
285
287
 
286
288
  extension("legislative list", /#{NEW_PARAGRAPH_LOOKBEHIND}\$LegislativeList\s*$(.*?)\$EndLegislativeList/m) do |body|
287
289
  Govspeak::KramdownOverrides.with_kramdown_ordered_lists_disabled do
@@ -295,9 +297,9 @@ module Govspeak
295
297
 
296
298
  extension("numbered list", /^[ \t]*((s\d+\.\s.*(?:\n|$))+)/) do |body|
297
299
  body.gsub!(/s(\d+)\.\s(.*)(?:\n|$)/) do
298
- "<li>#{Govspeak::Document.new($2.strip).to_html}</li>\n"
300
+ "<li>#{Govspeak::Document.new(Regexp.last_match(2).strip).to_html}</li>\n"
299
301
  end
300
- %{<ol class="steps">\n#{body}</ol>}
302
+ %(<ol class="steps">\n#{body}</ol>)
301
303
  end
302
304
 
303
305
  def self.devolved_options
@@ -362,13 +364,13 @@ module Govspeak
362
364
  extension("Attachment", /#{NEW_PARAGRAPH_LOOKBEHIND}\[Attachment:\s*(.*?)\s*\]/) do |attachment_id|
363
365
  next "" if attachments.none? { |a| a[:id] == attachment_id }
364
366
 
365
- %{<govspeak-embed-attachment id="#{attachment_id}"></govspeak-embed-attachment>}
367
+ %(<govspeak-embed-attachment id="#{attachment_id}"></govspeak-embed-attachment>)
366
368
  end
367
369
 
368
370
  extension("AttachmentLink", /\[AttachmentLink:\s*(.*?)\s*\]/) do |attachment_id|
369
371
  next "" if attachments.none? { |a| a[:id] == attachment_id }
370
372
 
371
- %{<govspeak-embed-attachment-link id="#{attachment_id}"></govspeak-embed-attachment-link>}
373
+ %(<govspeak-embed-attachment-link id="#{attachment_id}"></govspeak-embed-attachment-link>)
372
374
  end
373
375
 
374
376
  private
@@ -51,12 +51,15 @@ class Govspeak::HtmlSanitizer
51
51
  def sanitize_config
52
52
  Sanitize::Config.merge(
53
53
  Sanitize::Config::RELAXED,
54
- elements: Sanitize::Config::RELAXED[:elements] + %w[govspeak-embed-attachment govspeak-embed-attachment-link],
54
+ elements: Sanitize::Config::RELAXED[:elements] + %w[govspeak-embed-attachment govspeak-embed-attachment-link svg path],
55
55
  attributes: {
56
56
  :all => Sanitize::Config::RELAXED[:attributes][:all] + %w[role aria-label],
57
- "a" => Sanitize::Config::RELAXED[:attributes]["a"] + [:data],
58
- "th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
59
- "td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
57
+ "a" => Sanitize::Config::RELAXED[:attributes]["a"] + [:data],
58
+ "svg" => Sanitize::Config::RELAXED[:attributes][:all] + %w[xmlns width height viewbox focusable],
59
+ "path" => Sanitize::Config::RELAXED[:attributes][:all] + %w[fill d],
60
+ "div" => [:data],
61
+ "th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
62
+ "td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
60
63
  "govspeak-embed-attachment" => %w[content-id],
61
64
  },
62
65
  )
@@ -1,9 +1,9 @@
1
1
  module Govspeak
2
2
  module KramdownOverrides
3
- # This depends on two internal parts of Kramdown.
3
+ # This depends on two internal parts of Kramdown.
4
4
  # 1. Parser registry (kramdown/parser/kramdown.rb#define_parser)
5
5
  # 2. Kramdown list regexes (kramdown/parser/kramdown/list.rb)
6
- # Updating the Kramdown gem therefore also means updating this file to to
6
+ # Updating the Kramdown gem therefore also means updating this file to to
7
7
  # match Kramdown's internals.
8
8
 
9
9
  def self.with_kramdown_ordered_lists_disabled
@@ -14,9 +14,9 @@ module Govspeak
14
14
  attr_reader :document, :website_root
15
15
 
16
16
  def extract_links
17
- document_anchors.
18
- map { |link| extract_href_from_link(link) }.
19
- reject(&:blank?)
17
+ document_anchors
18
+ .map { |link| extract_href_from_link(link) }
19
+ .reject(&:blank?)
20
20
  end
21
21
 
22
22
  def extract_href_from_link(link)
@@ -92,11 +92,31 @@ module Govspeak
92
92
  end
93
93
 
94
94
  document.css(":not(thead) tr td:first-child").map do |el|
95
- if el.content.match?(/^#($|\s.*$)/)
96
- el.content = el.content.gsub(/^#($|\s)/, "") # Replace '# ' and '#', but not '#Word'.
97
- el.name = "th" if el.content.present? # This also prevents a `th` with nothing inside it; a `td` is preferable.
98
- el[:scope] = "row" if el.content.present? # `scope` shouldn't be used if there's nothing in the table heading.
99
- end
95
+ next unless el.content.match?(/^#($|\s.*$)/)
96
+
97
+ # Replace '# ' and '#', but not '#Word'.
98
+ # This runs on the first child of the element to preserve any links
99
+ el.children.first.content = el.children.first.content.gsub(/^#($|\s)/, "")
100
+ el.name = "th" if el.content.present? # This also prevents a `th` with nothing inside it; a `td` is preferable.
101
+ el[:scope] = "row" if el.content.present? # `scope` shouldn't be used if there's nothing in the table heading.
102
+ end
103
+ end
104
+
105
+ extension("use gem component for buttons") do |document|
106
+ document.css(".govuk-button").map do |el|
107
+ button_html = GovukPublishingComponents.render(
108
+ "govuk_publishing_components/components/button",
109
+ text: el.content,
110
+ href: el["href"],
111
+ start: el["data-start"],
112
+ data_attributes: {
113
+ module: el["data-module"],
114
+ "tracking-code": el["data-tracking-code"],
115
+ "tracking-name": el["data-tracking-name"],
116
+ },
117
+ ).squish.gsub("> <", "><").gsub!(/\s+/, " ")
118
+
119
+ el.swap(button_html)
100
120
  end
101
121
  end
102
122
 
@@ -55,46 +55,46 @@ module Govspeak
55
55
 
56
56
  def humanized_content_type(file_extension)
57
57
  file_extension_vs_humanized_content_type = {
58
- "chm" => file_abbr_tag("CHM", "Microsoft Compiled HTML Help"),
59
- "csv" => file_abbr_tag("CSV", "Comma-separated Values"),
58
+ "chm" => file_abbr_tag("CHM", "Microsoft Compiled HTML Help"),
59
+ "csv" => file_abbr_tag("CSV", "Comma-separated Values"),
60
60
  "diff" => file_abbr_tag("DIFF", "Plain text differences"),
61
- "doc" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
61
+ "doc" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
62
62
  "docx" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
63
- "dot" => file_abbr_tag("DOT", "MS Word Document Template"),
64
- "dxf" => file_abbr_tag("DXF", "AutoCAD Drawing Exchange Format"),
65
- "eps" => file_abbr_tag("EPS", "Encapsulated PostScript"),
66
- "gif" => file_abbr_tag("GIF", "Graphics Interchange Format"),
67
- "gml" => file_abbr_tag("GML", "Geography Markup Language"),
63
+ "dot" => file_abbr_tag("DOT", "MS Word Document Template"),
64
+ "dxf" => file_abbr_tag("DXF", "AutoCAD Drawing Exchange Format"),
65
+ "eps" => file_abbr_tag("EPS", "Encapsulated PostScript"),
66
+ "gif" => file_abbr_tag("GIF", "Graphics Interchange Format"),
67
+ "gml" => file_abbr_tag("GML", "Geography Markup Language"),
68
68
  "html" => file_abbr_tag("HTML", "Hypertext Markup Language"),
69
69
  "ics" => file_abbr_tag("ICS", "iCalendar file"),
70
- "jpg" => "JPEG",
71
- "odp" => file_abbr_tag("ODP", "OpenDocument Presentation"),
72
- "ods" => file_abbr_tag("ODS", "OpenDocument Spreadsheet"),
73
- "odt" => file_abbr_tag("ODT", "OpenDocument Text document"),
74
- "pdf" => file_abbr_tag("PDF", "Portable Document Format"),
75
- "png" => file_abbr_tag("PNG", "Portable Network Graphic"),
76
- "ppt" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
70
+ "jpg" => "JPEG",
71
+ "odp" => file_abbr_tag("ODP", "OpenDocument Presentation"),
72
+ "ods" => file_abbr_tag("ODS", "OpenDocument Spreadsheet"),
73
+ "odt" => file_abbr_tag("ODT", "OpenDocument Text document"),
74
+ "pdf" => file_abbr_tag("PDF", "Portable Document Format"),
75
+ "png" => file_abbr_tag("PNG", "Portable Network Graphic"),
76
+ "ppt" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
77
77
  "pptx" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
78
- "ps" => file_abbr_tag("PS", "PostScript"),
79
- "rdf" => file_abbr_tag("RDF", "Resource Description Framework"),
80
- "rtf" => file_abbr_tag("RTF", "Rich Text Format"),
81
- "sch" => file_abbr_tag("SCH", "XML based Schematic"),
82
- "txt" => "Plain text",
78
+ "ps" => file_abbr_tag("PS", "PostScript"),
79
+ "rdf" => file_abbr_tag("RDF", "Resource Description Framework"),
80
+ "rtf" => file_abbr_tag("RTF", "Rich Text Format"),
81
+ "sch" => file_abbr_tag("SCH", "XML based Schematic"),
82
+ "txt" => "Plain text",
83
83
  "wsdl" => file_abbr_tag("WSDL", "Web Services Description Language"),
84
- "xls" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
84
+ "xls" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
85
85
  "xlsm" => file_abbr_tag("XLSM", "MS Excel Macro-Enabled Workbook"),
86
86
  "xlsx" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
87
- "xlt" => file_abbr_tag("XLT", "MS Excel Spreadsheet Template"),
88
- "xsd" => file_abbr_tag("XSD", "XML Schema"),
87
+ "xlt" => file_abbr_tag("XLT", "MS Excel Spreadsheet Template"),
88
+ "xsd" => file_abbr_tag("XSD", "XML Schema"),
89
89
  "xslt" => file_abbr_tag("XSLT", "Extensible Stylesheet Language Transformation"),
90
- "zip" => file_abbr_tag("ZIP", "Zip archive"),
90
+ "zip" => file_abbr_tag("ZIP", "Zip archive"),
91
91
  }
92
92
  file_extension_vs_humanized_content_type.fetch(file_extension.to_s.downcase, "")
93
93
  end
94
94
 
95
95
  def link(body, url, options = {})
96
- options_str = options.map { |k, v| %{#{encode(k)}="#{encode(v)}"} }.join(" ")
97
- %{<a href="#{encode(url)}" #{options_str}>#{body}</a>}
96
+ options_str = options.map { |k, v| %(#{encode(k)}="#{encode(v)}") }.join(" ")
97
+ %(<a href="#{encode(url)}" #{options_str}>#{body}</a>)
98
98
  end
99
99
 
100
100
  private