govspeak 4.0.0 → 5.0.0

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
  SHA1:
3
- metadata.gz: 6e737eca62f51b5b954bc37b5fc55d2ba680bb8b
4
- data.tar.gz: bf6f399b9badf4708ee7f4f9c6ca50a157493ac5
3
+ metadata.gz: 726774ba404422dd313e1afb12bbc3d1c2e036fd
4
+ data.tar.gz: 4f7e5f0f7b4c9e67c33120050f3021048c13dec7
5
5
  SHA512:
6
- metadata.gz: 9363e23283391abf19861ce9522b7cf30874aeecf37b7274dd19411a7539f1956ba3043218601fbc694acde33f2c062fc71f8bd1f113ce9884ca2e7938f1784f
7
- data.tar.gz: efe6a7cda89d313ef06470fa0a5e71acc40d85b16ebe8ab95928865e4f20295e7663a4fa43a1e428a5edd2943a6949a96cbc9ec7c31bac28c2322f3a5ba5f353
6
+ metadata.gz: a144b4353edd665f01109bd1eae1ff569f22ea6133bf564024e973d7df730e9f318d9eb76e76df5917e130b10dbf9897dcbcd75ccfdb816ba291b16e3e9349a0
7
+ data.tar.gz: 8370760f5a9929f8c33b001d99fc5409900182d815bbae0059c33360d47ef7ccd560ae4b206bdec7940747b73ce9e544edffd10751a7107070b8a9aa49006e8f
@@ -1,3 +1,11 @@
1
+ ## 5.0.0
2
+ * Update Kramdown version to 1.12.0
3
+ * Add pry-byebug to development dependencies
4
+ * Ability to run Govspeak as a binary from command line [#87](https://github.com/alphagov/govspeak/pull/87)
5
+ * Uses hashes the primary interface for options to commands [#89](https://github.com/alphagov/govspeak/pull/89)
6
+ * Adds the `[embed:attachments:image:%content_id%]` extension [#90](https://github.com/alphagov/govspeak/pull/90)
7
+ * Renders incorrect usages of embedding content as empty strings rather than outputting markdown [91](https://github.com/alphagov/govspeak/pull/91)
8
+
1
9
  ## 4.0.0
2
10
 
3
11
  * Drop support for Ruby 1.9.3
data/README.md CHANGED
@@ -18,6 +18,18 @@ then create a new document
18
18
  doc = Govspeak::Document.new "^Test^"
19
19
  puts doc.to_html
20
20
 
21
+ or alternatively, run it from the command line
22
+
23
+ $ govspeak "render-me"
24
+ $ govspeak --file render-me.md
25
+ $ echo "render-me" | govspeak
26
+
27
+ options can be passed in through `--options` as a string of JSON or a file
28
+ of JSON can be passed in as `--options-file options.json`.
29
+
30
+ if installed via bundler prefix commands with bundle exec eg `$ bundle exec govspeak "render-me"`
31
+
32
+
21
33
  # Extensions
22
34
 
23
35
  In addition to the [standard Markdown syntax](http://daringfireball.net/projects/markdown/syntax "Markdown syntax"), we have added our own extensions.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path("../../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require "govspeak/cli"
7
+
8
+ Govspeak::CLI.new.run
@@ -1,4 +1,6 @@
1
1
  require 'kramdown'
2
+ require 'active_support/core_ext/hash'
3
+ require 'active_support/core_ext/array'
2
4
  require 'govspeak/header_extractor'
3
5
  require 'govspeak/structured_header_extractor'
4
6
  require 'govspeak/html_validator'
@@ -9,6 +11,7 @@ require 'govspeak/post_processor'
9
11
  require 'kramdown/parser/kramdown_with_automatic_external_links'
10
12
  require 'htmlentities'
11
13
  require 'presenters/attachment_presenter'
14
+ require 'presenters/contact_presenter'
12
15
  require 'presenters/h_card_presenter'
13
16
  require 'erb'
14
17
 
@@ -29,11 +32,12 @@ module Govspeak
29
32
  end
30
33
 
31
34
  def initialize(source, options = {})
35
+ options.deep_symbolize_keys!
32
36
  @source = source ? source.dup : ""
33
37
  @images = options.delete(:images) || []
34
- @attachments = Array(options.delete(:attachments))
35
- @links = Array(options.delete(:links))
36
- @contacts = Array(options.delete(:contacts))
38
+ @attachments = Array.wrap(options.delete(:attachments))
39
+ @links = Array.wrap(options.delete(:links))
40
+ @contacts = Array.wrap(options.delete(:contacts))
37
41
  @locale = options.fetch(:locale, "en")
38
42
  @options = {input: PARSER_CLASS_NAME}.merge(options)
39
43
  @options[:entity_output] = :symbolic
@@ -185,29 +189,52 @@ module Govspeak
185
189
  end
186
190
  end
187
191
 
188
- extension('attachment', /\[embed:attachments:([0-9a-f-]+)\]/) do |content_id, body|
189
- attachment = attachments.detect { |a| a.content_id.match(content_id) }
192
+ extension('attachment', /\[embed:attachments:(?!inline:|image:)\s*(.*?)\s*\]/) do |content_id, body|
193
+ attachment = attachments.detect { |a| a[:content_id].match(content_id) }
190
194
  next "" unless attachment
191
195
  attachment = AttachmentPresenter.new(attachment)
192
- content = File.read('lib/govspeak/extension/attachment.html.erb')
196
+ content = File.read(__dir__ + '/templates/attachment.html.erb')
193
197
  ERB.new(content).result(binding)
194
198
  end
195
199
 
196
- extension('attachment inline', /\[embed:attachments:inline:([0-9a-f-]+)\]/) do |content_id, body|
197
- attachment = attachments.detect { |a| a.content_id.match(content_id) }
200
+ extension('attachment inline', /\[embed:attachments:inline:\s*(.*?)\s*\]/) do |content_id|
201
+ attachment = attachments.detect { |a| a[:content_id].match(content_id) }
198
202
  next "" unless attachment
199
203
  attachment = AttachmentPresenter.new(attachment)
200
- content = File.read('lib/govspeak/extension/inline_attachment.html.erb')
201
- ERB.new(content).result(binding)
204
+ span_id = attachment.id ? %{ id="attachment_#{attachment.id}"} : ""
205
+ # new lines inside our title cause problems with govspeak rendering as this is expected to be on one line.
206
+ title = (attachment.title || "").tr("\n", " ")
207
+ link = attachment.link(title, attachment.url)
208
+ attributes = attachment.attachment_attributes.empty? ? "" : " (#{attachment.attachment_attributes})"
209
+ %{<span#{span_id} class="attachment-inline">#{link}#{attributes}</span>}
202
210
  end
203
211
 
204
- def render_image(url, alt_text, caption = nil)
212
+ extension('attachment image', /\[embed:attachments:image:\s*(.*?)\s*\]/) do |content_id|
213
+ attachment = attachments.detect { |a| a[:content_id].match(content_id) }
214
+ next "" unless attachment
215
+ attachment = AttachmentPresenter.new(attachment)
216
+ title = (attachment.title || "").tr("\n", " ")
217
+ render_image(attachment.url, title, nil, attachment.id)
218
+ end
219
+
220
+ # As of version 1.12.0 of Kramdown the block elements (div & figcaption)
221
+ # inside this html block will have it's < > converted into HTML Entities
222
+ # when ever this code is used inside block level elements.
223
+ #
224
+ # To resolve this we have a post-processing task that will convert this
225
+ # back into HTML (I know - it's ugly). The way we could resolve this
226
+ # without ugliness would be to output only inline elements which rules
227
+ # out div and figcaption
228
+ #
229
+ # This issue is not considered a bug by kramdown: https://github.com/gettalong/kramdown/issues/191
230
+ def render_image(url, alt_text, caption = nil, id = nil)
231
+ id_attr = id ? %{ id="attachment_#{id}"} : ""
205
232
  lines = []
206
- lines << '<figure class="image embedded">'
207
- lines << %Q{ <div class="img"><img alt="#{encode(alt_text)}" src="#{encode(url)}" /></div>}
208
- lines << %Q{ <figcaption>#{encode(caption.strip)}</figcaption>} if caption && !caption.strip.empty?
233
+ lines << %{<figure#{id_attr} class="image embedded">}
234
+ lines << %Q{<div class="img"><img src="#{encode(url)}" alt="#{encode(alt_text)}"></div>}
235
+ lines << %Q{<figcaption>#{caption.strip}</figcaption>} if caption && !caption.strip.empty?
209
236
  lines << '</figure>'
210
- lines.join "\n"
237
+ lines.join
211
238
  end
212
239
 
213
240
  wrap_with_div('summary', '$!')
@@ -272,13 +299,13 @@ module Govspeak
272
299
  end
273
300
  end
274
301
 
275
- extension('embed link', /\[embed:link:([0-9a-f-]+)\]/) do |content_id|
276
- link = links.detect { |l| l.content_id.match(content_id) }
302
+ extension('embed link', /\[embed:link:\s*(.*?)\s*\]/) do |content_id|
303
+ link = links.detect { |l| l[:content_id].match(content_id) }
277
304
  next "" unless link
278
- if link.url
279
- %Q{<a href="#{encode(link.url)}">#{encode(link.title)}</a>}
305
+ if link[:url]
306
+ %Q{<a href="#{encode(link[:url])}">#{link[:title]}</a>}
280
307
  else
281
- encode(link.title)
308
+ link[:title]
282
309
  end
283
310
  end
284
311
 
@@ -287,10 +314,11 @@ module Govspeak
287
314
  end
288
315
  private :render_hcard_address
289
316
 
290
- extension('Contact', /\[Contact:([0-9a-f-]+)\]/) do |content_id|
291
- contact = contacts.detect { |c| c.content_id.match(content_id) }
317
+ extension('Contact', /\[Contact:\s*(.*?)\s*\]/) do |content_id|
318
+ contact = contacts.detect { |c| c[:content_id].match(content_id) }
292
319
  next "" unless contact
293
- @renderer ||= ERB.new(File.read('lib/templates/contact.html.erb'))
320
+ contact = ContactPresenter.new(contact)
321
+ @renderer ||= ERB.new(File.read(__dir__ + '/templates/contact.html.erb'))
294
322
  @renderer.result(binding)
295
323
  end
296
324
  end
@@ -0,0 +1,51 @@
1
+ require 'govspeak/version'
2
+ require 'govspeak'
3
+ require 'commander'
4
+
5
+ module Govspeak
6
+ class CLI
7
+ include Commander::Methods
8
+
9
+ def run
10
+ program(:name, 'Govspeak')
11
+ program(:version, Govspeak::VERSION)
12
+ program(:description, "A tool for rendering the GOV.UK dialect of markdown into HTML")
13
+ default_command(:render)
14
+ command(:render) do |command|
15
+ command.syntax = "govspeak render [options] <input>"
16
+ command.description = "Render Govspeak into HTML, can be sourced from stdin, as an argument or from a file"
17
+ command.option("--file FILENAME", String, "File to render")
18
+ command.option("--options JSON", String, "JSON to use as options")
19
+ command.option("--options-file FILENAME", String, "A file of JSON options")
20
+ command.action do |args, options|
21
+ input = get_input($stdin, args, options)
22
+ raise "Nothing to render. Use --help for assistance" unless input
23
+ puts Govspeak::Document.new(input, govspeak_options(options)).to_html
24
+ end
25
+ end
26
+ run!
27
+ end
28
+
29
+ private
30
+
31
+ def get_input(stdin, args, options)
32
+ return stdin.read unless stdin.tty?
33
+ return read_file(options.file) if options.file
34
+ args.empty? ? nil : args.join(" ")
35
+ end
36
+
37
+ def read_file(file_path)
38
+ path = Pathname.new(file_path).realpath
39
+ File.read(path)
40
+ end
41
+
42
+ def govspeak_options(command_options)
43
+ string = if command_options.options_file
44
+ read_file(command_options.options_file)
45
+ else
46
+ command_options.options
47
+ end
48
+ string ? JSON.load(string) : {}
49
+ end
50
+ end
51
+ end
@@ -38,5 +38,31 @@ module Govspeak
38
38
  el[:class] = "last-child"
39
39
  end
40
40
  end
41
+
42
+ # This "fix" here is tied into the rendering of images as one of the
43
+ # pre-processor tasks. As images can be created inside block level elements
44
+ # it's possible that their block level elements can be HTML entity escaped
45
+ # to produce "valid" HTML.
46
+ #
47
+ # This sucks for us as we spit the user out HTML elements.
48
+ #
49
+ # This fix reverses this, and of course, totally sucks because it's tightly
50
+ # coupled to the `render_image` code and it really isn't cool to undo HTML
51
+ # entity encoding.
52
+ extension("fix image attachment escaping") do |document|
53
+ document.css("figure.image").map do |el|
54
+ xml = el.children.to_s
55
+ next unless xml =~ /&lt;div class="img"&gt;|&lt;figcaption&gt;/
56
+ el.children = xml
57
+ .gsub(
58
+ %r{&lt;(div class="img")&gt;(.*?)&lt;(/div)&gt;},
59
+ "<\\1>\\2<\\3>"
60
+ )
61
+ .gsub(
62
+ %r{&lt;(figcaption)&gt;(.*?)&lt;(/figcaption&)gt;},
63
+ "<\\1>\\2<\\3>"
64
+ )
65
+ end
66
+ end
41
67
  end
42
68
  end
@@ -1,3 +1,3 @@
1
1
  module Govspeak
2
- VERSION = "4.0.0"
2
+ VERSION = "5.0.0"
3
3
  end
@@ -1,77 +1,80 @@
1
1
  require "action_view"
2
2
  require "money"
3
+ require "htmlentities"
3
4
 
4
5
  class AttachmentPresenter
5
6
  attr_reader :attachment
6
7
  include ActionView::Helpers::TagHelper
7
8
  include ActionView::Helpers::NumberHelper
8
9
  include ActionView::Helpers::AssetTagHelper
10
+ include ActionView::Helpers::TextHelper
9
11
 
10
12
  def initialize(attachment)
11
13
  @attachment = attachment
12
14
  end
13
15
 
14
16
  def id
15
- attachment.id
17
+ attachment[:id]
16
18
  end
17
19
 
18
20
  def order_url
19
- attachment.order_url
21
+ attachment[:order_url]
20
22
  end
21
23
 
22
24
  def opendocument?
23
- attachment.opendocument?
25
+ attachment[:opendocument?]
24
26
  end
25
27
 
26
28
  def url
27
- attachment.url
29
+ attachment[:url]
28
30
  end
29
31
 
30
32
  def external?
31
- attachment.external?
33
+ attachment[:external?]
32
34
  end
33
35
 
34
36
  def price
35
- return unless attachment.price
36
- Money.from_amount(attachment.price, 'GBP').format
37
+ return unless attachment[:price]
38
+ Money.from_amount(attachment[:price], 'GBP').format
37
39
  end
38
40
 
39
41
  def accessible?
40
- attachment.accessible?
42
+ attachment[:accessible?]
41
43
  end
42
44
 
43
45
  def thumbnail_link
44
46
  return if hide_thumbnail?
45
47
  return if previewable?
46
- link(attachment_thumbnail, url, "aria-hidden=true class=#{attachment_class}")
48
+ link(attachment_thumbnail, url, "aria-hidden" => "true", "class" => attachment_class)
47
49
  end
48
50
 
49
51
  def help_block_toggle_id
50
- "attachment-#{attachment.id}-accessibility-request"
52
+ "attachment-#{id}-accessibility-request"
51
53
  end
52
54
 
53
55
  def section_class
54
- attachment.external? ? "hosted-externally" : "embedded"
56
+ attachment[:external?] ? "hosted-externally" : "embedded"
55
57
  end
56
58
 
57
59
  def mail_to(email_address, name, options = {})
58
- "<a href='mailto:#{email_address}?Subject=#{options[:subject]}&body=#{options[:body]}'>#{name}</a>"
60
+ query_string = options.slice(:subject, :body).map { |k, v| "#{urlencode(k)}=#{urlencode(v)}" }.join("&")
61
+ "<a href='mailto:#{encode(email_address)}?#{encode(query_string)}'>#{name}</a>"
59
62
  end
60
63
 
61
64
  def alternative_format_order_link
62
65
  attachment_info = []
63
- attachment_info << " Title: #{attachment.title}"
64
- attachment_info << " Original format: #{attachment.file_extension}"
65
- attachment_info << " ISBN: #{attachment.isbn}" if attachment.isbn.present?
66
- attachment_info << " Unique reference: #{attachment.unique_reference}" if attachment.unique_reference.present?
67
- attachment_info << " Command paper number: #{attachment.command_paper_number}" if attachment.command_paper_number.present?
68
- if attachment.hoc_paper_number.present?
69
- attachment_info << " House of Commons paper number: #{attachment.hoc_paper_number}"
70
- attachment_info << " Parliamentary session: #{attachment.parliamentary_session}"
66
+ attachment_info << " Title: #{title}"
67
+ attachment_info << " Original format: #{file_extension}" if file_extension.present?
68
+ attachment_info << " ISBN: #{attachment[:isbn]}" if attachment[:isbn].present?
69
+ attachment_info << " Unique reference: #{attachment[:unique_reference]}" if attachment[:unique_reference].present?
70
+ attachment_info << " Command paper number: #{attachment[:command_paper_number]}" if attachment[:command_paper_number].present?
71
+ if attachment[:hoc_paper_number].present?
72
+ attachment_info << " House of Commons paper number: #{attachment[:hoc_paper_number]}"
73
+ attachment_info << " Parliamentary session: #{attachment[:parliamentary_session]}"
71
74
  end
72
75
 
73
76
  options = {
74
- subject: "Request for '#{attachment.title}' in an alternative format",
77
+ subject: "Request for '#{title}' in an alternative format",
75
78
  body: body_for_mail(attachment_info)
76
79
  }
77
80
 
@@ -80,7 +83,7 @@ class AttachmentPresenter
80
83
 
81
84
  def body_for_mail(attachment_info)
82
85
  <<-END
83
- Details of document required:
86
+ Details of document required:
84
87
 
85
88
  #{attachment_info.join("\n")}
86
89
 
@@ -95,66 +98,93 @@ Please tell us:
95
98
  "govuk-feedback@digital.cabinet-office.gov.uk"
96
99
  end
97
100
 
101
+ # FIXME: usage of image_tag will cause these to render at /images/ which seems
102
+ # very host dependent. I assume this will need links to static urls.
98
103
  def attachment_thumbnail
99
- if attachment.pdf?
100
- image_tag(attachment.file.thumbnail.url)
101
- elsif attachment.html?
104
+ if file_extension == "pdf" && attachment[:thumbnail_url]
105
+ image_tag(attachment[:thumbnail_url])
106
+ elsif file_extension == "html"
102
107
  image_tag('pub-cover-html.png')
103
- elsif %w{doc docx odt}.include? attachment.file_extension
108
+ elsif %w{doc docx odt}.include?(file_extension)
104
109
  image_tag('pub-cover-doc.png')
105
- elsif %w{xls xlsx ods csv}.include? attachment.file_extension
110
+ elsif %w{xls xlsx ods csv}.include?(file_extension)
106
111
  image_tag('pub-cover-spreadsheet.png')
107
112
  else
108
113
  image_tag('pub-cover.png')
109
114
  end
110
115
  end
111
116
 
112
- def references
117
+ def reference
118
+ ref = []
119
+ if attachment[:isbn].present?
120
+ ref << "ISBN " + content_tag(:span, attachment[:isbn], class: "isbn")
121
+ end
122
+
123
+ if attachment[:unique_reference].present?
124
+ ref << content_tag(:span, attachment[:unique_reference], class: "unique_reference")
125
+ end
126
+
127
+ if attachment[:command_paper_number].present?
128
+ ref << content_tag(:span, attachment[:command_paper_number], class: "command_paper_number")
129
+ end
130
+
131
+ if attachment[:hoc_paper_number].present?
132
+ ref << content_tag(:span, "HC #{attachment[:hoc_paper_number]}", class: 'house_of_commons_paper_number') + ' ' +
133
+ content_tag(:span, attachment[:parliamentary_session], class: 'parliamentary_session')
134
+ end
135
+
136
+ ref.join(', ').html_safe
137
+ end
138
+
139
+ # FIXME this has english in it so will cause problems if the locale is not en
140
+ def references_for_title
113
141
  references = []
114
- references << "ISBN: #{attachment.isbn}" if attachment.isbn.present?
115
- references << "Unique reference: #{attachment.unique_reference}" if attachment.unique_reference.present?
116
- references << "Command paper number: #{attachment.command_paper_number}" if attachment.command_paper_number.present?
117
- references << "HC: #{attachment.hoc_paper_number} #{attachment.parliamentary_session}" if attachment.hoc_paper_number.present?
142
+ references << "ISBN: #{attachment[:isbn]}" if attachment[:isbn].present?
143
+ references << "Unique reference: #{attachment[:unique_reference]}" if attachment[:unique_reference].present?
144
+ references << "Command paper number: #{attachment[:command_paper_number]}" if attachment[:command_paper_number].present?
145
+ references << "HC: #{attachment[:hoc_paper_number]} #{attachment[:parliamentary_session]}" if attachment[:hoc_paper_number].present?
118
146
  prefix = references.size == 1 ? "and its reference" : "and its references"
119
147
  references.any? ? ", #{prefix} (" + references.join(", ") + ")" : ""
120
148
  end
121
149
 
122
150
  def references?
123
- !attachment.isbn.to_s.empty? || !attachment.unique_reference.to_s.empty? || !attachment.command_paper_number.to_s.empty? || !attachment.hoc_paper_number.to_s.empty?
151
+ !attachment[:isbn].to_s.empty? || !attachment[:unique_reference].to_s.empty? || !attachment[:command_paper_number].to_s.empty? || !attachment[:hoc_paper_number].to_s.empty?
124
152
  end
125
153
 
126
154
  def attachment_class
127
- attachment.external? ? "hosted-externally" : "embedded"
155
+ attachment[:external?] ? "hosted-externally" : "embedded"
128
156
  end
129
157
 
130
158
  def unnumbered_paper?
131
- attachment.unnumbered_command_paper? || attachment.unnumbered_hoc_paper?
159
+ attachment[:unnumbered_command_paper?] || attachment[:unnumbered_hoc_paper?]
132
160
  end
133
161
 
134
162
  def unnumbered_command_paper?
135
- attachment.unnumbered_command_paper?
163
+ attachment[:unnumbered_command_paper?]
136
164
  end
137
165
 
138
166
  def download_link
139
- link(attachment.preview_url, "<strong>Download #{attachment.file_extension.upcase}</strong>", number_to_human_size(attachment.file_size))
167
+ options = {}
168
+ options[:title] = number_to_human_size(attachment[:file_size]) if attachment[:file_size].present?
169
+ link("<strong>Download #{file_extension.upcase}</strong>", attachment[:url], options)
140
170
  end
141
171
 
142
172
  def attachment_attributes
143
173
  attributes = []
144
- if attachment.html?
174
+ if file_extension == "html"
145
175
  attributes << content_tag(:span, 'HTML', class: 'type')
146
- elsif attachment.external?
176
+ elsif attachment[:external?]
147
177
  attributes << content_tag(:span, url, class: 'url')
148
178
  else
149
- attributes << content_tag(:span, humanized_content_type(attachment.file_extension), class: 'type')
150
- attributes << content_tag(:span, number_to_human_size(attachment.file_size), class: 'file-size')
151
- attributes << content_tag(:span, pluralize(attachment.number_of_pages, "page") , class: 'page-length') if attachment.number_of_pages.present?
179
+ attributes << content_tag(:span, humanized_content_type(file_extension), class: 'type') if file_extension
180
+ attributes << content_tag(:span, number_to_human_size(attachment[:file_size]), class: 'file-size') if attachment[:file_size]
181
+ attributes << content_tag(:span, pluralize(attachment[:number_of_pages], "page"), class: 'page-length') if attachment[:number_of_pages]
152
182
  end
153
183
  attributes.join(', ').html_safe
154
184
  end
155
185
 
156
186
  def preview_url
157
- url << '/preview'
187
+ url + '/preview'
158
188
  end
159
189
 
160
190
  MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE = "MS Word Document"
@@ -205,37 +235,52 @@ Please tell us:
205
235
  end
206
236
 
207
237
  def previewable?
208
- attachment.csv?
238
+ file_extension == "csv"
209
239
  end
210
240
 
211
241
  def title
212
- attachment.title
242
+ attachment[:title]
243
+ end
244
+
245
+ def file_extension
246
+ # Note: this is a separate parameter rather than being calculated from the
247
+ # filename because at the time of writing not all apps were using the effects
248
+ # of this field.
249
+ attachment[:file_extension]
213
250
  end
214
251
 
215
252
  def hide_thumbnail?
216
253
  defined?(hide_thumbnail) && hide_thumbnail
217
254
  end
218
255
 
219
- def attachement_details
256
+ def attachment_details
220
257
  return if previewable?
221
- link(attachment.title, url, title_link_options)
258
+ link(title, url, title_link_options)
222
259
  end
223
260
 
224
261
  def title_link_options
225
- title_link_options = ''
226
- title_link_options << "rel=external" if attachment.external?
227
- title_link_options << "aria-describedby=#{help_block_id}" unless attachment.accessible?
262
+ title_link_options = {}
263
+ title_link_options["rel"] = "external" if attachment[:external?]
264
+ title_link_options["aria-describedby"] = help_block_id unless attachment[:accessible?]
265
+ title_link_options
228
266
  end
229
267
 
230
268
  def help_block_id
231
- "attachment-#{attachment.id}-accessibility-help"
269
+ "attachment-#{id}-accessibility-help"
232
270
  end
233
271
 
234
- def link(body, url, options={})
235
- <<-END
236
- <a href="#{url} #{options}">
237
- #{body}
238
- </a>
239
- END
272
+ def link(body, url, options = {})
273
+ options_str = options.map { |k, v| %{#{encode(k)}="#{encode(v)}"} }.join(" ")
274
+ %{<a href="#{encode(url)}" #{options_str}>#{body}</a>}
275
+ end
276
+
277
+ private
278
+
279
+ def encode(text)
280
+ HTMLEntities.new.encode(text)
281
+ end
282
+
283
+ def urlencode(text)
284
+ ERB::Util.url_encode(text)
240
285
  end
241
286
  end