govspeak 6.5.0 → 6.5.1
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 +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
|