govspeak 5.6.0 → 5.7.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 +5 -5
- data/CHANGELOG.md +6 -0
- data/Rakefile +1 -7
- data/bin/govspeak +1 -1
- data/lib/govspeak.rb +76 -55
- data/lib/govspeak/blockquote_extra_quote_remover.rb +3 -2
- data/lib/govspeak/cli.rb +5 -3
- data/lib/govspeak/header_extractor.rb +6 -5
- data/lib/govspeak/html_sanitizer.rb +5 -5
- data/lib/govspeak/kramdown_overrides.rb +1 -2
- data/lib/govspeak/link_extractor.rb +1 -1
- data/lib/govspeak/post_processor.rb +28 -21
- data/lib/govspeak/presenters/attachment_presenter.rb +12 -7
- data/lib/govspeak/presenters/contact_presenter.rb +55 -10
- data/lib/govspeak/presenters/h_card_presenter.rb +22 -32
- data/lib/govspeak/structured_header_extractor.rb +0 -1
- data/lib/govspeak/version.rb +1 -1
- data/lib/kramdown/parser/kramdown_with_automatic_external_links.rb +12 -10
- data/lib/templates/attachment.html.erb +9 -9
- data/lib/templates/contact.html.erb +15 -18
- data/test/govspeak_attachments_test.rb +0 -53
- data/test/govspeak_contacts_test.rb +125 -38
- data/test/govspeak_extract_contact_content_ids_test.rb +29 -0
- data/test/govspeak_link_extractor_test.rb +1 -1
- data/test/govspeak_structured_headers_test.rb +20 -21
- data/test/govspeak_test.rb +41 -40
- data/test/govspeak_test_helper.rb +7 -10
- data/test/html_sanitizer_test.rb +3 -4
- data/test/html_validator_test.rb +7 -7
- data/test/presenters/h_card_presenter_test.rb +11 -13
- data/test/test_helper.rb +6 -5
- metadata +70 -74
@@ -2,35 +2,18 @@ require 'nokogiri'
|
|
2
2
|
|
3
3
|
module Govspeak
|
4
4
|
class PostProcessor
|
5
|
-
|
6
|
-
|
7
|
-
@@extensions = []
|
8
|
-
|
9
|
-
def initialize(html)
|
10
|
-
@input = html
|
11
|
-
end
|
5
|
+
@extensions = []
|
12
6
|
|
13
|
-
def
|
14
|
-
|
15
|
-
doc.encoding = "UTF-8"
|
16
|
-
doc.fragment(input)
|
7
|
+
def self.extensions
|
8
|
+
@extensions
|
17
9
|
end
|
18
|
-
private :nokogiri_document
|
19
10
|
|
20
11
|
def self.process(html)
|
21
12
|
new(html).output
|
22
13
|
end
|
23
14
|
|
24
15
|
def self.extension(title, &block)
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
def output
|
29
|
-
document = nokogiri_document
|
30
|
-
@@extensions.each do |_, block|
|
31
|
-
instance_exec(document, &block)
|
32
|
-
end
|
33
|
-
document.to_html
|
16
|
+
@extensions << [title, block]
|
34
17
|
end
|
35
18
|
|
36
19
|
extension("add class to last p of blockquote") do |document|
|
@@ -53,6 +36,7 @@ module Govspeak
|
|
53
36
|
document.css("figure.image").map do |el|
|
54
37
|
xml = el.children.to_s
|
55
38
|
next unless xml =~ /<div class="img">|<figcaption>/
|
39
|
+
|
56
40
|
el.children = xml
|
57
41
|
.gsub(
|
58
42
|
%r{<(div class="img")>(.*?)<(/div)>},
|
@@ -64,5 +48,28 @@ module Govspeak
|
|
64
48
|
)
|
65
49
|
end
|
66
50
|
end
|
51
|
+
|
52
|
+
attr_reader :input
|
53
|
+
|
54
|
+
def initialize(html)
|
55
|
+
@input = html
|
56
|
+
end
|
57
|
+
|
58
|
+
def output
|
59
|
+
document = nokogiri_document
|
60
|
+
self.class.extensions.each do |_, block|
|
61
|
+
instance_exec(document, &block)
|
62
|
+
end
|
63
|
+
document.to_html
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def nokogiri_document
|
70
|
+
doc = Nokogiri::HTML::Document.new
|
71
|
+
doc.encoding = "UTF-8"
|
72
|
+
doc.fragment(input)
|
73
|
+
end
|
67
74
|
end
|
68
75
|
end
|
@@ -2,6 +2,8 @@ require "action_view"
|
|
2
2
|
require "money"
|
3
3
|
require "htmlentities"
|
4
4
|
|
5
|
+
Money.locale_backend = :currency
|
6
|
+
|
5
7
|
module Govspeak
|
6
8
|
class AttachmentPresenter
|
7
9
|
attr_reader :attachment
|
@@ -36,6 +38,7 @@ module Govspeak
|
|
36
38
|
|
37
39
|
def price
|
38
40
|
return unless attachment[:price]
|
41
|
+
|
39
42
|
Money.from_amount(attachment[:price], 'GBP').format
|
40
43
|
end
|
41
44
|
|
@@ -46,6 +49,7 @@ module Govspeak
|
|
46
49
|
def thumbnail_link
|
47
50
|
return if hide_thumbnail?
|
48
51
|
return if previewable?
|
52
|
+
|
49
53
|
link(attachment_thumbnail, url, "aria-hidden" => "true", "class" => attachment_class)
|
50
54
|
end
|
51
55
|
|
@@ -83,16 +87,16 @@ module Govspeak
|
|
83
87
|
end
|
84
88
|
|
85
89
|
def body_for_mail(attachment_info)
|
86
|
-
|
87
|
-
Details of document required:
|
90
|
+
<<~TEXT
|
91
|
+
Details of document required:
|
88
92
|
|
89
|
-
#{attachment_info.join("\n")}
|
93
|
+
#{attachment_info.join("\n")}
|
90
94
|
|
91
|
-
Please tell us:
|
95
|
+
Please tell us:
|
92
96
|
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
1. What makes this format unsuitable for you?
|
98
|
+
2. What format you would prefer?
|
99
|
+
TEXT
|
96
100
|
end
|
97
101
|
|
98
102
|
def alternative_format_contact_email
|
@@ -256,6 +260,7 @@ END
|
|
256
260
|
|
257
261
|
def attachment_details
|
258
262
|
return if previewable?
|
263
|
+
|
259
264
|
link(title, url, title_link_options)
|
260
265
|
end
|
261
266
|
|
@@ -1,24 +1,69 @@
|
|
1
1
|
require 'active_support/core_ext/array'
|
2
|
-
require '
|
2
|
+
require 'active_support/core_ext/hash'
|
3
3
|
|
4
4
|
module Govspeak
|
5
5
|
class ContactPresenter
|
6
6
|
attr_reader :contact
|
7
|
-
delegate :id, :content_id, :title, :recipient, :street_address, :postal_code,
|
8
|
-
:locality, :region, :country_code, :country_name, :email, :contact_form_url,
|
9
|
-
:comments, :worldwide_organisation_path, to: :contact
|
10
7
|
|
11
8
|
def initialize(contact)
|
12
|
-
@contact =
|
9
|
+
@contact = ActiveSupport::HashWithIndifferentAccess.new(contact)
|
13
10
|
end
|
14
11
|
|
15
|
-
def
|
16
|
-
|
12
|
+
def content_id
|
13
|
+
contact[:content_id]
|
17
14
|
end
|
18
15
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
16
|
+
def title
|
17
|
+
contact[:title]
|
18
|
+
end
|
19
|
+
|
20
|
+
def description
|
21
|
+
contact[:description]
|
22
|
+
end
|
23
|
+
|
24
|
+
def post_addresses
|
25
|
+
@post_addresses ||= begin
|
26
|
+
addresses = contact.dig(:details, :post_addresses) || []
|
27
|
+
filter_post_addresses(addresses)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def email_addresses
|
32
|
+
@email_addresses ||= begin
|
33
|
+
emails = contact.dig(:details, :email_addresses) || []
|
34
|
+
emails.select { |e| e[:email].present? }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def phone_numbers
|
39
|
+
@phone_numbers ||= begin
|
40
|
+
phone_numbers = contact.dig(:details, :phone_numbers) || []
|
41
|
+
phone_numbers.select { |p| p[:number].present? }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def contact_form_links
|
46
|
+
@contact_form_links ||= begin
|
47
|
+
contact_form_links = contact.dig(:details, :contact_form_links) || []
|
48
|
+
contact_form_links.select { |c| c[:link].present? }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def filter_post_addresses(addresses)
|
55
|
+
addresses.each do |address|
|
56
|
+
# A lot of the postal addresses published to the Publishing API have
|
57
|
+
# a populated array of postal addresses but each field an empty string :-(
|
58
|
+
address.delete_if do |key, value|
|
59
|
+
# Not showing United Kingdom country is a "feature" lifted and shifted
|
60
|
+
# from Whitehall:
|
61
|
+
# https://github.com/alphagov/whitehall/blob/c67d53d80f9856549c2da1941a10dbb9170be494/lib/address_formatter/formatter.rb#L17
|
62
|
+
(key == "world_location" && value.strip == "United Kingdom" || value == "")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
addresses.reject(&:empty?)
|
22
67
|
end
|
23
68
|
end
|
24
69
|
end
|
@@ -1,46 +1,26 @@
|
|
1
1
|
module Govspeak
|
2
2
|
class HCardPresenter
|
3
|
-
def self.from_contact(contact)
|
4
|
-
new(contact_properties(contact), contact.country_code)
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.contact_properties(contact)
|
8
|
-
{ 'fn' => contact.recipient,
|
9
|
-
'street-address' => contact.street_address,
|
10
|
-
'postal-code' => contact.postal_code,
|
11
|
-
'locality' => contact.locality,
|
12
|
-
'region' => contact.region,
|
13
|
-
'country-name' => country_name(contact) }
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.country_name(contact)
|
17
|
-
contact.country_name unless contact.country_code == 'GB'
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.property_keys
|
21
|
-
['fn', 'street-address', 'postal-code', 'locality', 'region', 'country-name']
|
22
|
-
end
|
23
|
-
|
24
3
|
def self.address_formats
|
25
|
-
@address_formats ||= YAML.load_file(
|
4
|
+
@address_formats ||= YAML.load_file(
|
5
|
+
File.expand_path('config/address_formats.yml', Govspeak.root)
|
6
|
+
)
|
26
7
|
end
|
27
8
|
|
28
|
-
attr_reader :
|
9
|
+
attr_reader :contact_address
|
29
10
|
|
30
|
-
def initialize(
|
31
|
-
@
|
32
|
-
@country_code = country_code
|
11
|
+
def initialize(contact_address)
|
12
|
+
@contact_address = contact_address
|
33
13
|
end
|
34
14
|
|
35
15
|
def render
|
36
16
|
"<p class=\"adr\">\n#{interpolate_address_template}\n</p>\n".html_safe
|
37
17
|
end
|
38
18
|
|
39
|
-
def interpolate_address_property(
|
40
|
-
value =
|
19
|
+
def interpolate_address_property(our_name, hcard_name)
|
20
|
+
value = contact_address[our_name]
|
41
21
|
|
42
22
|
if value.present?
|
43
|
-
"<span class=\"#{
|
23
|
+
"<span class=\"#{hcard_name}\">#{ERB::Util.html_escape(value)}</span>"
|
44
24
|
else
|
45
25
|
""
|
46
26
|
end
|
@@ -51,8 +31,17 @@ module Govspeak
|
|
51
31
|
def interpolate_address_template
|
52
32
|
address = address_template
|
53
33
|
|
54
|
-
|
55
|
-
|
34
|
+
properties = {
|
35
|
+
title: "fn",
|
36
|
+
street_address: "street-address",
|
37
|
+
locality: "locality",
|
38
|
+
region: "region",
|
39
|
+
postal_code: "postal-code",
|
40
|
+
world_location: "country-name",
|
41
|
+
}
|
42
|
+
|
43
|
+
properties.each do |our_name, hcard_name|
|
44
|
+
address.gsub!(/\{\{#{hcard_name}\}\}/, interpolate_address_property(our_name, hcard_name))
|
56
45
|
end
|
57
46
|
|
58
47
|
address.gsub(/^\n/, '') # get rid of blank lines
|
@@ -61,7 +50,8 @@ module Govspeak
|
|
61
50
|
end
|
62
51
|
|
63
52
|
def address_template
|
64
|
-
|
53
|
+
country_code = contact_address[:iso2_country_code].to_s.downcase
|
54
|
+
(self.class.address_formats[country_code] || default_format_string).dup
|
65
55
|
end
|
66
56
|
|
67
57
|
def default_format_string
|
data/lib/govspeak/version.rb
CHANGED
@@ -4,17 +4,17 @@ require "kramdown/options"
|
|
4
4
|
module Kramdown
|
5
5
|
module Options
|
6
6
|
class AlwaysEqual
|
7
|
-
def ==(
|
7
|
+
def ==(_other)
|
8
8
|
true
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
define(:document_domains, Object, %w{www.gov.uk},
|
13
|
-
Defines the domains which are considered local to the document
|
12
|
+
define(:document_domains, Object, %w{www.gov.uk}, <<~DESCRIPTION) do |val|
|
13
|
+
Defines the domains which are considered local to the document
|
14
14
|
|
15
|
-
Default: www.gov.uk
|
16
|
-
Used by: KramdownWithAutomaticExternalLinks
|
17
|
-
|
15
|
+
Default: www.gov.uk
|
16
|
+
Used by: KramdownWithAutomaticExternalLinks
|
17
|
+
DESCRIPTION
|
18
18
|
simple_array_validator(val, :document_domains, AlwaysEqual.new)
|
19
19
|
end
|
20
20
|
end
|
@@ -26,16 +26,18 @@ EOF
|
|
26
26
|
super
|
27
27
|
end
|
28
28
|
|
29
|
-
def add_link(
|
30
|
-
if
|
29
|
+
def add_link(element, href, title, alt_text = nil, ial = nil)
|
30
|
+
if element.type == :a
|
31
31
|
begin
|
32
32
|
host = Addressable::URI.parse(href).host
|
33
|
-
unless host.nil? ||
|
34
|
-
|
33
|
+
unless host.nil? || @document_domains.compact.include?(host)
|
34
|
+
element.attr['rel'] = 'external'
|
35
35
|
end
|
36
|
+
# rubocop:disable Lint/HandleExceptions
|
36
37
|
rescue Addressable::URI::InvalidURIError
|
37
38
|
# it's safe to ignore these very *specific* exceptions
|
38
39
|
end
|
40
|
+
# rubocop:enable Lint/HandleExceptions
|
39
41
|
end
|
40
42
|
super
|
41
43
|
end
|
@@ -6,14 +6,14 @@
|
|
6
6
|
<h2 class="title"><%= attachment.attachment_details %></h2>
|
7
7
|
<p class="metadata">
|
8
8
|
<% if attachment.references? %>
|
9
|
-
<span class="references"><%= t('attachment.headings.reference') %>: <%= attachment.reference %></span>
|
9
|
+
<span class="references"><%= t('govspeak.attachment.headings.reference') %>: <%= attachment.reference %></span>
|
10
10
|
<% end %>
|
11
11
|
<% if attachment.unnumbered_paper? %>
|
12
12
|
<span class="unnumbered-paper">
|
13
13
|
<% if attachment.unnumbered_command_paper? %>
|
14
|
-
<%= t('attachment.headings.unnumbered_command_paper') %>
|
14
|
+
<%= t('govspeak.attachment.headings.unnumbered_command_paper') %>
|
15
15
|
<% else %>
|
16
|
-
<%= t('attachment.headings.unnumbered_hoc_paper') %>
|
16
|
+
<%= t('govspeak.attachment.headings.unnumbered_hoc_paper') %>
|
17
17
|
<% end %>
|
18
18
|
</span>
|
19
19
|
<% end %>
|
@@ -28,25 +28,25 @@
|
|
28
28
|
</p>
|
29
29
|
<% if attachment.order_url.present? %>
|
30
30
|
<p>
|
31
|
-
<%= attachment.link t('attachment.headings.order_a_copy'), attachment.order_url,
|
32
|
-
class: "order_url", title: t('attachment.headings.order_a_copy_full')
|
31
|
+
<%= attachment.link t('govspeak.attachment.headings.order_a_copy'), attachment.order_url,
|
32
|
+
class: "order_url", title: t('govspeak.attachment.headings.order_a_copy_full')
|
33
33
|
%><% if attachment.price %>(<span class="price"><%= attachment.price %></span>)<% end %>
|
34
34
|
</p>
|
35
35
|
<% end %>
|
36
36
|
|
37
37
|
<% if attachment.opendocument? %>
|
38
38
|
<p class="opendocument-help">
|
39
|
-
<%= t('attachment.opendocument.help_html') %>
|
39
|
+
<%= t('govspeak.attachment.opendocument.help_html') %>
|
40
40
|
</p>
|
41
41
|
<% end %>
|
42
42
|
|
43
43
|
<% unless attachment.accessible? %>
|
44
44
|
<div data-module="toggle" class="accessibility-warning" id="<%= attachment.help_block_id %>">
|
45
|
-
<h2><%= t('attachment.accessibility.heading') %>
|
46
|
-
<a class="toggler" href="#<%= attachment.help_block_toggle_id %>" data-controls="<%= attachment.help_block_toggle_id %>" data-expanded="false"><%= t('attachment.accessibility.request_a_different_format') %></a>
|
45
|
+
<h2><%= t('govspeak.attachment.accessibility.heading') %>
|
46
|
+
<a class="toggler" href="#<%= attachment.help_block_toggle_id %>" data-controls="<%= attachment.help_block_toggle_id %>" data-expanded="false"><%= t('govspeak.attachment.accessibility.request_a_different_format') %></a>
|
47
47
|
</h2>
|
48
48
|
<p id="<%= attachment.help_block_toggle_id %>" class="js-hidden">
|
49
|
-
<%= t('attachment.accessibility.full_help_html',
|
49
|
+
<%= t('govspeak.attachment.accessibility.full_help_html',
|
50
50
|
email: attachment.alternative_format_order_link,
|
51
51
|
title: attachment.title,
|
52
52
|
references: attachment.references_for_title) %>
|
@@ -1,41 +1,38 @@
|
|
1
1
|
<%
|
2
2
|
extra_class = []
|
3
|
-
extra_class << 'postal-address' if contact.
|
3
|
+
extra_class << 'postal-address' if contact.post_addresses.any?
|
4
4
|
%>
|
5
|
-
<div id="contact_<%= contact.
|
5
|
+
<div id="contact_<%= contact.content_id %>" class="<%= ['contact', extra_class].flatten.join(' ') %>">
|
6
6
|
<div class="content">
|
7
7
|
<h3><%= contact.title %></h3>
|
8
8
|
<div class="vcard contact-inner">
|
9
|
-
<%
|
10
|
-
<%= render_hcard_address(
|
9
|
+
<% contact.post_addresses.each do |address| %>
|
10
|
+
<%= render_hcard_address(address) %>
|
11
11
|
<% end %>
|
12
|
-
<% if contact.
|
12
|
+
<% if contact.email_addresses.any? || contact.phone_numbers.any? || contact.contact_form_links.any? %>
|
13
13
|
<div class="email-url-number">
|
14
|
-
<%
|
14
|
+
<% contact.email_addresses.each do |email| %>
|
15
15
|
<p class="email">
|
16
|
-
<span class="type"><%= t('contact.email') %></span>
|
17
|
-
<a href="mailto:<%=
|
16
|
+
<span class="type"><%= t('govspeak.contact.email') %></span>
|
17
|
+
<a href="mailto:<%= email[:email] %>" class="email"><%= email[:title].present? ? email[:title] : email[:email] %></a>
|
18
18
|
</p>
|
19
19
|
<% end %>
|
20
|
-
<%
|
20
|
+
<% contact.contact_form_links.each do |form_link| %>
|
21
21
|
<p class="contact_form_url">
|
22
|
-
<span class="type"><%= t('contact.contact_form') %></span>
|
23
|
-
<a href="<%=
|
22
|
+
<span class="type"><%= t('govspeak.contact.contact_form') %></span>
|
23
|
+
<a href="<%= form_link[:link] %>"><%= form_link[:link].truncate(25) %></a>
|
24
24
|
</p>
|
25
25
|
<% end %>
|
26
|
-
<% contact.
|
26
|
+
<% contact.phone_numbers.each do |number| %>
|
27
27
|
<p class="tel">
|
28
|
-
<span class="type"><%= number[:
|
28
|
+
<span class="type"><%= number[:title] %></span>
|
29
29
|
<%= number[:number] %>
|
30
30
|
</p>
|
31
31
|
<% end %>
|
32
32
|
</div>
|
33
33
|
<% end %>
|
34
|
-
<% if contact.
|
35
|
-
<p class="comments"><%= auto_link(format_with_html_line_breaks(
|
36
|
-
<% end %>
|
37
|
-
<% if contact.worldwide_organisation_path %>
|
38
|
-
<a href="<%= contact.worldwide_organisation_path %>">Access and opening times</a>
|
34
|
+
<% if contact.description.present? %>
|
35
|
+
<p class="comments"><%= Rinku.auto_link(format_with_html_line_breaks(contact.description)) %></p>
|
39
36
|
<% end %>
|
40
37
|
</div>
|
41
38
|
</div>
|