govspeak 6.5.0 → 6.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -2
- data/Rakefile +1 -1
- data/lib/govspeak.rb +74 -74
- data/lib/govspeak/header_extractor.rb +1 -1
- data/lib/govspeak/html_sanitizer.rb +5 -5
- data/lib/govspeak/link_extractor.rb +3 -3
- data/lib/govspeak/post_processor.rb +12 -12
- data/lib/govspeak/presenters/attachment_presenter.rb +32 -32
- data/lib/govspeak/presenters/contact_presenter.rb +2 -2
- data/lib/govspeak/presenters/h_card_presenter.rb +3 -3
- data/lib/govspeak/presenters/image_presenter.rb +2 -2
- data/lib/govspeak/version.rb +1 -1
- data/lib/kramdown/parser/govuk.rb +1 -1
- data/test/blockquote_extra_quote_remover_test.rb +10 -10
- data/test/govspeak_attachments_image_test.rb +10 -10
- data/test/govspeak_attachments_inline_test.rb +18 -18
- data/test/govspeak_button_test.rb +4 -4
- data/test/govspeak_contacts_test.rb +18 -18
- data/test/govspeak_extract_contact_content_ids_test.rb +1 -1
- data/test/govspeak_images_bang_test.rb +14 -14
- data/test/govspeak_images_test.rb +16 -16
- data/test/govspeak_link_test.rb +1 -1
- data/test/govspeak_structured_headers_test.rb +2 -2
- data/test/govspeak_table_with_headers_test.rb +1 -1
- data/test/govspeak_test.rb +18 -18
- data/test/html_sanitizer_test.rb +8 -8
- data/test/html_validator_test.rb +2 -2
- data/test/presenters/h_card_presenter_test.rb +39 -39
- data/test/test_helper.rb +6 -6
- metadata +38 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1153c71bd7cd224750aa162c1ea63c89a902309cd08238a888d07a60f079d89
|
4
|
+
data.tar.gz: a7efca4529a1c6017224a77965e45994d30eba3a3e05f6d209dadff85a516cd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33209bc51e340ccc5a2a0dc3b43f887eddbd464b687232fb4a17c6c34ae0a3097321f7f51da3ba17762e6ad1307424204872c3374dfa99eeb0100992641f4911
|
7
|
+
data.tar.gz: ab00d13be257f7730b4b7c2a858a9a9eba3802148dfc851f60fdf91eb65acdb80510a5c67a1e599f2cedbf945502cf29d6567526d695872118025ebb384318f9
|
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
+
## 6.5.1
|
2
|
+
* Change unicode testing characters after external gem change
|
3
|
+
* Move from govuk-lint to rubocop-govuk
|
4
|
+
* Allow version 6 of actionview
|
5
|
+
|
1
6
|
## 6.5.0
|
2
7
|
|
3
8
|
* Allow data attributes on links
|
4
9
|
|
5
10
|
## 6.4.0
|
6
11
|
|
7
|
-
|
12
|
+
* Add table heading syntax that allows a table cell outside of `thead` to be marked as a table heading with a scope of row. (PR#161)
|
8
13
|
|
9
|
-
|
14
|
+
## 6.3.0
|
10
15
|
|
11
16
|
* Unicode characters forbidden in HTML are stripped from input
|
12
17
|
* Validation is now more lenient for HTML input
|
data/Rakefile
CHANGED
data/lib/govspeak.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
24
|
-
require
|
25
|
-
require
|
1
|
+
require "active_support/core_ext/hash"
|
2
|
+
require "active_support/core_ext/array"
|
3
|
+
require "erb"
|
4
|
+
require "govuk_publishing_components"
|
5
|
+
require "htmlentities"
|
6
|
+
require "kramdown"
|
7
|
+
require "kramdown/parser/govuk"
|
8
|
+
require "nokogiri"
|
9
|
+
require "nokogumbo"
|
10
|
+
require "rinku"
|
11
|
+
require "sanitize"
|
12
|
+
require "govspeak/header_extractor"
|
13
|
+
require "govspeak/structured_header_extractor"
|
14
|
+
require "govspeak/html_validator"
|
15
|
+
require "govspeak/html_sanitizer"
|
16
|
+
require "govspeak/kramdown_overrides"
|
17
|
+
require "govspeak/blockquote_extra_quote_remover"
|
18
|
+
require "govspeak/post_processor"
|
19
|
+
require "govspeak/link_extractor"
|
20
|
+
require "govspeak/template_renderer"
|
21
|
+
require "govspeak/presenters/attachment_presenter"
|
22
|
+
require "govspeak/presenters/contact_presenter"
|
23
|
+
require "govspeak/presenters/h_card_presenter"
|
24
|
+
require "govspeak/presenters/image_presenter"
|
25
|
+
require "govspeak/presenters/attachment_image_presenter"
|
26
26
|
|
27
27
|
module Govspeak
|
28
28
|
def self.root
|
29
|
-
File.expand_path(
|
29
|
+
File.expand_path("..", File.dirname(__FILE__))
|
30
30
|
end
|
31
31
|
|
32
32
|
class Document
|
@@ -44,8 +44,8 @@ module Govspeak
|
|
44
44
|
new(source, options).to_html
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
class << self
|
48
|
+
attr_reader :extensions
|
49
49
|
end
|
50
50
|
|
51
51
|
def initialize(source, options = {})
|
@@ -118,7 +118,7 @@ module Govspeak
|
|
118
118
|
def remove_forbidden_characters(source)
|
119
119
|
# These are characters that are not deemed not suitable for
|
120
120
|
# markup: https://www.w3.org/TR/unicode-xml/#Charlist
|
121
|
-
source.gsub(Sanitize::REGEX_UNSUITABLE_CHARS,
|
121
|
+
source.gsub(Sanitize::REGEX_UNSUITABLE_CHARS, "")
|
122
122
|
end
|
123
123
|
|
124
124
|
def self.extension(title, regexp = nil, &block)
|
@@ -147,7 +147,7 @@ module Govspeak
|
|
147
147
|
parser.new(body.strip).to_html.sub(/^<p>(.*)<\/p>$/, "<p><strong>\\1</strong></p>")
|
148
148
|
end
|
149
149
|
|
150
|
-
extension(
|
150
|
+
extension("button", %r{
|
151
151
|
(?:\r|\n|^) # non-capturing match to make sure start of line and linebreak
|
152
152
|
{button(.*?)} # match opening bracket and capture attributes
|
153
153
|
\s* # any whitespace between opening bracket and link
|
@@ -176,51 +176,51 @@ module Govspeak
|
|
176
176
|
%{\n<a role="button" class="#{button_classes}" href="#{href}" #{data_attribute}>#{text}</a>\n}
|
177
177
|
}
|
178
178
|
|
179
|
-
extension(
|
179
|
+
extension("highlight-answer") { |body|
|
180
180
|
%{\n\n<div class="highlight-answer">
|
181
181
|
#{Govspeak::Document.new(body.strip).to_html}</div>\n}
|
182
182
|
}
|
183
183
|
|
184
|
-
extension(
|
184
|
+
extension("stat-headline", %r${stat-headline}(.*?){/stat-headline}$m) { |body|
|
185
185
|
%{\n\n<aside class="stat-headline">
|
186
186
|
#{Govspeak::Document.new(body.strip).to_html}</aside>\n}
|
187
187
|
}
|
188
188
|
|
189
189
|
# FIXME: these surrounded_by arguments look dodgy
|
190
|
-
extension(
|
190
|
+
extension("external", surrounded_by("x[", ")x")) { |body|
|
191
191
|
Kramdown::Document.new("[#{body.strip}){:rel='external'}").to_html
|
192
192
|
}
|
193
193
|
|
194
|
-
extension(
|
194
|
+
extension("informational", surrounded_by("^")) { |body|
|
195
195
|
%{\n\n<div role="note" aria-label="Information" class="application-notice info-notice">
|
196
196
|
#{Govspeak::Document.new(body.strip).to_html}</div>\n}
|
197
197
|
}
|
198
198
|
|
199
|
-
extension(
|
199
|
+
extension("important", surrounded_by("@")) { |body|
|
200
200
|
%{\n\n<div role="note" aria-label="Important" class="advisory">#{insert_strong_inside_p(body)}</div>\n}
|
201
201
|
}
|
202
202
|
|
203
|
-
extension(
|
203
|
+
extension("helpful", surrounded_by("%")) { |body|
|
204
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
|
}
|
206
206
|
|
207
|
-
extension(
|
208
|
-
stacked =
|
209
|
-
compact =
|
210
|
-
negative =
|
207
|
+
extension("barchart", /{barchart(.*?)}/) do |captures|
|
208
|
+
stacked = ".mc-stacked" if captures.include? "stacked"
|
209
|
+
compact = ".compact" if captures.include? "compact"
|
210
|
+
negative = ".mc-negative" if captures.include? "negative"
|
211
211
|
|
212
212
|
[
|
213
|
-
|
214
|
-
|
213
|
+
"{:",
|
214
|
+
".js-barchart-table",
|
215
215
|
stacked,
|
216
216
|
compact,
|
217
217
|
negative,
|
218
|
-
|
219
|
-
|
220
|
-
].join(
|
218
|
+
".mc-auto-outdent",
|
219
|
+
"}",
|
220
|
+
].join(" ")
|
221
221
|
end
|
222
222
|
|
223
|
-
extension(
|
223
|
+
extension("attached-image", /^!!([0-9]+)/) do |image_number|
|
224
224
|
image = images[image_number.to_i - 1]
|
225
225
|
next "" unless image
|
226
226
|
|
@@ -228,7 +228,7 @@ module Govspeak
|
|
228
228
|
end
|
229
229
|
|
230
230
|
# DEPRECATED: use 'AttachmentLink:attachment-id' instead
|
231
|
-
extension(
|
231
|
+
extension("embed attachment inline", /\[embed:attachments:inline:\s*(.*?)\s*\]/) do |content_id|
|
232
232
|
attachment = attachments.detect { |a| a[:content_id] == content_id }
|
233
233
|
next "" unless attachment
|
234
234
|
|
@@ -243,7 +243,7 @@ module Govspeak
|
|
243
243
|
end
|
244
244
|
|
245
245
|
# DEPRECATED: use 'Image:image-id' instead
|
246
|
-
extension(
|
246
|
+
extension("attachment image", /\[embed:attachments:image:\s*(.*?)\s*\]/) do |content_id|
|
247
247
|
attachment = attachments.detect { |a| a[:content_id] == content_id }
|
248
248
|
next "" unless attachment
|
249
249
|
|
@@ -266,29 +266,29 @@ module Govspeak
|
|
266
266
|
lines << %{<figure#{id_attr} class="image embedded">}
|
267
267
|
lines << %{<div class="img"><img src="#{encode(image.url)}" alt="#{encode(image.alt_text)}"></div>}
|
268
268
|
lines << image.figcaption_html if image.figcaption?
|
269
|
-
lines <<
|
269
|
+
lines << "</figure>"
|
270
270
|
lines.join
|
271
271
|
end
|
272
272
|
|
273
|
-
wrap_with_div(
|
274
|
-
wrap_with_div(
|
275
|
-
wrap_with_div(
|
276
|
-
wrap_with_div(
|
277
|
-
wrap_with_div(
|
278
|
-
wrap_with_div(
|
279
|
-
wrap_with_div(
|
280
|
-
wrap_with_div(
|
273
|
+
wrap_with_div("summary", "$!")
|
274
|
+
wrap_with_div("form-download", "$D")
|
275
|
+
wrap_with_div("contact", "$C")
|
276
|
+
wrap_with_div("place", "$P", Govspeak::Document)
|
277
|
+
wrap_with_div("information", "$I", Govspeak::Document)
|
278
|
+
wrap_with_div("additional-information", "$AI")
|
279
|
+
wrap_with_div("example", "$E", Govspeak::Document)
|
280
|
+
wrap_with_div("call-to-action", "$CTA", Govspeak::Document)
|
281
281
|
|
282
|
-
extension(
|
282
|
+
extension("address", surrounded_by("$A")) { |body|
|
283
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
|
}
|
285
285
|
|
286
286
|
extension("legislative list", /#{NEW_PARAGRAPH_LOOKBEHIND}\$LegislativeList\s*$(.*?)\$EndLegislativeList/m) do |body|
|
287
287
|
Govspeak::KramdownOverrides.with_kramdown_ordered_lists_disabled do
|
288
288
|
Kramdown::Document.new(body.strip).to_html.tap do |doc|
|
289
|
-
doc.gsub!(
|
290
|
-
doc.gsub!(
|
291
|
-
doc.sub!(
|
289
|
+
doc.gsub!("<ul>", "<ol>")
|
290
|
+
doc.gsub!("</ul>", "</ol>")
|
291
|
+
doc.sub!("<ol>", '<ol class="legislative-list">')
|
292
292
|
end
|
293
293
|
end
|
294
294
|
end
|
@@ -301,12 +301,12 @@ module Govspeak
|
|
301
301
|
end
|
302
302
|
|
303
303
|
def self.devolved_options
|
304
|
-
{
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
304
|
+
{ "scotland" => "Scotland",
|
305
|
+
"england" => "England",
|
306
|
+
"england-wales" => "England and Wales",
|
307
|
+
"northern-ireland" => "Northern Ireland",
|
308
|
+
"wales" => "Wales",
|
309
|
+
"london" => "London" }
|
310
310
|
end
|
311
311
|
|
312
312
|
devolved_options.each do |k, v|
|
@@ -333,7 +333,7 @@ module Govspeak
|
|
333
333
|
end
|
334
334
|
end
|
335
335
|
|
336
|
-
extension(
|
336
|
+
extension("embed link", /\[embed:link:\s*(.*?)\s*\]/) do |content_id|
|
337
337
|
link = links.detect { |l| l[:content_id] == content_id }
|
338
338
|
next "" unless link
|
339
339
|
|
@@ -344,28 +344,28 @@ module Govspeak
|
|
344
344
|
end
|
345
345
|
end
|
346
346
|
|
347
|
-
extension(
|
347
|
+
extension("Contact", /\[Contact:\s*(.*?)\s*\]/) do |content_id|
|
348
348
|
contact = contacts.detect { |c| c[:content_id] == content_id }
|
349
349
|
next "" unless contact
|
350
350
|
|
351
|
-
renderer = TemplateRenderer.new(
|
351
|
+
renderer = TemplateRenderer.new("contact.html.erb", locale)
|
352
352
|
renderer.render(contact: ContactPresenter.new(contact))
|
353
353
|
end
|
354
354
|
|
355
|
-
extension(
|
355
|
+
extension("Image", /#{NEW_PARAGRAPH_LOOKBEHIND}\[Image:\s*(.*?)\s*\]/) do |image_id|
|
356
356
|
image = images.detect { |c| c.is_a?(Hash) && c[:id] == image_id }
|
357
357
|
next "" unless image
|
358
358
|
|
359
359
|
render_image(ImagePresenter.new(image))
|
360
360
|
end
|
361
361
|
|
362
|
-
extension(
|
362
|
+
extension("Attachment", /#{NEW_PARAGRAPH_LOOKBEHIND}\[Attachment:\s*(.*?)\s*\]/) do |attachment_id|
|
363
363
|
next "" if attachments.none? { |a| a[:id] == attachment_id }
|
364
364
|
|
365
365
|
%{<govspeak-embed-attachment id="#{attachment_id}"></govspeak-embed-attachment>}
|
366
366
|
end
|
367
367
|
|
368
|
-
extension(
|
368
|
+
extension("AttachmentLink", /\[AttachmentLink:\s*(.*?)\s*\]/) do |attachment_id|
|
369
369
|
next "" if attachments.none? { |a| a[:id] == attachment_id }
|
370
370
|
|
371
371
|
%{<govspeak-embed-attachment-link id="#{attachment_id}"></govspeak-embed-attachment-link>}
|
@@ -384,5 +384,5 @@ module Govspeak
|
|
384
384
|
end
|
385
385
|
|
386
386
|
I18n.load_path.unshift(
|
387
|
-
*Dir.glob(File.expand_path(
|
387
|
+
*Dir.glob(File.expand_path("locales/*.yml", Govspeak.root)),
|
388
388
|
)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "addressable/uri"
|
2
2
|
|
3
3
|
class Govspeak::HtmlSanitizer
|
4
4
|
class ImageSourceWhitelister
|
@@ -10,7 +10,7 @@ class Govspeak::HtmlSanitizer
|
|
10
10
|
return unless sanitize_context[:node_name] == "img"
|
11
11
|
|
12
12
|
node = sanitize_context[:node]
|
13
|
-
image_uri = Addressable::URI.parse(node[
|
13
|
+
image_uri = Addressable::URI.parse(node["src"])
|
14
14
|
unless image_uri.relative? || @allowed_image_hosts.include?(image_uri.host)
|
15
15
|
node.unlink # the node isn't sanitary. Remove it from the document.
|
16
16
|
end
|
@@ -25,8 +25,8 @@ class Govspeak::HtmlSanitizer
|
|
25
25
|
|
26
26
|
# Kramdown uses text-align to allow table cells to be aligned
|
27
27
|
# http://kramdown.gettalong.org/quickref.html#tables
|
28
|
-
if invalid_style_attribute?(node[
|
29
|
-
node.remove_attribute(
|
28
|
+
if invalid_style_attribute?(node["style"])
|
29
|
+
node.remove_attribute("style")
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -58,7 +58,7 @@ class Govspeak::HtmlSanitizer
|
|
58
58
|
"th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
|
59
59
|
"td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
|
60
60
|
"govspeak-embed-attachment" => %w[content-id],
|
61
|
-
}
|
61
|
+
},
|
62
62
|
)
|
63
63
|
end
|
64
64
|
end
|
@@ -20,8 +20,8 @@ module Govspeak
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def extract_href_from_link(link)
|
23
|
-
href = link[
|
24
|
-
if website_root && href.start_with?(
|
23
|
+
href = link["href"] || ""
|
24
|
+
if website_root && href.start_with?("/")
|
25
25
|
"#{website_root}#{href}"
|
26
26
|
else
|
27
27
|
href
|
@@ -29,7 +29,7 @@ module Govspeak
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def document_anchors
|
32
|
-
processed_govspeak.css(
|
32
|
+
processed_govspeak.css("a[href]").css('a:not([href^="mailto"])').css('a:not([href^="#"])')
|
33
33
|
end
|
34
34
|
|
35
35
|
def processed_govspeak
|
@@ -2,8 +2,8 @@ module Govspeak
|
|
2
2
|
class PostProcessor
|
3
3
|
@extensions = []
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
class << self
|
6
|
+
attr_reader :extensions
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.process(html, govspeak_document)
|
@@ -38,11 +38,11 @@ module Govspeak
|
|
38
38
|
el.children = xml
|
39
39
|
.gsub(
|
40
40
|
%r{<(div class="img")>(.*?)<(/div)>},
|
41
|
-
"<\\1>\\2<\\3>"
|
41
|
+
"<\\1>\\2<\\3>",
|
42
42
|
)
|
43
43
|
.gsub(
|
44
44
|
%r{<(figcaption)>(.*?)<(/figcaption&)gt;},
|
45
|
-
"<\\1>\\2<\\3>"
|
45
|
+
"<\\1>\\2<\\3>",
|
46
46
|
)
|
47
47
|
end
|
48
48
|
end
|
@@ -59,7 +59,7 @@ module Govspeak
|
|
59
59
|
attachment_html = GovukPublishingComponents.render(
|
60
60
|
"govuk_publishing_components/components/attachment",
|
61
61
|
attachment: attachment,
|
62
|
-
locale: govspeak_document.locale
|
62
|
+
locale: govspeak_document.locale,
|
63
63
|
)
|
64
64
|
el.swap(attachment_html)
|
65
65
|
end
|
@@ -77,7 +77,7 @@ module Govspeak
|
|
77
77
|
attachment_html = GovukPublishingComponents.render(
|
78
78
|
"govuk_publishing_components/components/attachment_link",
|
79
79
|
attachment: attachment,
|
80
|
-
locale: govspeak_document.locale
|
80
|
+
locale: govspeak_document.locale,
|
81
81
|
)
|
82
82
|
el.swap(attachment_html)
|
83
83
|
end
|
@@ -85,17 +85,17 @@ module Govspeak
|
|
85
85
|
|
86
86
|
extension("Add table headers and row / column scopes") do |document|
|
87
87
|
document.css("thead th").map do |el|
|
88
|
-
el.content = el.content.gsub(/^# /,
|
89
|
-
el.content = el.content.gsub(/[[:space:]]/,
|
90
|
-
el.name =
|
88
|
+
el.content = el.content.gsub(/^# /, "")
|
89
|
+
el.content = el.content.gsub(/[[:space:]]/, "") if el.content.blank? # Removes a strange whitespace in the cell if the cell is already blank.
|
90
|
+
el.name = "td" if el.content.blank? # This prevents a `th` with nothing inside it; a `td` is preferable.
|
91
91
|
el[:scope] = "col" if el.content.present? # `scope` shouldn't be used if there's nothing in the table heading.
|
92
92
|
end
|
93
93
|
|
94
94
|
document.css(":not(thead) tr td:first-child").map do |el|
|
95
95
|
if el.content.match?(/^#($|\s.*$)/)
|
96
|
-
el.content = el.content.gsub(/^#($|\s)/,
|
97
|
-
el.name =
|
98
|
-
el[:scope] =
|
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
99
|
end
|
100
100
|
end
|
101
101
|
end
|