paru 0.1.0 → 0.2.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 +4 -4
- data/lib/paru.rb +2 -2
- data/lib/paru/error.rb +2 -2
- data/lib/paru/filter.rb +69 -70
- data/lib/paru/filter/alignment.rb +7 -7
- data/lib/paru/filter/ast_manipulation.rb +39 -39
- data/lib/paru/filter/attr.rb +30 -32
- data/lib/paru/filter/block.rb +7 -7
- data/lib/paru/filter/block_quote.rb +8 -7
- data/lib/paru/filter/bullet_list.rb +6 -5
- data/lib/paru/filter/citation.rb +23 -23
- data/lib/paru/filter/cite.rb +19 -18
- data/lib/paru/filter/code.rb +23 -22
- data/lib/paru/filter/code_block.rb +20 -19
- data/lib/paru/filter/definition_list.rb +14 -13
- data/lib/paru/filter/definition_list_item.rb +17 -17
- data/lib/paru/filter/div.rb +19 -18
- data/lib/paru/filter/document.rb +56 -42
- data/lib/paru/filter/emph.rb +5 -4
- data/lib/paru/filter/empty_block.rb +17 -0
- data/lib/paru/filter/empty_inline.rb +21 -0
- data/lib/paru/filter/header.rb +22 -21
- data/lib/paru/filter/horizontal_rule.rb +5 -7
- data/lib/paru/filter/image.rb +10 -4
- data/lib/paru/filter/line_block.rb +9 -0
- data/lib/paru/filter/line_break.rb +5 -12
- data/lib/paru/filter/link.rb +21 -20
- data/lib/paru/filter/list.rb +16 -16
- data/lib/paru/filter/list_attributes.rb +31 -18
- data/lib/paru/filter/markdown.rb +62 -42
- data/lib/paru/filter/math.rb +46 -48
- data/lib/paru/filter/meta.rb +15 -16
- data/lib/paru/filter/meta_blocks.rb +8 -7
- data/lib/paru/filter/meta_bool.rb +5 -4
- data/lib/paru/filter/meta_inlines.rb +9 -8
- data/lib/paru/filter/meta_list.rb +5 -4
- data/lib/paru/filter/meta_map.rb +30 -29
- data/lib/paru/filter/meta_string.rb +5 -4
- data/lib/paru/filter/meta_value.rb +13 -11
- data/lib/paru/filter/node.rb +125 -122
- data/lib/paru/filter/note.rb +19 -11
- data/lib/paru/filter/null.rb +5 -7
- data/lib/paru/filter/ordered_list.rb +18 -17
- data/lib/paru/filter/para.rb +12 -11
- data/lib/paru/filter/plain.rb +11 -10
- data/lib/paru/filter/quoted.rb +17 -16
- data/lib/paru/filter/raw_block.rb +18 -18
- data/lib/paru/filter/raw_inline.rb +21 -21
- data/lib/paru/filter/small_caps.rb +5 -4
- data/lib/paru/filter/soft_break.rb +5 -12
- data/lib/paru/filter/space.rb +5 -11
- data/lib/paru/filter/span.rb +17 -16
- data/lib/paru/filter/str.rb +17 -16
- data/lib/paru/filter/strikeout.rb +5 -4
- data/lib/paru/filter/strong.rb +10 -0
- data/lib/paru/filter/subscript.rb +5 -4
- data/lib/paru/filter/superscript.rb +5 -4
- data/lib/paru/filter/table.rb +28 -27
- data/lib/paru/filter/table_row.rb +13 -13
- data/lib/paru/filter/target.rb +14 -14
- data/lib/paru/filter/version.rb +19 -0
- data/lib/paru/pandoc.rb +18 -13
- data/lib/paru/pandoc_options.yaml +14 -1
- data/lib/paru/selector.rb +152 -148
- metadata +9 -5
- data/lib/paru/filter/string.rb +0 -9
data/lib/paru/filter/table.rb
CHANGED
@@ -1,34 +1,35 @@
|
|
1
|
+
# Table [Inline] [Alignment] [Double] [TableCell] [[TableCell]]
|
1
2
|
module Paru
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
ALIGNMENTS = ["AlignLeft", "AlignRight", "AlignCenter", "AlignDefault"]
|
3
|
+
module PandocFilter
|
4
|
+
require_relative "./block"
|
5
|
+
require_relative "./inline"
|
6
|
+
require_relative "./alignment"
|
8
7
|
|
9
|
-
|
10
|
-
attr_accessor :caption, :alignment, :column_widths, :headers, :rows
|
8
|
+
ALIGNMENTS = ["AlignLeft", "AlignRight", "AlignCenter", "AlignDefault"]
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
@alignment = contents[1]
|
15
|
-
@column_widths = contents[2]
|
16
|
-
@headers = TableRow.new contents[3]
|
17
|
-
@rows = []
|
18
|
-
contents[4].each do |row_data|
|
19
|
-
@rows.push TableRow.new row_data
|
20
|
-
end
|
21
|
-
end
|
10
|
+
class Table < Block
|
11
|
+
attr_accessor :caption, :alignment, :column_widths, :headers, :rows
|
22
12
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
13
|
+
def initialize contents
|
14
|
+
@caption = Inline.new contents[0]
|
15
|
+
@alignment = contents[1]
|
16
|
+
@column_widths = contents[2]
|
17
|
+
@headers = TableRow.new contents[3]
|
18
|
+
@rows = []
|
19
|
+
contents[4].each do |row_data|
|
20
|
+
@rows.push TableRow.new row_data
|
32
21
|
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ast_contents
|
25
|
+
[
|
26
|
+
@caption.ast_contents,
|
27
|
+
@alignment,
|
28
|
+
@column_widths,
|
29
|
+
@headers.ast_contents,
|
30
|
+
@rows.map {|row| row.ast_contents}
|
31
|
+
]
|
32
|
+
end
|
33
33
|
end
|
34
|
+
end
|
34
35
|
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
module Paru
|
2
|
-
|
3
|
-
|
2
|
+
module PandocFilter
|
3
|
+
require_relative "./block"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def ast_contents
|
14
|
-
@children.map {|child| child.ast_contents}
|
15
|
-
end
|
5
|
+
class TableRow < Block
|
6
|
+
def initialize row_data
|
7
|
+
super []
|
8
|
+
row_data.each do |cell|
|
9
|
+
@children.push Block.new cell
|
16
10
|
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def ast_contents
|
14
|
+
@children.map {|child| child.ast_contents}
|
15
|
+
end
|
17
16
|
end
|
17
|
+
end
|
18
18
|
end
|
data/lib/paru/filter/target.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
module Paru
|
2
|
-
|
2
|
+
module PandocFilter
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
class Target
|
5
|
+
attr_accessor :url, :title
|
6
|
+
def initialize contents
|
7
|
+
@url = contents[0]
|
8
|
+
@title = contents[1]
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
11
|
+
def to_ast
|
12
|
+
[
|
13
|
+
@url,
|
14
|
+
@title
|
15
|
+
]
|
16
|
+
end
|
18
17
|
end
|
18
|
+
end
|
19
19
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Paru
|
2
|
+
module PandocFilter
|
3
|
+
require_relative "./node"
|
4
|
+
|
5
|
+
class Version < Node
|
6
|
+
def initialize contents
|
7
|
+
@major, @minor, @revision = contents
|
8
|
+
end
|
9
|
+
|
10
|
+
def ast_type
|
11
|
+
"pandoc-api-version"
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_ast
|
15
|
+
[@major, @minor, @revision]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/paru/pandoc.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Paru
|
2
2
|
|
3
|
-
require
|
3
|
+
require "yaml"
|
4
4
|
|
5
5
|
# Pandoc is a wrapper around the pandoc system. See
|
6
6
|
# <http://pandoc.org/README.html> for details about pandoc. This file is
|
@@ -22,8 +22,8 @@ module Paru
|
|
22
22
|
# Converts input string to output string using the pandoc invocation
|
23
23
|
# configures in this Pandoc instance.
|
24
24
|
def convert input
|
25
|
-
output =
|
26
|
-
IO.popen(to_command,
|
25
|
+
output = ""
|
26
|
+
IO.popen(to_command, "r+") do |p|
|
27
27
|
p << input
|
28
28
|
p.close_write
|
29
29
|
output << p.read
|
@@ -39,11 +39,11 @@ module Paru
|
|
39
39
|
def to_option_string option_sep
|
40
40
|
options_arr = []
|
41
41
|
@options.each do |option, value|
|
42
|
-
option_string = "--#{option.to_s.gsub
|
42
|
+
option_string = "--#{option.to_s.gsub "_", "-"}"
|
43
43
|
|
44
44
|
case value
|
45
45
|
when TrueClass then
|
46
|
-
# Flags don
|
46
|
+
# Flags don"t have a value, only its name
|
47
47
|
# For example: --standalone
|
48
48
|
options_arr.push "#{option_string}"
|
49
49
|
when FalseClass then
|
@@ -53,14 +53,14 @@ module Paru
|
|
53
53
|
# For example: --css=main.css --css=print.css
|
54
54
|
options_arr.push value.map {|val| "#{option_string}=#{val.to_s}"}.join(option_sep)
|
55
55
|
else
|
56
|
-
# All options that aren
|
56
|
+
# All options that aren"t flags and can occur only once have the
|
57
57
|
# same pattern: --option=value
|
58
58
|
options_arr.push "#{option_string}=#{value.to_s}"
|
59
59
|
end
|
60
60
|
end
|
61
61
|
options_arr.join(option_sep)
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
# Pandoc has a number of command line options. Most are simple options,
|
65
65
|
# like flags, that can be set only once. Other options can occur more than
|
66
66
|
# once, such as the css option: to add more than one css file to a
|
@@ -74,7 +74,7 @@ module Paru
|
|
74
74
|
# an array with one value, the default value.
|
75
75
|
#
|
76
76
|
# For each of these options a method is defined as follows:
|
77
|
-
OPTIONS = YAML.load_file File.join(__dir__,
|
77
|
+
OPTIONS = YAML.load_file File.join(__dir__, "pandoc_options.yaml")
|
78
78
|
|
79
79
|
OPTIONS.keys.each do |option|
|
80
80
|
if OPTIONS[option].is_a? Array then
|
@@ -85,11 +85,16 @@ module Paru
|
|
85
85
|
default = OPTIONS[option][0]
|
86
86
|
|
87
87
|
define_method(option) do |value = default|
|
88
|
-
if @options[option] then
|
89
|
-
@options[option]
|
88
|
+
if @options[option].nil? then
|
89
|
+
@options[option] = []
|
90
|
+
end
|
91
|
+
|
92
|
+
if value.is_a? Array then
|
93
|
+
@options[option] += value
|
90
94
|
else
|
91
|
-
|
95
|
+
@options[option].push value
|
92
96
|
end
|
97
|
+
|
93
98
|
self
|
94
99
|
end
|
95
100
|
|
@@ -99,8 +104,8 @@ module Paru
|
|
99
104
|
|
100
105
|
default = OPTIONS[option]
|
101
106
|
define_method(option) do |value = default|
|
102
|
-
|
103
|
-
|
107
|
+
@options[option] = value
|
108
|
+
self
|
104
109
|
end
|
105
110
|
|
106
111
|
end
|
@@ -2,9 +2,17 @@
|
|
2
2
|
#
|
3
3
|
# General options
|
4
4
|
from: ""
|
5
|
+
read: ""
|
5
6
|
to: ""
|
7
|
+
write: ""
|
6
8
|
output: ""
|
7
9
|
data_dir: ""
|
10
|
+
list_input_formats: true
|
11
|
+
list_output_formats: true
|
12
|
+
list_extensions: true
|
13
|
+
list_highlight_languages: true
|
14
|
+
list_highlight_styles: true
|
15
|
+
version: true
|
8
16
|
# Reader options
|
9
17
|
parse_raw: true
|
10
18
|
smart: true
|
@@ -12,10 +20,11 @@ old_dashes: true
|
|
12
20
|
base_header_level: 1
|
13
21
|
indented_code_classes: ""
|
14
22
|
default_image_extension: ""
|
23
|
+
file_scope: true
|
15
24
|
filter: [""]
|
16
25
|
metadata: [""]
|
17
26
|
normalize: true
|
18
|
-
|
27
|
+
preserve_tabs: true
|
19
28
|
tab_stop: 4
|
20
29
|
track_changes: "accept"
|
21
30
|
extract_media: true
|
@@ -23,6 +32,8 @@ extract_media: true
|
|
23
32
|
standalone: true
|
24
33
|
template: ""
|
25
34
|
variable: [""]
|
35
|
+
print_default_template: ""
|
36
|
+
print_default_data_file: ""
|
26
37
|
dpi: 96
|
27
38
|
wrap: "auto"
|
28
39
|
no_wrap: true
|
@@ -40,8 +51,10 @@ self_contained: true
|
|
40
51
|
html_q_tags: true
|
41
52
|
ascii: true
|
42
53
|
reference_links: true
|
54
|
+
reference_location: "document"
|
43
55
|
atx_headers: true
|
44
56
|
chapters: true
|
57
|
+
top_level_division: "section"
|
45
58
|
number_sections: true
|
46
59
|
number_offset: "0"
|
47
60
|
no_tex_ligatures: true
|
data/lib/paru/selector.rb
CHANGED
@@ -1,151 +1,155 @@
|
|
1
1
|
module Paru
|
2
|
+
require_relative "./filter"
|
3
|
+
require_relative "./error"
|
4
|
+
|
5
|
+
class SelectorParseError < Error
|
6
|
+
end
|
7
|
+
|
8
|
+
class Selector
|
9
|
+
|
10
|
+
def initialize selector
|
11
|
+
@type = "Unknown"
|
12
|
+
@relations = []
|
13
|
+
parse selector
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches? node, filtered_nodes
|
17
|
+
node.type == @type and
|
18
|
+
@classes.all? {|c| node.has_class? c } and
|
19
|
+
@relations.all? {|r| r.matches? node, filtered_nodes}
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
S = /\s*/
|
25
|
+
TYPE = /(?<type>(?<name>[A-Z][a-zA-Z]*)(?<classes>(\.[a-zA-Z-]+)*))/
|
26
|
+
OTHER_TYPE = /(?<other_type>(?<other_name>[A-Z][a-zA-Z]*)(?<other_classes>(\.[a-zA-Z-]+)*))/
|
27
|
+
OPERATOR = /(?<operator>\+|-|>)/
|
28
|
+
DISTANCE = /(?<distance>[1-9][0-9]*)/
|
29
|
+
RELATION = /(?<relation>#{S}#{OTHER_TYPE}#{S}#{OPERATOR}#{S}#{DISTANCE}?#{S})/
|
30
|
+
RELATIONS = /(?<relations>#{RELATION}+)/
|
31
|
+
SELECTOR = /\A#{S}(?<selector>#{RELATIONS}?#{S}#{TYPE})#{S}\Z/
|
32
|
+
|
33
|
+
def parse selector_string
|
34
|
+
partial_match = expect_match SELECTOR, selector_string
|
35
|
+
@type, @classes = expect_pandoc_type partial_match
|
36
|
+
|
37
|
+
while continue_parsing? partial_match
|
38
|
+
operator = expect partial_match, :operator
|
39
|
+
distance = expect_integer partial_match, :distance
|
40
|
+
type, classes = expect_pandoc_other_type partial_match
|
41
|
+
|
42
|
+
@relations.push Relation.new(operator, distance, type, classes)
|
43
|
+
|
44
|
+
partial_match = rest partial_match
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_pandoc_type type
|
49
|
+
Paru::PANDOC_TYPES.include? type
|
50
|
+
end
|
51
|
+
|
52
|
+
def expect parts, part
|
53
|
+
raise SelectorParseError.new "Expected #{part}" if parts[part].nil?
|
54
|
+
parts[part]
|
55
|
+
end
|
56
|
+
|
57
|
+
def expect_match regexp, string
|
58
|
+
match = regexp.match string
|
59
|
+
raise SelectorParseError.new "Unable to parse '#{string}'" if match.nil?
|
60
|
+
match
|
61
|
+
end
|
62
|
+
|
63
|
+
def expect_pandoc_type parts
|
64
|
+
type = expect parts, :name
|
65
|
+
classes = parts[:classes].split(".").select {|c| not c.empty?} if not parts[:classes].nil?
|
66
|
+
raise SelectorParseError.new "Expected a Pandoc type, got '#{type}' instead" if not is_pandoc_type type
|
67
|
+
[type, classes]
|
68
|
+
end
|
69
|
+
|
70
|
+
def expect_pandoc_other_type parts
|
71
|
+
type = expect parts, :other_name
|
72
|
+
classes = parts[:other_classes].split(".").select {|c| not c.empty?} if not parts[:other_classes].nil?
|
73
|
+
raise SelectorParseError.new "Expected a Pandoc type, got '#{type}' instead" if not is_pandoc_type type
|
74
|
+
[type, classes]
|
75
|
+
end
|
76
|
+
|
77
|
+
def expect_integer parts, part
|
78
|
+
if parts[part].nil?
|
79
|
+
number = 0
|
80
|
+
else
|
81
|
+
number = parts[part].to_i
|
82
|
+
raise SelectorParseError.new "Expected a positive #{part}, got '#{parts[part]}' instead" if number <= 0
|
83
|
+
end
|
84
|
+
number
|
85
|
+
end
|
86
|
+
|
87
|
+
def continue_parsing? parts
|
88
|
+
not parts.nil? and not parts[:relations].nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
def rest parts
|
92
|
+
rest_string = parts[:relations].slice 0, parts[:relations].size - parts[:relation].size
|
93
|
+
RELATIONS.match rest_string
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Relation
|
98
|
+
def initialize selector, distance, type, classes
|
99
|
+
@selector = selector
|
100
|
+
@distance = distance
|
101
|
+
@type = type
|
102
|
+
@classes = classes
|
103
|
+
end
|
104
|
+
|
105
|
+
def matches? node, filtered_nodes
|
106
|
+
level_nodes = filtered_nodes.keep_if do |n|
|
107
|
+
node.is_inline? == n.is_inline? or
|
108
|
+
node.can_act_as_both_block_and_inline?
|
109
|
+
end
|
110
|
+
previous_nodes = previous level_nodes, @distance
|
111
|
+
case @selector
|
112
|
+
when "+"
|
113
|
+
in_sequence? node, previous_nodes
|
114
|
+
when "-"
|
115
|
+
not_in_sequence? node, previous_nodes
|
116
|
+
when ">"
|
117
|
+
is_descendant? node
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def in_sequence? node, previous_nodes
|
124
|
+
previous_nodes.any? do |other|
|
125
|
+
other.type == @type and @classes.all? {|c| other.has_class? c}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def not_in_sequence? node, previous_nodes
|
130
|
+
previous_nodes.all? do |other|
|
131
|
+
other.type != @type or not @classes.all? {|c| other.has_class? c}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def is_descendant? node
|
136
|
+
distance = 0
|
137
|
+
begin
|
138
|
+
distance += 1 if @distance > 0
|
139
|
+
parent = node.parent
|
140
|
+
ancestry = parent.type == @type and @classes.all? {|c| parent.has_class? c}
|
141
|
+
end while not ancestry and not parent.is_root? and distance <= @distance
|
142
|
+
ancestry
|
143
|
+
end
|
144
|
+
|
145
|
+
def previous filtered_nodes, distance
|
146
|
+
distance = [distance, filtered_nodes.size - 1].min
|
147
|
+
if distance <= 0
|
148
|
+
filtered_nodes.slice(0, filtered_nodes.size - 1)
|
149
|
+
else
|
150
|
+
filtered_nodes.slice(-1 * distance - 1, distance)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
2
154
|
|
3
|
-
require_relative "./filter"
|
4
|
-
require_relative "./error"
|
5
|
-
|
6
|
-
class SelectorParseError < Error
|
7
|
-
end
|
8
|
-
|
9
|
-
class Selector
|
10
|
-
|
11
|
-
def initialize selector
|
12
|
-
@type = "Unknown"
|
13
|
-
@relations = []
|
14
|
-
parse selector
|
15
|
-
end
|
16
|
-
|
17
|
-
def matches? node, filtered_nodes
|
18
|
-
node.type == @type and
|
19
|
-
@classes.all? {|c| node.has_class? c } and
|
20
|
-
@relations.all? {|r| r.matches? node, filtered_nodes}
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
S = /\s*/
|
26
|
-
TYPE = /(?<type>(?<name>[A-Z][a-zA-Z]*)(?<classes>(\.[a-zA-Z-]+)*))/
|
27
|
-
OTHER_TYPE = /(?<other_type>(?<other_name>[A-Z][a-zA-Z]*)(?<other_classes>(\.[a-zA-Z-]+)*))/
|
28
|
-
OPERATOR = /(?<operator>\+|-|>)/
|
29
|
-
DISTANCE = /(?<distance>[1-9][0-9]*)/
|
30
|
-
RELATION = /(?<relation>#{S}#{OTHER_TYPE}#{S}#{OPERATOR}#{S}#{DISTANCE}?#{S})/
|
31
|
-
RELATIONS = /(?<relations>#{RELATION}+)/
|
32
|
-
SELECTOR = /\A#{S}(?<selector>#{RELATIONS}?#{S}#{TYPE})#{S}\Z/
|
33
|
-
|
34
|
-
def parse selector_string
|
35
|
-
partial_match = expect_match SELECTOR, selector_string
|
36
|
-
@type, @classes = expect_pandoc_type partial_match
|
37
|
-
|
38
|
-
while continue_parsing? partial_match
|
39
|
-
operator = expect partial_match, :operator
|
40
|
-
distance = expect_integer partial_match, :distance
|
41
|
-
type, classes = expect_pandoc_other_type partial_match
|
42
|
-
|
43
|
-
@relations.push Relation.new(operator, distance, type, classes)
|
44
|
-
|
45
|
-
partial_match = rest partial_match
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def is_pandoc_type type
|
50
|
-
Paru::PANDOC_TYPES.include? type
|
51
|
-
end
|
52
|
-
|
53
|
-
def expect parts, part
|
54
|
-
raise SelectorParseError.new "Expected #{part}" if parts[part].nil?
|
55
|
-
parts[part]
|
56
|
-
end
|
57
|
-
|
58
|
-
def expect_match regexp, string
|
59
|
-
match = regexp.match string
|
60
|
-
raise SelectorParseError.new "Unable to parse '#{string}'" if match.nil?
|
61
|
-
match
|
62
|
-
end
|
63
|
-
|
64
|
-
def expect_pandoc_type parts
|
65
|
-
type = expect parts, :name
|
66
|
-
classes = parts[:classes].split(".").select {|c| not c.empty?} if not parts[:classes].nil?
|
67
|
-
raise SelectorParseError.new "Expected a Pandoc type, got '#{type}' instead" if not is_pandoc_type type
|
68
|
-
[type, classes]
|
69
|
-
end
|
70
|
-
|
71
|
-
def expect_pandoc_other_type parts
|
72
|
-
type = expect parts, :other_name
|
73
|
-
classes = parts[:other_classes].split(".").select {|c| not c.empty?} if not parts[:other_classes].nil?
|
74
|
-
raise SelectorParseError.new "Expected a Pandoc type, got '#{type}' instead" if not is_pandoc_type type
|
75
|
-
[type, classes]
|
76
|
-
end
|
77
|
-
|
78
|
-
def expect_integer parts, part
|
79
|
-
if parts[part].nil?
|
80
|
-
number = 0
|
81
|
-
else
|
82
|
-
number = parts[part].to_i
|
83
|
-
raise SelectorParseError.new "Expected a positive #{part}, got '#{parts[part]}' instead" if number <= 0
|
84
|
-
end
|
85
|
-
number
|
86
|
-
end
|
87
|
-
|
88
|
-
def continue_parsing? parts
|
89
|
-
not parts.nil? and not parts[:relations].nil?
|
90
|
-
end
|
91
|
-
|
92
|
-
def rest parts
|
93
|
-
rest_string = parts[:relations].slice 0, parts[:relations].size - parts[:relation].size
|
94
|
-
RELATIONS.match rest_string
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
class Relation
|
99
|
-
def initialize selector, distance, type, classes
|
100
|
-
@selector = selector
|
101
|
-
@distance = distance
|
102
|
-
@type = type
|
103
|
-
@classes = classes
|
104
|
-
end
|
105
|
-
|
106
|
-
def matches? node, filtered_nodes
|
107
|
-
previous_nodes = previous filtered_nodes, @distance
|
108
|
-
case @selector
|
109
|
-
when "+"
|
110
|
-
in_sequence? node, previous_nodes
|
111
|
-
when "-"
|
112
|
-
not_in_sequence? node, previous_nodes
|
113
|
-
when ">"
|
114
|
-
is_descendant? node
|
115
|
-
else
|
116
|
-
false
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def in_sequence? node, previous_nodes
|
121
|
-
previous_nodes.any? do |other|
|
122
|
-
other.type == @type and @classes.all? {|c| other.has_class? c}
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def not_in_sequence? node, previous_nodes
|
127
|
-
previous_nodes.all? do |other|
|
128
|
-
other.type != @type or not @classes.all? {|c| other.has_class? c}
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def is_descendant? node
|
133
|
-
distance = 0
|
134
|
-
begin
|
135
|
-
distance += 1 if @distance > 0
|
136
|
-
parent = node.parent
|
137
|
-
ancestry = parent.type == @type and @classes.all? {|c| parent.has_class? c}
|
138
|
-
end while not ancestry and not parent.is_root? and distance <= @distance
|
139
|
-
ancestry
|
140
|
-
end
|
141
|
-
|
142
|
-
def previous filtered_nodes, distance
|
143
|
-
if distance <= 0
|
144
|
-
filtered_nodes.slice(0, filtered_nodes.size - 1)
|
145
|
-
else
|
146
|
-
filtered_nodes.slice(-1 * distance - 1, distance)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
155
|
end
|