Skip to content

Commit

Permalink
Refactoring parsing and formatting errors and problems, parse errors …
Browse files Browse the repository at this point in the history
…from XML parsing
  • Loading branch information
samnung committed Sep 18, 2018
1 parent 12f70d3 commit 6e90a98
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 104 deletions.
76 changes: 8 additions & 68 deletions lib/epuber/checker/text_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,85 +4,25 @@

require_relative '../ruby_extensions/match_data'
require_relative '../checker'

require_relative '../compiler/problem'

module Epuber
class Checker
class TextChecker < Checker
class MatchProblem
class MatchProblem < Compiler::Problem
# @param message [String]
# @param file_path [String]
# @param match [MatchData]
#
def initialize(match, message, file_path)
@match = match
@message = message
@file_path = file_path
end

# Formats caret symbol with space indent
#
# @param [Fixnum] indent
#
# @return [String]
#
def caret_symbol(indent)
' ' * indent + '^'
end

# Formats caret symbols for indent and length
#
# @param [Fixnum] length
# @param [Fixnum] indent
#
# @return [String]
#
def caret_symbols(indent, length)
start_sign = caret_symbol(indent)
end_sign = if length > 1
caret_symbol(length-2)
else
''
end

"#{start_sign}#{end_sign}"
end

def formatted_match_line
match_line = @match.matched_string
pre_line = @match.pre_match_lines.last || ''

pre = match_pre_line = pre_line
if remove_tabs(match_pre_line).length > 100
pre = "#{match_pre_line.first(20)}...#{match_pre_line.last(30)}"
end

pre = remove_tabs(pre)

post_line = @match.post_match_lines.first || ''

post = if post_line.length > 50
"#{post_line.first(50)}..."
else
post_line
end

[pre, match_line, post]
end

def remove_tabs(text)
text.gsub("\t", ' ' * 4)
end

def to_s
pre_original = @match.pre_match_lines.last || ''
pre, match_text, post = formatted_match_line
whole_text = match.pre_match + match.matched_string + match.post_match

pointers = caret_symbols(pre.length, match_text.length)
line = match.pre_match_lines.count
column = (match.pre_match_lines.last || '').length + 1
length = match.matched_string.length
location = Epuber::Compiler::Problem::Location.new(line, column, length)

%{#{@file_path}:#{@match.line_number} column: #{pre_original.length} --- #{@message}
#{pre + match_text.ansi.red + post}
#{pointers}}
super(:warn, message, whole_text, location: location, file_path: file_path)
end
end

Expand Down
12 changes: 6 additions & 6 deletions lib/epuber/compiler/file_types/xhtml_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ def load_source(compilation_context)
xhtml_content
end

# @param [Array] errors
# @param [Array<Epuber::Compiler::Problem>] errors
#
def process_nokogiri_errors(errors)
errors.each do |e|
UI.warning(e)
errors.each do |problem|
UI.warning(problem, location: self)
end
end

Expand All @@ -86,12 +86,12 @@ def common_process(content, compilation_context)
book = compilation_context.book
file_resolver = compilation_context.file_resolver

xhtml_doc = UI.print_step_processing_time('parsing XHTML file') do
XHTMLProcessor.xml_document_from_string(content, source_path)
xhtml_doc, errors = UI.print_step_processing_time('parsing XHTML file') do
XHTMLProcessor.xml_doc_from_str_with_errors(content, source_path)
end

if compilation_context.release_build && xhtml_doc.errors.count > 0
process_nokogiri_errors(xhtml_doc.errors)
process_nokogiri_errors(errors)
end

UI.print_step_processing_time('adding missing elements') do
Expand Down
124 changes: 124 additions & 0 deletions lib/epuber/compiler/problem.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# encoding: utf-8


module Epuber
class Compiler
class Problem
class Location
attr_reader :line
attr_reader :column
attr_reader :length

def initialize(line, column, length = nil)
@line = line
@column = column
@length = length || 1
end
end

attr_reader :level
attr_reader :message
attr_reader :source
attr_reader :location
attr_reader :file_path

def initialize(level, message, source, location: nil, line: nil, column: nil, length: nil, file_path: nil)
@level = level
@message = message
@source = source
@location = location
if @location.nil? && line && column
@location = Location.new(line, column, length)
end

@file_path = file_path
end

# Formats caret symbol with space indent
#
# @param [Fixnum] indent
#
# @return [String]
#
def self.caret_symbol(indent)
' ' * indent + '^'
end

# Formats caret symbols for indent and length
#
# @param [Fixnum] length
# @param [Fixnum] indent
#
# @return [String]
#
def self.caret_symbols(indent, length)
start_sign = caret_symbol(indent)
end_sign = if length > 1
caret_symbol(length-2)
else
''
end

"#{start_sign}#{end_sign}"
end

def self.remove_tabs(text)
text.gsub("\t", ' ' * 4)
end

# @param [Location] location
#
def self.text_at(text, location)
line_index = location.line - 1
column_index = location.column - 1

lines = text.split("\n")

line = lines[line_index] || ''
matched_text = line[column_index ... column_index + location.length] || ''

pre = (lines[0 ... line_index] + [line[0 ... column_index]]).join("\n")
post = ([line[column_index + location.length .. line.length]] + (lines[location.line .. lines.count] || [])).join("\n")

[pre, matched_text, post]
end

def self.formatted_match_line(text, location)
pre, matched, post = text_at(text, location)

pre_line = pre.split("\n").last || ''
post_line = post.split("\n").first || ''

pre = match_pre_line = pre_line
if remove_tabs(match_pre_line).length > 100
pre = "#{match_pre_line.first(20)}...#{match_pre_line.last(30)}"
end

pre = remove_tabs(pre)

post = if post_line.length > 50
"#{post_line.first(50)}..."
else
post_line
end

[pre, matched, post]
end

def to_s
pre, match_text, post = self.class.formatted_match_line(@source, @location)

pointers = self.class.caret_symbols(pre.length, @location.length)
colored_match_text = match_text.empty? ? match_text : match_text.ansi.red
column = @location.column
line = [@location.line, 1].max

[
"#{@file_path}:#{line} column: #{column} --- #{@message}",
' ' + pre + colored_match_text + post,
' ' + pointers,
].join("\n")
end
end
end
end
22 changes: 18 additions & 4 deletions lib/epuber/compiler/xhtml_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ class UnparseableLinkError < StandardError; end
#
# @return [Nokogiri::XML::Document] parsed document
#
def self.xml_document_from_string(text, file_path = nil)
def self.xml_doc_from_str_with_errors(text, file_path = nil)
if /\A[\n\r ]+(<\?xml)/ =~ text
UI.warning('XML header must be at the beginning of document', location: UI::Location.new(file_path, 1))

text = text.lstrip
end

xml_header = ''
xml_header = nil
if /\A\s*(<\?xml[^>]*\?>)/ =~ text
match = Regexp.last_match
xml_header = text[match.begin(1)...match.end(1)]
Expand All @@ -50,10 +50,19 @@ def self.xml_document_from_string(text, file_path = nil)
Nokogiri::XML::ParseOptions::NOERROR | # to silence any errors or warnings printing into console
Nokogiri::XML::ParseOptions::NOWARNING

doc = Nokogiri::XML("#{before}<root>#{text}</root>", nil, nil, parse_options)
doc = Nokogiri::XML("#{before}<root>#{text}</root>", file_path, nil, parse_options)
text_for_errors = before + text
doc.encoding = 'UTF-8'
doc.file_path = file_path

if doc.errors.empty?
errors = []
else
errors = doc.errors.map do |e|
Problem.new(:error, e.message, text_for_errors, line: e.line, column: e.column, file_path: file_path)
end
end

root = root_node = doc.root
root_elements = root.children.select { |a| a.element? || a.comment? }

Expand All @@ -67,7 +76,12 @@ def self.xml_document_from_string(text, file_path = nil)
root_node.node_name = 'html'
end

doc
[doc, errors]
end

def self.xml_document_from_string(text, file_path = nil)
xml, errros = self.xml_doc_from_str_with_errors(text, file_path)
xml
end


Expand Down
8 changes: 7 additions & 1 deletion lib/epuber/user_interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,13 @@ def self._format_message(level, message, location: nil)

comps = []
comps << message.to_s
comps << " (in file #{location.path} line #{location.lineno}" unless location.nil?
if !location.nil? && !(message.is_a?(Epuber::Compiler::Problem) || message.is_a?(Epuber::Checker::TextChecker::MatchProblem))
if location.lineno
comps << " (in file #{location.path} line #{location.lineno})"
else
comps << " (in file #{location.path})"
end
end

comps.join("\n").ansi.send(_color_from_level(level))
end
Expand Down

0 comments on commit 6e90a98

Please sign in to comment.