epuber 0.5.5 → 0.5.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/epuber/checker/text_checker.rb +8 -68
- data/lib/epuber/compiler/file_types/xhtml_file.rb +6 -6
- data/lib/epuber/compiler/problem.rb +124 -0
- data/lib/epuber/compiler/xhtml_processor.rb +39 -9
- data/lib/epuber/user_interface.rb +7 -1
- data/lib/epuber/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b03d446f6c71a6105554b794179ec35e719bc3a3c630ece5bd2607edbf379681
|
4
|
+
data.tar.gz: b8c27f00396dc720d1e5dfc13d92fa5d97061f58947217d873a173c2ce939f7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 632a864249f1e89cdf915e28bcfdd8e83dbae9f3b8b4c84d933ccb873eed6d4b5ef3ab74d6775af229e9b49b6e21f9d199fe8990a542ef5b12f50fb8eda3e30d
|
7
|
+
data.tar.gz: 9105843aac2528104ff2febf949ccd89e9a3efe9724e8a422c5a98de003563b3bd58643f5894964c5a787071b30325c64ace3e13414c2960058c28c2417e1291
|
@@ -4,85 +4,25 @@ require 'active_support/core_ext/string/access'
|
|
4
4
|
|
5
5
|
require_relative '../ruby_extensions/match_data'
|
6
6
|
require_relative '../checker'
|
7
|
-
|
7
|
+
require_relative '../compiler/problem'
|
8
8
|
|
9
9
|
module Epuber
|
10
10
|
class Checker
|
11
11
|
class TextChecker < Checker
|
12
|
-
class MatchProblem
|
12
|
+
class MatchProblem < Compiler::Problem
|
13
13
|
# @param message [String]
|
14
14
|
# @param file_path [String]
|
15
15
|
# @param match [MatchData]
|
16
16
|
#
|
17
17
|
def initialize(match, message, file_path)
|
18
|
-
|
19
|
-
@message = message
|
20
|
-
@file_path = file_path
|
21
|
-
end
|
22
|
-
|
23
|
-
# Formats caret symbol with space indent
|
24
|
-
#
|
25
|
-
# @param [Fixnum] indent
|
26
|
-
#
|
27
|
-
# @return [String]
|
28
|
-
#
|
29
|
-
def caret_symbol(indent)
|
30
|
-
' ' * indent + '^'
|
31
|
-
end
|
32
|
-
|
33
|
-
# Formats caret symbols for indent and length
|
34
|
-
#
|
35
|
-
# @param [Fixnum] length
|
36
|
-
# @param [Fixnum] indent
|
37
|
-
#
|
38
|
-
# @return [String]
|
39
|
-
#
|
40
|
-
def caret_symbols(indent, length)
|
41
|
-
start_sign = caret_symbol(indent)
|
42
|
-
end_sign = if length > 1
|
43
|
-
caret_symbol(length-2)
|
44
|
-
else
|
45
|
-
''
|
46
|
-
end
|
47
|
-
|
48
|
-
"#{start_sign}#{end_sign}"
|
49
|
-
end
|
50
|
-
|
51
|
-
def formatted_match_line
|
52
|
-
match_line = @match.matched_string
|
53
|
-
pre_line = @match.pre_match_lines.last || ''
|
54
|
-
|
55
|
-
pre = match_pre_line = pre_line
|
56
|
-
if remove_tabs(match_pre_line).length > 100
|
57
|
-
pre = "#{match_pre_line.first(20)}...#{match_pre_line.last(30)}"
|
58
|
-
end
|
59
|
-
|
60
|
-
pre = remove_tabs(pre)
|
61
|
-
|
62
|
-
post_line = @match.post_match_lines.first || ''
|
63
|
-
|
64
|
-
post = if post_line.length > 50
|
65
|
-
"#{post_line.first(50)}..."
|
66
|
-
else
|
67
|
-
post_line
|
68
|
-
end
|
69
|
-
|
70
|
-
[pre, match_line, post]
|
71
|
-
end
|
72
|
-
|
73
|
-
def remove_tabs(text)
|
74
|
-
text.gsub("\t", ' ' * 4)
|
75
|
-
end
|
76
|
-
|
77
|
-
def to_s
|
78
|
-
pre_original = @match.pre_match_lines.last || ''
|
79
|
-
pre, match_text, post = formatted_match_line
|
18
|
+
whole_text = match.pre_match + match.matched_string + match.post_match
|
80
19
|
|
81
|
-
|
20
|
+
line = match.pre_match_lines.count
|
21
|
+
column = (match.pre_match_lines.last || '').length + 1
|
22
|
+
length = match.matched_string.length
|
23
|
+
location = Epuber::Compiler::Problem::Location.new(line, column, length)
|
82
24
|
|
83
|
-
|
84
|
-
#{pre + match_text.ansi.red + post}
|
85
|
-
#{pointers}}
|
25
|
+
super(:warn, message, whole_text, location: location, file_path: file_path)
|
86
26
|
end
|
87
27
|
end
|
88
28
|
|
@@ -68,11 +68,11 @@ module Epuber
|
|
68
68
|
xhtml_content
|
69
69
|
end
|
70
70
|
|
71
|
-
# @param [Array] errors
|
71
|
+
# @param [Array<Epuber::Compiler::Problem>] errors
|
72
72
|
#
|
73
73
|
def process_nokogiri_errors(errors)
|
74
|
-
errors.each do |
|
75
|
-
UI.warning(
|
74
|
+
errors.each do |problem|
|
75
|
+
UI.warning(problem, location: self)
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
@@ -86,12 +86,12 @@ module Epuber
|
|
86
86
|
book = compilation_context.book
|
87
87
|
file_resolver = compilation_context.file_resolver
|
88
88
|
|
89
|
-
xhtml_doc = UI.print_step_processing_time('parsing XHTML file') do
|
90
|
-
XHTMLProcessor.
|
89
|
+
xhtml_doc, errors = UI.print_step_processing_time('parsing XHTML file') do
|
90
|
+
XHTMLProcessor.xml_doc_from_str_with_errors(content, source_path)
|
91
91
|
end
|
92
92
|
|
93
93
|
if compilation_context.release_build && xhtml_doc.errors.count > 0
|
94
|
-
process_nokogiri_errors(
|
94
|
+
process_nokogiri_errors(errors)
|
95
95
|
end
|
96
96
|
|
97
97
|
UI.print_step_processing_time('adding missing elements') do
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
module Epuber
|
5
|
+
class Compiler
|
6
|
+
class Problem
|
7
|
+
class Location
|
8
|
+
attr_reader :line
|
9
|
+
attr_reader :column
|
10
|
+
attr_reader :length
|
11
|
+
|
12
|
+
def initialize(line, column, length = nil)
|
13
|
+
@line = line
|
14
|
+
@column = column
|
15
|
+
@length = length || 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :level
|
20
|
+
attr_reader :message
|
21
|
+
attr_reader :source
|
22
|
+
attr_reader :location
|
23
|
+
attr_reader :file_path
|
24
|
+
|
25
|
+
def initialize(level, message, source, location: nil, line: nil, column: nil, length: nil, file_path: nil)
|
26
|
+
@level = level
|
27
|
+
@message = message
|
28
|
+
@source = source
|
29
|
+
@location = location
|
30
|
+
if @location.nil? && line && column
|
31
|
+
@location = Location.new(line, column, length)
|
32
|
+
end
|
33
|
+
|
34
|
+
@file_path = file_path
|
35
|
+
end
|
36
|
+
|
37
|
+
# Formats caret symbol with space indent
|
38
|
+
#
|
39
|
+
# @param [Fixnum] indent
|
40
|
+
#
|
41
|
+
# @return [String]
|
42
|
+
#
|
43
|
+
def self.caret_symbol(indent)
|
44
|
+
' ' * indent + '^'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Formats caret symbols for indent and length
|
48
|
+
#
|
49
|
+
# @param [Fixnum] length
|
50
|
+
# @param [Fixnum] indent
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
#
|
54
|
+
def self.caret_symbols(indent, length)
|
55
|
+
start_sign = caret_symbol(indent)
|
56
|
+
end_sign = if length > 1
|
57
|
+
caret_symbol(length-2)
|
58
|
+
else
|
59
|
+
''
|
60
|
+
end
|
61
|
+
|
62
|
+
"#{start_sign}#{end_sign}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.remove_tabs(text)
|
66
|
+
text.gsub("\t", ' ' * 4)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Location] location
|
70
|
+
#
|
71
|
+
def self.text_at(text, location)
|
72
|
+
line_index = location.line - 1
|
73
|
+
column_index = location.column - 1
|
74
|
+
|
75
|
+
lines = text.split("\n")
|
76
|
+
|
77
|
+
line = lines[line_index] || ''
|
78
|
+
matched_text = line[column_index ... column_index + location.length] || ''
|
79
|
+
|
80
|
+
pre = (lines[0 ... line_index] + [line[0 ... column_index]]).join("\n")
|
81
|
+
post = ([line[column_index + location.length .. line.length]] + (lines[location.line .. lines.count] || [])).join("\n")
|
82
|
+
|
83
|
+
[pre, matched_text, post]
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.formatted_match_line(text, location)
|
87
|
+
pre, matched, post = text_at(text, location)
|
88
|
+
|
89
|
+
pre_line = pre.split("\n").last || ''
|
90
|
+
post_line = post.split("\n").first || ''
|
91
|
+
|
92
|
+
pre = match_pre_line = pre_line
|
93
|
+
if remove_tabs(match_pre_line).length > 100
|
94
|
+
pre = "#{match_pre_line.first(20)}...#{match_pre_line.last(30)}"
|
95
|
+
end
|
96
|
+
|
97
|
+
pre = remove_tabs(pre)
|
98
|
+
|
99
|
+
post = if post_line.length > 50
|
100
|
+
"#{post_line.first(50)}..."
|
101
|
+
else
|
102
|
+
post_line
|
103
|
+
end
|
104
|
+
|
105
|
+
[pre, matched, post]
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_s
|
109
|
+
pre, match_text, post = self.class.formatted_match_line(@source, @location)
|
110
|
+
|
111
|
+
pointers = self.class.caret_symbols(pre.length, @location.length)
|
112
|
+
colored_match_text = match_text.empty? ? match_text : match_text.ansi.red
|
113
|
+
column = @location.column
|
114
|
+
line = [@location.line, 1].max
|
115
|
+
|
116
|
+
[
|
117
|
+
"#{@file_path}:#{line} column: #{column} --- #{@message}",
|
118
|
+
' ' + pre + colored_match_text + post,
|
119
|
+
' ' + pointers,
|
120
|
+
].join("\n")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -19,14 +19,14 @@ module Epuber
|
|
19
19
|
#
|
20
20
|
# @return [Nokogiri::XML::Document] parsed document
|
21
21
|
#
|
22
|
-
def self.
|
22
|
+
def self.xml_doc_from_str_with_errors(text, file_path = nil)
|
23
23
|
if /\A[\n\r ]+(<\?xml)/ =~ text
|
24
24
|
UI.warning('XML header must be at the beginning of document', location: UI::Location.new(file_path, 1))
|
25
25
|
|
26
26
|
text = text.lstrip
|
27
27
|
end
|
28
28
|
|
29
|
-
xml_header =
|
29
|
+
xml_header = nil
|
30
30
|
if /\A\s*(<\?xml[^>]*\?>)/ =~ text
|
31
31
|
match = Regexp.last_match
|
32
32
|
xml_header = text[match.begin(1)...match.end(1)]
|
@@ -50,20 +50,38 @@ module Epuber
|
|
50
50
|
Nokogiri::XML::ParseOptions::NOERROR | # to silence any errors or warnings printing into console
|
51
51
|
Nokogiri::XML::ParseOptions::NOWARNING
|
52
52
|
|
53
|
-
doc = Nokogiri::XML("#{before}<root>#{text}</root>",
|
53
|
+
doc = Nokogiri::XML("#{before}<root>#{text}</root>", file_path, nil, parse_options)
|
54
|
+
text_for_errors = before + text
|
54
55
|
doc.encoding = 'UTF-8'
|
55
56
|
doc.file_path = file_path
|
56
57
|
|
58
|
+
if doc.errors.empty?
|
59
|
+
errors = []
|
60
|
+
else
|
61
|
+
errors = doc.errors.map do |e|
|
62
|
+
Problem.new(:error, e.message, text_for_errors, line: e.line, column: e.column, file_path: file_path)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
57
66
|
root = root_node = doc.root
|
58
67
|
root_elements = root.children.select { |a| a.element? || a.comment? }
|
59
68
|
|
60
69
|
if root_elements.count == 1
|
61
70
|
doc.root = root_elements.first
|
71
|
+
elsif root_node.at_css('html')
|
72
|
+
doc.root = root_node.at_css('html')
|
62
73
|
elsif root_node.at_css('body').nil?
|
63
74
|
root_node.node_name = 'body'
|
75
|
+
else
|
76
|
+
root_node.node_name = 'html'
|
64
77
|
end
|
65
78
|
|
66
|
-
doc
|
79
|
+
[doc, errors]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.xml_document_from_string(text, file_path = nil)
|
83
|
+
xml, errros = self.xml_doc_from_str_with_errors(text, file_path)
|
84
|
+
xml
|
67
85
|
end
|
68
86
|
|
69
87
|
|
@@ -83,25 +101,37 @@ module Epuber
|
|
83
101
|
def self.add_missing_root_elements(xhtml_doc, title, epub_version)
|
84
102
|
# add missing body element
|
85
103
|
if xhtml_doc.at_css('body').nil?
|
86
|
-
xhtml_doc.root.
|
104
|
+
if xhtml_doc.root.node_name == 'html'
|
105
|
+
xhtml_doc.root << xhtml_doc.create_element('body')
|
106
|
+
else
|
107
|
+
xhtml_doc.root.surround_with_element('body')
|
108
|
+
end
|
87
109
|
end
|
88
110
|
|
111
|
+
html = xhtml_doc.at_css('html')
|
112
|
+
|
89
113
|
# add missing root html element
|
90
|
-
if
|
114
|
+
if html.nil?
|
91
115
|
attrs = {}
|
92
116
|
attrs['xmlns'] = 'http://www.w3.org/1999/xhtml'
|
93
117
|
attrs['xmlns:epub'] = 'http://www.idpf.org/2007/ops' if epub_version >= 3
|
94
|
-
xhtml_doc.root.surround_with_element('html', attrs)
|
118
|
+
html = xhtml_doc.root.surround_with_element('html', attrs)
|
119
|
+
elsif html.namespaces.empty?
|
120
|
+
html['xmlns'] = 'http://www.w3.org/1999/xhtml'
|
121
|
+
html['xmlns:epub'] = 'http://www.idpf.org/2007/ops' if epub_version >= 3
|
95
122
|
end
|
96
123
|
|
97
124
|
# add missing head in html
|
98
125
|
if xhtml_doc.at_css('html > head').nil?
|
99
|
-
html = xhtml_doc.css('html').first
|
100
126
|
head = xhtml_doc.create_element('head')
|
101
127
|
head << xhtml_doc.create_element('title', title)
|
102
128
|
head << xhtml_doc.create_element('meta', charset: 'utf-8') if epub_version >= 3.0
|
103
129
|
|
104
|
-
html.children.first
|
130
|
+
if (first = html.children.first)
|
131
|
+
first.before(head)
|
132
|
+
else
|
133
|
+
html << head
|
134
|
+
end
|
105
135
|
end
|
106
136
|
|
107
137
|
# https://github.com/IDPF/epubcheck/issues/631
|
@@ -191,7 +191,13 @@ module Epuber
|
|
191
191
|
|
192
192
|
comps = []
|
193
193
|
comps << message.to_s
|
194
|
-
|
194
|
+
if !location.nil? && !(message.is_a?(Epuber::Compiler::Problem) || message.is_a?(Epuber::Checker::TextChecker::MatchProblem))
|
195
|
+
if location.lineno
|
196
|
+
comps << " (in file #{location.path} line #{location.lineno})"
|
197
|
+
else
|
198
|
+
comps << " (in file #{location.path})"
|
199
|
+
end
|
200
|
+
end
|
195
201
|
|
196
202
|
comps.join("\n").ansi.send(_color_from_level(level))
|
197
203
|
end
|
data/lib/epuber/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: epuber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roman Kříž
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-09-
|
11
|
+
date: 2018-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -369,6 +369,7 @@ files:
|
|
369
369
|
- lib/epuber/compiler/meta_inf_generator.rb
|
370
370
|
- lib/epuber/compiler/nav_generator.rb
|
371
371
|
- lib/epuber/compiler/opf_generator.rb
|
372
|
+
- lib/epuber/compiler/problem.rb
|
372
373
|
- lib/epuber/compiler/xhtml_processor.rb
|
373
374
|
- lib/epuber/config.rb
|
374
375
|
- lib/epuber/dsl/attribute.rb
|