narou 1.5.11 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of narou might be problematic. Click here for more details.

Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/ChangeLog.md +619 -432
  4. data/Gemfile +9 -0
  5. data/README.md +101 -76
  6. data/lib/color.rb +62 -22
  7. data/lib/command.rb +21 -19
  8. data/lib/command/alias.rb +9 -9
  9. data/lib/command/backup.rb +11 -4
  10. data/lib/command/browser.rb +8 -7
  11. data/lib/command/convert.rb +25 -77
  12. data/lib/command/diff.rb +67 -36
  13. data/lib/command/download.rb +5 -4
  14. data/lib/command/flag.rb +32 -6
  15. data/lib/command/folder.rb +7 -5
  16. data/lib/command/freeze.rb +21 -11
  17. data/lib/command/help.rb +29 -17
  18. data/lib/command/init.rb +7 -7
  19. data/lib/command/inspect.rb +6 -5
  20. data/lib/command/list.rb +106 -25
  21. data/lib/command/mail.rb +6 -7
  22. data/lib/command/remove.rb +9 -7
  23. data/lib/command/send.rb +6 -5
  24. data/lib/command/setting.rb +51 -16
  25. data/lib/command/tag.rb +179 -0
  26. data/lib/command/update.rb +9 -8
  27. data/lib/command/version.rb +4 -4
  28. data/lib/commandbase.rb +79 -8
  29. data/lib/commandline.rb +14 -8
  30. data/lib/converterbase.rb +78 -28
  31. data/lib/database.rb +3 -3
  32. data/lib/device.rb +26 -13
  33. data/lib/device/ibooks.rb +116 -0
  34. data/lib/device/ibunko.rb +45 -0
  35. data/lib/device/kindle.rb +4 -0
  36. data/lib/device/kobo.rb +4 -0
  37. data/lib/device/library/windows.rb +12 -4
  38. data/lib/device/reader.rb +9 -0
  39. data/lib/diffviewer.rb +147 -0
  40. data/lib/downloader.rb +195 -80
  41. data/lib/extensions/jruby.rb +33 -0
  42. data/lib/extensions/windows.rb +8 -9
  43. data/lib/helper.rb +79 -37
  44. data/lib/illustration.rb +1 -1
  45. data/lib/inspector.rb +2 -2
  46. data/lib/inventory.rb +48 -0
  47. data/lib/logger.rb +28 -4
  48. data/lib/narou.rb +9 -7
  49. data/lib/narou/api.rb +7 -2
  50. data/lib/novelconverter.rb +46 -19
  51. data/lib/novelinfo.rb +13 -3
  52. data/lib/novelsetting.rb +17 -9
  53. data/lib/version.rb +1 -1
  54. data/narou.gemspec +106 -27
  55. data/narou.rb +2 -2
  56. data/spec/convert_spec.rb +102 -0
  57. data/spec/data/convert_test/auto_indent/correct_test_auto_indent.txt +18 -0
  58. data/spec/data/convert_test/auto_indent/test_auto_indent.txt +21 -0
  59. data/spec/data/convert_test/auto_join_bracket/correct_test_auto_join_bracket.txt +12 -0
  60. data/spec/data/convert_test/auto_join_bracket/test_auto_join_bracket.txt +16 -0
  61. data/spec/data/convert_test/auto_join_line/correct_test_auto_join_line.txt +36 -0
  62. data/spec/data/convert_test/auto_join_line/test_auto_join_line.txt +51 -0
  63. data/spec/data/convert_test/convert_page_break/correct_test_convert_page_break.txt +21 -0
  64. data/spec/data/convert_test/convert_page_break/setting.ini +5 -0
  65. data/spec/data/convert_test/convert_page_break/test_convert_page_break.txt +60 -0
  66. data/spec/data/convert_test/force_indent_special_chapter/correct_test_force_indent_special_chapter.txt +64 -0
  67. data/spec/data/convert_test/force_indent_special_chapter/test_force_indent_special_chapter.txt +61 -0
  68. data/spec/data/convert_test/horizontal_ellipsis/correct_test_horizontal_ellipsis.txt +52 -0
  69. data/spec/data/convert_test/horizontal_ellipsis/test_horizontal_ellipsis.txt +57 -0
  70. data/spec/data/convert_test/kanji_num/correct_test_kanji_num.txt +49 -0
  71. data/spec/data/convert_test/kanji_num/test_kanji_num.txt +56 -0
  72. data/spec/data/convert_test/nonokagi/correct_test_nonokagi.txt +29 -0
  73. data/spec/data/convert_test/nonokagi/test_nonokagi.txt +34 -0
  74. data/spec/data/convert_test/replace/correct_test_replace.txt +12 -0
  75. data/spec/data/convert_test/replace/replace.txt +7 -0
  76. data/spec/data/convert_test/replace/test_replace.txt +14 -0
  77. data/spec/data/convert_test/ruby/correct_test_ruby.txt +131 -0
  78. data/spec/data/convert_test/ruby/test_ruby.txt +170 -0
  79. data/spec/data/convert_test/ruby_youon/correct_test_ruby_youon.txt +12 -0
  80. data/spec/data/convert_test/ruby_youon/setting.ini +2 -0
  81. data/spec/data/convert_test/ruby_youon/test_ruby_youon.txt +14 -0
  82. data/spec/data/convert_test/sesame/correct_test_sesame.txt +40 -0
  83. data/spec/data/convert_test/sesame/test_sesame.txt +52 -0
  84. data/spec/data/convert_test/to_odd_leader/correct_test_to_odd_leader.txt +20 -0
  85. data/spec/data/convert_test/to_odd_leader/test_to_odd_leader.txt +21 -0
  86. data/spec/device_spec.rb +3 -3
  87. data/spec/generator/convert_spec_gen.rb +95 -0
  88. data/spec/html_spec.rb +9 -8
  89. data/spec/ini_spec.rb +31 -31
  90. data/spec/novelinfo_spec.rb +2 -2
  91. data/spec/num_to_kanji_spec.rb +2 -2
  92. data/template/diff.txt.erb +5 -2
  93. data/template/ibunko_novel.txt.erb +2 -1
  94. data/template/novel.txt.erb +16 -3
  95. data/template/setting.ini.erb +1 -1
  96. data/webnovel/ncode.syosetu.com.yaml +7 -7
  97. data/webnovel/novel18.syosetu.com.yaml +7 -7
  98. data/webnovel/syosetu.org.yaml +8 -11
  99. metadata +193 -23
  100. data/lib/globalsetting.rb +0 -64
  101. data/lib/localsetting.rb +0 -63
@@ -8,8 +8,12 @@ require_relative "../downloader"
8
8
 
9
9
  module Command
10
10
  class Update < CommandBase
11
+ def self.oneline_help
12
+ "小説を更新します"
13
+ end
14
+
11
15
  def initialize
12
- super("[<target> <target2> ...] [option]")
16
+ super("[<target> ...] [options]")
13
17
  @opt.separator <<-EOS
14
18
 
15
19
  ・管理対象の小説を更新します。
@@ -19,8 +23,9 @@ module Command
19
23
  ・一度に複数の小説を指定する場合は空白で区切って下さい。
20
24
  ・全て更新する場合、convert.no-openが設定されていなくても保存フォルダは開きません。
21
25
 
22
- Example:
26
+ Examples:
23
27
  narou update # 全て更新
28
+ narou u # 短縮コマンド
24
29
  narou update 0 1 2 4
25
30
  narou update n9669bk 異世界迷宮で奴隷ハーレムを
26
31
  narou update http://ncode.syosetu.com/n9669bk/
@@ -45,11 +50,12 @@ module Command
45
50
  end
46
51
  no_open = true
47
52
  end
53
+ tagname_to_ids(update_target_list)
48
54
  update_target_list.each_with_index do |target, i|
49
55
  display_message = nil
50
56
  data = Downloader.get_data_by_target(target)
51
57
  if !data
52
- display_message = "<red>[ERROR]</red> #{target} は管理小説の中に存在しません".termcolor
58
+ display_message = "<bold><red>[ERROR]</red></bold> #{target} は管理小説の中に存在しません".termcolor
53
59
  elsif Narou.novel_frozen?(target)
54
60
  if argv.length > 0
55
61
  display_message = "ID:#{data["id"]} #{data["title"]} は凍結中です"
@@ -73,7 +79,6 @@ module Command
73
79
  end
74
80
  when :failed
75
81
  puts "ID:#{data["id"]} #{data["title"]} の更新は失敗しました"
76
- Freeze.execute!([target])
77
82
  when :canceled
78
83
  puts "ID:#{data["id"]} #{data["title"]} の更新はキャンセルされました"
79
84
  when :none
@@ -84,9 +89,5 @@ module Command
84
89
  puts "アップデートを中断しました"
85
90
  exit 1
86
91
  end
87
-
88
- def oneline_help
89
- "小説を更新します"
90
- end
91
92
  end
92
93
  end
@@ -5,12 +5,12 @@
5
5
 
6
6
  module Command
7
7
  class Version < CommandBase
8
- def execute(argv)
9
- puts ::Version + " build " + ::CommitVersion
8
+ def self.oneline_help
9
+ "バージョンを表示します"
10
10
  end
11
11
 
12
- def oneline_help
13
- "バージョンを表示します"
12
+ def execute(argv)
13
+ puts ::Version + " build " + ::CommitVersion
14
14
  end
15
15
  end
16
16
  end
data/lib/commandbase.rb CHANGED
@@ -7,27 +7,49 @@ require "optparse"
7
7
 
8
8
  module Command
9
9
  class CommandBase
10
- def initialize(postfix = "")
10
+ # postfixies は改行で区切ることで2パターン以上記述できる
11
+ def initialize(postfixies = "")
11
12
  @opt = OptionParser.new(nil, 20)
12
- @opt.banner = ("<bold><green>" +
13
- TermColor.escape("Usage: narou #{self.class.to_s.scan(/::(.+)$/)[0][0].downcase} #{postfix}") +
14
- "</green></bold>").termcolor
13
+ command_name = self.class.to_s.scan(/::(.+)$/)[0][0].downcase
14
+ banner = postfixies.split("\n").map.with_index { |postfix, i|
15
+ (i == 0 ? "Usage: " : " or: ") + "narou #{command_name} #{postfix}"
16
+ }.join("\n")
17
+ @opt.banner = "<bold><green>#{TermColor.escape(banner)}</green></bold>".termcolor
15
18
  @options = {}
19
+ # ヘルプを見やすく色付け
20
+ def @opt.help
21
+ msg = super
22
+ # 見出し部分
23
+ msg.gsub!(/((?:Examples|Options|Configuration|[^\s]+? Variable List):)/) do
24
+ "<underline><bold>#{$1}</bold></underline>".termcolor
25
+ end
26
+ # Examples のコメント部分
27
+ msg.gsub!(/(#.+)$/) do
28
+ "<cyan>#{TermColor.escape($1)}</cyan>".termcolor
29
+ end
30
+ # 文字列部分
31
+ msg.gsub!(/(".+?")/) do
32
+ "<yellow>#{TermColor.escape($1)}</yellow>".termcolor
33
+ end
34
+ msg
35
+ end
16
36
  end
17
37
 
18
38
  def execute(argv)
39
+ @options.clear
40
+ load_local_settings
19
41
  @opt.parse!(argv)
20
42
  rescue OptionParser::InvalidOption => e
21
- error "不正なオプションです(#{e})"
43
+ error "不明なオプションです(#{e})"
22
44
  exit 1
23
45
  rescue OptionParser::MissingArgument => e
24
- error "オプションの引数が不正です(#{e})"
46
+ error "オプションの引数が指定されていないか正しくありません(#{e})"
25
47
  exit 1
26
48
  end
27
49
 
28
50
  def load_local_settings
29
51
  command_prefix = self.class.to_s.scan(/[^:]+$/)[0].downcase
30
- local_settings = LocalSetting.get["local_setting"]
52
+ local_settings = Inventory.load("local_setting", :local)
31
53
  local_settings.each do |name, value|
32
54
  if name =~ /^#{command_prefix}\.(.+)$/
33
55
  @options[$1] = value
@@ -35,6 +57,24 @@ module Command
35
57
  end
36
58
  end
37
59
 
60
+ #
61
+ # タグ情報をID情報に展開する
62
+ #
63
+ def tagname_to_ids(array)
64
+ database = Database.instance
65
+ tag_index = Hash.new { [] }
66
+ database.each do |id, data|
67
+ tags = data["tags"] || []
68
+ tags.each do |tag|
69
+ tag_index[tag] |= [id]
70
+ end
71
+ end
72
+ array.map! { |arg|
73
+ ids = tag_index[arg]
74
+ ids.empty? ? arg : ids
75
+ }.flatten!
76
+ end
77
+
38
78
  #
39
79
  # 普通にコマンドを実行するけど、exit(2) を補足してexitstatus を返す
40
80
  # 正常終了なら0
@@ -47,8 +87,39 @@ module Command
47
87
  0
48
88
  end
49
89
 
50
- def oneline_help(msg)
90
+ def self.oneline_help(msg)
51
91
  ""
52
92
  end
93
+
94
+ #
95
+ # 指定したメソッドを呼び出す際に、フック関数があればそれ経由で呼ぶ
96
+ #
97
+ # 指定したメソッドは存在しなくてもいい。存在しなければ空のProcが作られる
98
+ #
99
+ def hook_call(target_method, *argv)
100
+ hook = "hook_#{target_method}"
101
+ target_method_proc = self.method(target_method) rescue ->{}
102
+ if respond_to?(hook)
103
+ self.__send__(hook, *argv, &target_method_proc)
104
+ else
105
+ target_method_proc.call(*argv)
106
+ end
107
+ end
108
+
109
+ #
110
+ # 設定の強制設定
111
+ #
112
+ def force_change_settings_function(pairs)
113
+ settings = Inventory.load("local_setting", :local)
114
+ modified = false
115
+ pairs.each do |name, value|
116
+ if settings[name].nil? || settings[name] != value
117
+ settings[name] = value
118
+ puts "<bold><cyan>#{name} を #{value} に強制変更しました</cyan></bold>".termcolor
119
+ modified = true
120
+ end
121
+ end
122
+ settings.save if modified
123
+ end
53
124
  end
54
125
  end
data/lib/commandline.rb CHANGED
@@ -6,10 +6,12 @@
6
6
  require_relative "narou"
7
7
  require_relative "command"
8
8
  require_relative "helper"
9
- require_relative "localsetting"
9
+ require_relative "inventory"
10
10
 
11
11
  module CommandLine
12
- def self.run(argv)
12
+ extend self
13
+
14
+ def run(argv)
13
15
  if Helper.os_windows?
14
16
  argv.map! do |arg|
15
17
  arg.encode(Encoding::UTF_8)
@@ -30,25 +32,29 @@ module CommandLine
30
32
  puts
31
33
  arg = "help"
32
34
  end
33
- if argv.empty?
35
+ if argv.empty? && STDIN.tty?
34
36
  argv += load_default_arguments(arg)
35
37
  end
36
38
  if argv.delete("--multiple")
37
39
  multiple_argument_extract(argv)
38
40
  end
39
- Command.get_list[arg].execute(argv)
41
+ unless STDIN.tty?
42
+ # pipeで接続された場合、標準入力からIDリストを受け取って引数に繋げる
43
+ argv += STDIN.gets.split
44
+ end
45
+ Command.get_list[arg].new.execute(argv)
40
46
  end
41
47
 
42
- def self.load_default_arguments(cmd)
43
- default_arguments_list = LocalSetting.get["local_setting"]
48
+ def load_default_arguments(cmd)
49
+ default_arguments_list = Inventory.load("local_setting", :local)
44
50
  (default_arguments_list["default_args.#{cmd}"] || "").split
45
51
  end
46
52
 
47
53
  #
48
54
  # 引数をスペース以外による区切り文字で展開する
49
55
  #
50
- def self.multiple_argument_extract(argv)
51
- delimiter = LocalSetting.get["local_setting"]["multiple-delimiter"] || ","
56
+ def multiple_argument_extract(argv)
57
+ delimiter = Inventory.load("local_setting", :local)["multiple-delimiter"] || ","
52
58
  argv.map! { |arg|
53
59
  arg.split(delimiter)
54
60
  }.flatten!
data/lib/converterbase.rb CHANGED
@@ -17,10 +17,10 @@ class ConverterBase
17
17
 
18
18
  def before(io, text_type)
19
19
  data = io.string
20
- convert_page_break(data) if @text_type == "body"
21
- if @text_type != "story"
20
+ convert_page_break(data) if @text_type == "body" || @text_type == "textfile"
21
+ if @text_type != "story" && @setting.enable_pack_blank_line
22
22
  data.gsub!("\n\n", "\n")
23
- data.gsub!("\n\n\n", "\n\n")
23
+ data.gsub!(/(^\n){3}/m, "\n\n") # 改行のみの行3つを2つに削減
24
24
  end
25
25
  io
26
26
  end
@@ -48,6 +48,7 @@ class ConverterBase
48
48
  @illust_chuki_list = []
49
49
  @kanji_num_list = {}
50
50
  @num_and_comma_list = {}
51
+ @force_indent_special_chapter_list = {}
51
52
  @in_author_comment_block = nil
52
53
  end
53
54
 
@@ -187,6 +188,7 @@ class ConverterBase
187
188
  def convert_kanji_num_with_unit(data, lower_digit_zero = 0)
188
189
  data.gsub!(/([#{KANJI_NUM}十百千万億兆京]+)/) do |match|
189
190
  total = kanji_num_to_integer($1)
191
+ next match if total.to_s.length > KANJI_NUM_UNITS_DIGIT["京"] + 4
190
192
  m1 = total.to_s.tr("0-9", KANJI_NUM)
191
193
  if m1 =~ /〇{#{lower_digit_zero},}$/
192
194
  digits = m1.reverse.scan(/.{1,4}/).map(&:reverse).reverse # 下の桁から4桁ずつ区切った配列を作成
@@ -327,16 +329,21 @@ class ConverterBase
327
329
  data.replace(NKF.nkf("-wWX", data).tr("\u2014", "―"))
328
330
  end
329
331
 
332
+ # ミュート(ノノカギ)化する記号定義
333
+ SINGLE_MINUTE_FAMILY = %!‘’'!
334
+ DOUBLE_MINUTE_FAMILY = %!“”〝〟"!
335
+
330
336
  #
331
337
  # 半角記号を全角に変換
332
338
  #
333
339
  def symbols_to_zenkaku(data)
334
- data.tr!("“”‘’〝〟", %!""''""!)
335
- data.gsub!(/"([^"\n]+)"/, "〝\\1〟")
336
- data.gsub!(/'([^'\n]+)'/, "〝\\1〟") # MEMO: シングルミュート(ノノカギ)を表示出来るフォントはほとんど無い
340
+ data.gsub!(/[#{SINGLE_MINUTE_FAMILY}]([^"\n]+)[#{SINGLE_MINUTE_FAMILY}]/, "〝\\1〟")
341
+ # MEMO: シングルミュートを表示出来るフォントはほとんど無いためダブルにする
342
+ data.gsub!(/[#{DOUBLE_MINUTE_FAMILY}]([^"\n]+)[#{DOUBLE_MINUTE_FAMILY}]/, "〝\\1〟")
337
343
  data.tr!("-=+/*《》'\"%$#&!?<><>()|‐,._;:[]",
338
344
  "-=+/*≪≫’”%$#&!?〈〉〈〉()|-,._;:[]")
339
345
  data.gsub!("\\", "¥")
346
+ data
340
347
  end
341
348
 
342
349
  #
@@ -511,7 +518,7 @@ class ConverterBase
511
518
  end
512
519
 
513
520
  #
514
- # 全角数字を半角アラビア数字に
521
+ # 全角数字(漢数字含む)を半角アラビア数字に
515
522
  #
516
523
  def zenkaku_num_to_hankaku_num(num)
517
524
  num.tr("0-9#{KANJI_NUM}", "0-90-9")
@@ -536,7 +543,7 @@ class ConverterBase
536
543
  data
537
544
  end
538
545
 
539
- HALF_INDENT_TARGET = /^[  \t]*([〔「『((【〈《≪〝])/
546
+ HALF_INDENT_TARGET = /^[  \t]*((?:[〔「『((【〈《≪〝])|(?:※[#始め二重山括弧]))/
540
547
  FULL_INDENT_TARGET = /^[  \t]*(――)/
541
548
  AUTO_INDENT_IGNORE_INDENT_CHAR = Inspector::IGNORE_INDENT_CHAR.sub("・", "")
542
549
  #
@@ -546,7 +553,13 @@ class ConverterBase
546
553
  # kindle paperwhite で鍵括弧のインデントがおかしいことへの対応
547
554
  #
548
555
  def half_indent_bracket(data)
549
- data.gsub!(HALF_INDENT_TARGET, "[#二分アキ]\\1") if @setting.enable_half_indent_bracket
556
+ data.gsub!(HALF_INDENT_TARGET) do
557
+ if @setting.enable_half_indent_bracket
558
+ "[#二分アキ]#{$1}"
559
+ else
560
+ $1
561
+ end
562
+ end
550
563
  end
551
564
 
552
565
  #
@@ -572,23 +585,46 @@ class ConverterBase
572
585
  end
573
586
 
574
587
  #
575
- # 章見出しっぽい文字列を字下げして前後に空行を入れる
576
- #
577
- # TODO: 半角数字の縦中横注記をいれた影響で、2桁の半角数字が認識されてないのをどうにかする
588
+ # 章見出しっぽい文字列を字下げする
578
589
  #
579
- def force_indent_special_chapter(line)
580
- line.sub!(/^(?:[  \t]|[#二分アキ])*([-―<<〈-]*)([0-90-9#{KANJI_NUM}]+)([-―>>〉-]*)$/) do
581
- @request_insert_blank_next_line = true
590
+ def force_indent_special_chapter(data)
591
+ return unless @text_type == "body" || @text_type == "textfile"
592
+ @@count_of_rebuild_container ||= 0
593
+ data.gsub!(/^[  \t]*([-―<<〈-]*)([0-90-9#{KANJI_NUM}]{1,3})([-―>>〉-]*)$/) do
582
594
  top, chapter, bottom = $1, $2, $3
583
- if top != "" && "―--".include?(top)
595
+ pre, post = $`, $'
596
+ if top != "" && "―--".include?(top) # include?は空文字("")だとtrueなのでチェック必須
584
597
  top = "― "
585
598
  bottom = " ―"
586
599
  end
587
- (blank_line?(@before_line) ? "" : "\n") + "   [#ゴシック体]" + \
588
- top + hankaku_num_to_zenkaku_num(zenkaku_num_to_hankaku_num(chapter)) + bottom + "[#ゴシック体終わり]"
600
+ str = "   [#ゴシック体]#{top}"
601
+ str += hankaku_num_to_zenkaku_num(chapter.tr("0-9", "0-9"))
602
+ str += "#{bottom}[#ゴシック体終わり]"
603
+ # 前後に空行を入れたいが、それは行処理ループ中に行う
604
+ symbols_to_zenkaku(str)
605
+ index = @@count_of_rebuild_container += 1
606
+ @force_indent_special_chapter_list[convert_numbers(index.to_s)] = str
607
+ "[#章見出しっぽい文=#{index}]"
589
608
  end
590
609
  end
591
610
 
611
+ def rebuild_force_indent_special_chapter(data)
612
+ data.gsub!(/[#章見出しっぽい文=(.+?)]/) do
613
+ @force_indent_special_chapter_list[$1]
614
+ end
615
+ end
616
+
617
+ def insert_blank_before_line_and_behind_to_special_chapter(line)
618
+ result = ""
619
+ if line =~ /[#章見出しっぽい文=/
620
+ unless blank_line?(@before_line)
621
+ result << "\n"
622
+ end
623
+ @request_insert_blank_next_line = true
624
+ end
625
+ line.sub!(/\A/, result)
626
+ end
627
+
592
628
  #
593
629
  # 行頭空白を考慮した字下げ
594
630
  #
@@ -800,7 +836,7 @@ class ConverterBase
800
836
  to_ruby(match, $1, $2, ["≪", "≫"])
801
837
  end
802
838
  # ()なルビの対処
803
- data.gsub!(/(.+?)(([ぁ-んァ-ヶーゝゞ・A-Za-zA-Za-z ]{,20}))/) do |match|
839
+ data.gsub!(/(.+?)(([ぁ-んァ-ヶーゝゞ・A-Za-zA-Za-z  ]{,20}))/) do |match|
804
840
  to_ruby(match, $1, $2, ["(", ")"])
805
841
  end
806
842
  end
@@ -849,7 +885,8 @@ class ConverterBase
849
885
  "#{m1.sub(/|([^|]*)$/, "[#ルビ用縦線]\\1")}《#{m2}》"
850
886
  when object_of_ruby?(last_char)
851
887
  # なろうのルビ対象文字を辿って|を挿入する(青空文庫となろうのルビ仕様の差異吸収のため)
852
- m1.sub(/([#{CHARACTER_OF_RUBY} ]+)$/) {
888
+ # 空白もルビ対象文字に含むのはなろうの仕様である
889
+ m1.sub(/([#{CHARACTER_OF_RUBY}  ]+)$/) {
853
890
  match_target = $1
854
891
  if match_target =~ /^( +)/
855
892
  "#{$1}[#ルビ用縦線]#{match_target[$1.length..-1]}"
@@ -894,7 +931,7 @@ class ConverterBase
894
931
  def rebuild_url(data)
895
932
  @url_list.each_with_index do |url, id|
896
933
  data.sub!("[#URL=#{convert_numbers(id.to_s)}]",
897
- "<a href=\"#{url}\">#{url}</a>")
934
+ "<a href=\"#{Helper.ampersand_to_entity(url)}\">#{url}</a>")
898
935
  end
899
936
  end
900
937
 
@@ -920,8 +957,9 @@ class ConverterBase
920
957
  # 中黒(・)や句読点を並べて三点リーダーもどきにしているのを三点リーダーに変換
921
958
  #
922
959
  def convert_horizontal_ellipsis(data)
923
- return if !@setting.enable_convert_horizontal_ellipsis || @text_type == "subtitle" || @text_type == "chapter"
924
- %w(・ 、).each do |char|
960
+ return if !@setting.enable_convert_horizontal_ellipsis || \
961
+ @text_type == "subtitle" || @text_type == "chapter"
962
+ %w(・ 。 、 .).each do |char|
925
963
  data.gsub!(/#{char}{3,}/) do |match|
926
964
  pre_char, post_char = $`[-1], $'[0]
927
965
  if pre_char == "―" || post_char == "―"
@@ -952,21 +990,30 @@ class ConverterBase
952
990
  "[#3字下げ][#ここから中見出し]#{midashi_title}[#ここで中見出し終わり]"
953
991
  end
954
992
 
993
+ def calc_cr_count(str)
994
+ head_cr_count = str.index(/[^\n]/)
995
+ head_cr_count > 2 ? 2 : head_cr_count
996
+ end
997
+
998
+ # 実際に見出しを付与する
955
999
  data.gsub!(/[#改ページ]\n(.+?)\n/) do |match|
956
1000
  m1 = $1
957
1001
  rest = $'
1002
+ # 前書きがある場合は今回は保留して、次の処理で見出しを付与する
958
1003
  if $1 =~ /#{AUTHOR_COMMENT_CHUKI[:introduction][:open]}/
959
1004
  match
960
1005
  else
961
1006
  # 見出しの次の行が空行ではない場合空行を追加する
962
- add_tail = rest =~ /\A$/ ? "" : "\n\n"
1007
+ add_tail = "\n" * (2 - calc_cr_count(rest))
1008
+ # 見出しと本文の間には空行を2行挟む
963
1009
  "[#改ページ]\n#{midashi(m1)}\n#{add_tail}"
964
1010
  end
965
1011
  end
1012
+ # 前書きがある場合は、前書き→見出しの順番を見出し→前書きに入れ替えて置換
966
1013
  data.gsub!(/([#改ページ]\n)(#{AUTHOR_COMMENT_CHUKI[:introduction][:open]}.+?#{AUTHOR_COMMENT_CHUKI[:introduction][:close]}\n)(.+?\n)/m) do
967
1014
  m1, m2, m3 = $1, $2, $3
968
1015
  add_tail = $' =~ /\A$/ ? "" : "\n"
969
- "#{m1 + midashi(m3) + m2}#{add_tail}" # 前書き→見出しの順番を見出し→前書きに入れ替えて置換
1016
+ "#{m1 + midashi(m3) + m2}#{add_tail}"
970
1017
  end
971
1018
  end
972
1019
 
@@ -1003,9 +1050,9 @@ class ConverterBase
1003
1050
  #
1004
1051
  def convert_page_break(data)
1005
1052
  if @setting.enable_convert_page_break
1006
- threshold = @setting.to_page_break_threshold + 1
1053
+ threshold = @setting.to_page_break_threshold
1007
1054
  # `改ページ' を使うと見出し付与等で混乱するので自動生成したものは区別する
1008
- data.gsub!(/\n{#{threshold},}/, "\n[#改頁]\n")
1055
+ data.gsub!(/(^\n){#{threshold},}/, "[#改頁]\n")
1009
1056
  end
1010
1057
  end
1011
1058
 
@@ -1030,6 +1077,7 @@ class ConverterBase
1030
1077
  replace_narou_tag(data)
1031
1078
  convert_rome_numeric(data)
1032
1079
  alphabet_to_zenkaku(data, @setting.enable_alphabet_force_zenkaku)
1080
+ force_indent_special_chapter(data)
1033
1081
  convert_numbers(data)
1034
1082
  exception_reconvert_kanji_to_num(data)
1035
1083
  if @setting.enable_convert_num_to_kanji && @text_type != "subtitle" && @text_type != "chapter" \
@@ -1105,10 +1153,11 @@ class ConverterBase
1105
1153
  if @request_insert_blank_next_line
1106
1154
  outputs unless blank_line?(line)
1107
1155
  @request_insert_blank_next_line = false
1156
+ @before_line = ""
1108
1157
  end
1109
1158
  process_author_comment(line) if @text_type == "textfile"
1159
+ insert_blank_before_line_and_behind_to_special_chapter(line)
1110
1160
  insert_blank_line_to_border_symbol(line)
1111
- force_indent_special_chapter(line)
1112
1161
 
1113
1162
  outputs(line)
1114
1163
  unless @delay_outputs_buffer.empty?
@@ -1138,6 +1187,7 @@ class ConverterBase
1138
1187
  rebuild_english_sentences(data)
1139
1188
  rebuild_hankaku_num_and_comma(data)
1140
1189
  rebuild_kome_to_gaiji(data)
1190
+ rebuild_force_indent_special_chapter(data)
1141
1191
  # 再構築された文章にルビがふられる可能性を考慮して、
1142
1192
  # この位置でルビの処理を行う
1143
1193
  narou_ruby(data) if @setting.enable_ruby