epuber 0.5.5 → 0.5.6
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/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
|