govspeak 5.6.0 → 5.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|