govspeak 4.0.0 → 5.0.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
  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