Class: Slamdown::Conversion::Normaliser::Content

Inherits:
Object
  • Object
show all
Defined in:
lib/slamdown/conversion/normaliser/content.rb

Overview

Owns format-neutral whitespace, phrasing-run and content-inspection rules shared by structural transformations.

Constant Summary collapse

HTML_WHITESPACE =
/[\t\n\f\r ]+/.freeze
LEADING_SPACE =
/\A +/.freeze
TRAILING_SPACE =
/ +\z/.freeze
BLOCK_SEMANTICS =
[
	:paragraph, :heading, :blockquote, :ordered_list, :unordered_list, :list_item, :definition_list, :definition_term, :definition_description,
	:horizontal_rule, :code_block, :table, :table_head, :table_body, :table_foot, :table_row, :table_cell, :table_caption, :table_colgroup, :table_column
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(node_factory) ⇒ Content

Returns a new instance of Content.



17
18
19
# File 'lib/slamdown/conversion/normaliser/content.rb', line 17

def initialize(node_factory)
	@node_factory = node_factory
end

Instance Method Details

#block_node?(node) ⇒ Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/slamdown/conversion/normaliser/content.rb', line 147

def block_node?(node)
	node.category == :block || BLOCK_SEMANTICS.include?(node.semantic_kind)
end

#descendants(nodes) ⇒ Object



139
140
141
# File 'lib/slamdown/conversion/normaliser/content.rb', line 139

def descendants(nodes)
	nodes.flat_map { |node| [node] + descendants(node.children) }
end

#normalise_code_block(value) ⇒ Object

Removes editor-layout whitespace around code while retaining meaningful indentation in already-left-aligned blocks.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/slamdown/conversion/normaliser/content.rb', line 26

def normalise_code_block(value)
	lines = value.to_s.gsub("\r\n", "\n").gsub("\r", "\n").split("\n", -1)
	lines.shift while lines.first && lines.first.strip.empty?
	lines.pop while lines.last && lines.last.strip.empty?
	return "" if lines.empty?

	lines.map! { |line| line.sub(/[\t ]+\z/, "") }
	prefixes = lines.reject { |line| line.empty? }.map { |line| line[/\A[\t ]*/] }
	if !prefixes.empty? && prefixes.none?(&:empty?)
		width = prefixes.map { |prefix| indentation_width(prefix) }.min
		lines.map! { |line| remove_indentation(line, width) }
	end
	lines.join("\n")
end

#normalise_inline_sequence(nodes, trim:) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/slamdown/conversion/normaliser/content.rb', line 62

def normalise_inline_sequence(nodes, trim:)
	result = []
	nodes.each do |node|
		if node.kind == :text && result.last && result.last.kind == :text
			result[-1] = result.last.with(:value => normalise_text(result.last.value + node.value))
		else
			result << node
		end
	end

	if trim
		result[0] = result.first.with(:value => result.first.value.sub(LEADING_SPACE, "")) if result.first && result.first.kind == :text
		result[-1] = result.last.with(:value => result.last.value.sub(TRAILING_SPACE, "")) if result.last && result.last.kind == :text
	end
	result.reject { |node| node.kind == :text && node.value.empty? }
end

#normalise_text(value) ⇒ Object



21
22
23
# File 'lib/slamdown/conversion/normaliser/content.rb', line 21

def normalise_text(value)
	value.to_s.gsub("\u00A0", " ").gsub(HTML_WHITESPACE, " ")
end

#plain_text(nodes) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/slamdown/conversion/normaliser/content.rb', line 102

def plain_text(nodes)
	nodes.map do |node|
		if node.kind == :text
			node.value
		elsif node.kind == :entity && node.value.is_a?(Hash)
			node.value[:character].to_s
		elsif node.value.is_a?(String)
			node.value
		else
			plain_text(node.children)
		end
	end.join
end

#split_around_blocks(nodes) ⇒ Object

Splits inline runs around block children so a phrasing-only block can be closed and reopened canonically.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/slamdown/conversion/normaliser/content.rb', line 42

def split_around_blocks(nodes)
	result = []
	run = []
	flush = lambda do
		result << run unless run.empty?
		run = []
	end

	nodes.each do |node|
		if block_node?(node)
			flush.call
			result << node
		else
			run << node
		end
	end
	flush.call
	result
end

#textual_content?(nodes) ⇒ Boolean

Returns:

  • (Boolean)


116
117
118
119
120
121
122
123
# File 'lib/slamdown/conversion/normaliser/content.rb', line 116

def textual_content?(nodes)
	return true unless plain_text(nodes).strip.empty?

	nodes.any? do |node|
		image_alt = node.semantic_kind == :image ? Hash[node.attributes].fetch("alt", "").to_s : nil
		[:typographic_symbol, :smart_quote].include?(node.kind) || !image_alt.to_s.strip.empty? || textual_content?(node.children)
	end
end

#visible_content?(nodes) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/slamdown/conversion/normaliser/content.rb', line 125

def visible_content?(nodes)
	nodes.any? do |node|
		if node.kind == :text
			!node.value.strip.empty?
		elsif [:entity, :typographic_symbol, :smart_quote].include?(node.kind)
			true
		elsif [:line_break, :horizontal_rule, :paragraph_boundary].include?(node.semantic_kind)
			false
		else
			textual_content?([node]) || [:image, :code_span].include?(node.semantic_kind) || node.representation == :html
		end
	end
end

#whitespace_text?(node) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/slamdown/conversion/normaliser/content.rb', line 143

def whitespace_text?(node)
	node.kind == :text && node.value.strip.empty?
end

#wrap_phrasing_runs(nodes) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/slamdown/conversion/normaliser/content.rb', line 79

def wrap_phrasing_runs(nodes)
	result = []
	run = []
	flush = lambda do
		run = normalise_inline_sequence(run, :trim => true)
		result << @node_factory.paragraph(run, run.first.source_path) if visible_content?(run)
		run = []
	end

	nodes.each do |node|
		if node.semantic_kind == :paragraph_boundary
			flush.call
		elsif block_node?(node)
			flush.call
			result << node
		else
			run << node
		end
	end
	flush.call
	result
end