OSDN Git Service

svnプロパティをファイルの種類に応じたものに更新
[wptscs/wpts.git] / Wptscs / Logics / MediaWikiTranslator.cs
1 // ================================================================================================
2 // <summary>
3 //      Wikipedia用の翻訳支援処理実装クラスソース</summary>
4 //
5 // <copyright file="MediaWikiTranslator.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
7 // <author>
8 //      Honeplus</author>
9 // ================================================================================================
10
11 namespace Honememo.Wptscs.Logics
12 {
13     using System;
14     using System.Collections.Generic;
15     using System.IO;
16     using System.Net;
17     using System.Text;
18     using System.Windows.Forms;
19     using Honememo.Utilities;
20     using Honememo.Wptscs.Models;
21     using Honememo.Wptscs.Properties;
22
23     /// <summary>
24     /// Wikipedia用の翻訳支援処理実装クラスです。
25     /// </summary>
26     public class MediaWikiTranslator : Translator
27     {
28         #region プロパティ
29
30         /// <summary>
31         /// 翻訳元言語のサイト。
32         /// </summary>
33         public new MediaWiki From
34         {
35             get
36             {
37                 return base.From as MediaWiki;
38             }
39
40             set
41             {
42                 base.From = value;
43             }
44         }
45
46         /// <summary>
47         /// 翻訳先言語のサイト。
48         /// </summary>
49         public new MediaWiki To
50         {
51             get
52             {
53                 return base.To as MediaWiki;
54             }
55
56             set
57             {
58                 base.To = value;
59             }
60         }
61         
62         #endregion
63
64         #region 整理予定の静的メソッド
65
66         /// <summary>
67         /// コメント区間のチェック。
68         /// </summary>
69         /// <param name="comment">解析したコメント。</param>
70         /// <param name="text">解析するテキスト。</param>
71         /// <param name="index">解析開始インデックス。</param>
72         /// <returns>コメント区間の場合、終了位置のインデックスを返す。それ以外は-1。</returns>
73         public static int ChkComment(out string comment, string text, int index)
74         {
75             // 入力値確認
76             if (String.IsNullOrEmpty(text))
77             {
78                 comment = String.Empty;
79                 return -1;
80             }
81
82             // 改良版メソッドをコール
83             if (!LazyXmlParser.TryParseComment(text.Substring(index), out comment))
84             {
85                 comment = String.Empty;
86                 return -1;
87             }
88
89             return index + comment.Length - 1;
90         }
91
92         /// <summary>
93         /// nowiki区間のチェック。
94         /// </summary>
95         /// <param name="nowiki">解析したnowikiブロック。</param>
96         /// <param name="text">解析するテキスト。</param>
97         /// <param name="index">解析開始インデックス。</param>
98         /// <returns>nowiki区間の場合、終了位置のインデックスを返す。それ以外は-1。</returns>
99         public static int ChkNowiki(out string nowiki, string text, int index)
100         {
101             // 入力値確認
102             if (String.IsNullOrEmpty(text))
103             {
104                 nowiki = String.Empty;
105                 return -1;
106             }
107
108             // 改良版メソッドをコール
109             if (!MediaWikiPage.TryParseNowiki(text.Substring(index), out nowiki))
110             {
111                 nowiki = String.Empty;
112                 return -1;
113             }
114
115             return index + nowiki.Length - 1;
116         }
117
118         #endregion
119
120         #region メイン処理メソッド
121
122         /// <summary>
123         /// 翻訳支援処理実行部の本体。
124         /// ※継承クラスでは、この関数に処理を実装すること
125         /// </summary>
126         /// <param name="name">記事名。</param>
127         /// <returns><c>true</c> 処理成功。</returns>
128         protected override bool RunBody(string name)
129         {
130             System.Diagnostics.Debug.WriteLine("\nMediaWikiTranslator.runBody > " + name);
131
132             // 対象記事を取得
133             MediaWikiPage article = this.ChkTargetArticle(name);
134             if (article == null)
135             {
136                 return false;
137             }
138
139             // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
140             string interWiki = article.GetInterWiki(this.To.Language.Code);
141             if (interWiki != String.Empty)
142             {
143                 if (MessageBox.Show(
144                         String.Format(Resources.QuestionMessage_ArticleExist, interWiki),
145                         Resources.QuestionTitle,
146                         MessageBoxButtons.YesNo,
147                         MessageBoxIcon.Question)
148                    == System.Windows.Forms.DialogResult.No)
149                 {
150                     this.LogLine(ENTER + String.Format(Resources.QuestionMessage_ArticleExist, interWiki));
151                     return false;
152                 }
153                 else
154                 {
155                     this.LogLine(Resources.RightArrow + " " + String.Format(Resources.LogMessage_ArticleExistInterWiki, interWiki));
156                 }
157             }
158
159             // 冒頭部を作成
160             this.Text += "'''xxx'''";
161             string bracket = this.To.Language.Bracket;
162             if (bracket.Contains("{0}"))
163             {
164                 string originalName = String.Empty;
165                 string langTitle = this.GetFullName(this.From, this.To.Language.Code);
166                 if (langTitle != String.Empty)
167                 {
168                     originalName = "[[" + langTitle + "]]: ";
169                 }
170
171                 this.Text += String.Format(bracket, originalName + "'''" + name + "'''");
172             }
173
174             this.Text += "\n\n";
175
176             // 言語間リンク・定型句の変換
177             this.LogLine(ENTER + Resources.RightArrow + " " + String.Format(Resources.LogMessage_CheckAndReplaceStart, interWiki));
178             this.Text += this.ReplaceText(article.Text, article.Title);
179
180             // ユーザーからの中止要求をチェック
181             if (CancellationPending)
182             {
183                 return false;
184             }
185
186             // 新しい言語間リンクと、コメントを追記
187             this.Text += "\n\n[[" + this.From.Language.Code + ":" + name + "]]\n";
188             this.Text += String.Format(
189                 Resources.ArticleFooter,
190                 FormUtils.ApplicationName(),
191                 this.From.Language.Code,
192                 name,
193                 article.Timestamp.HasValue ? article.Timestamp.Value.ToString("U") : String.Empty) + "\n";
194
195             // ダウンロードされるテキストがLFなので、ここで全てCRLFに変換
196             // ※ダウンロード時にCRLFにするような仕組みが見つかれば、そちらを使う
197             //   その場合、上のように\nをべたに吐いている部分を修正する
198             this.Text = this.Text.Replace("\n", ENTER);
199
200             System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.runBody > Success!");
201             return true;
202         }
203
204         #endregion
205
206         #region 他のクラスの処理をこのクラスにあわせて拡張したメソッド
207
208         /// <summary>
209         /// ログメッセージを出力しつつページを取得。
210         /// </summary>
211         /// <param name="title">ページタイトル。</param>
212         /// <param name="notFoundMsg">取得できない場合に出力するメッセージ。</param>
213         /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
214         /// <remarks>通信エラーなど例外が発生した場合は、別途エラーログを出力する。</remarks>
215         protected new MediaWikiPage GetPage(string title, string notFoundMsg)
216         {
217             // 親クラスのメソッドを戻り値の型だけ変更
218             return base.GetPage(title, notFoundMsg) as MediaWikiPage;
219         }
220
221         #endregion
222
223         #region 各処理のメソッド
224         
225         /// <summary>
226         /// 翻訳支援対象のページを取得。
227         /// </summary>
228         /// <param name="title">ページ名。</param>
229         /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
230         protected MediaWikiPage ChkTargetArticle(string title)
231         {
232             // 指定された記事の生データをWikipediaから取得
233             this.LogLine(String.Format(Resources.LogMessage_GetArticle, this.From.Location, title));
234             MediaWikiPage page = this.GetPage(title, Resources.RightArrow + " " + Resources.LogMessage_ArticleNothing);
235
236             // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
237             if (page != null && page.IsRedirect())
238             {
239                 this.LogLine(Resources.RightArrow + " " + Resources.LogMessage_Redirect + " [[" + page.Redirect.Title + "]]");
240                 page = this.GetPage(page.Redirect.Title, Resources.RightArrow + " " + Resources.LogMessage_ArticleNothing);
241             }
242
243             return page;
244         }
245
246         /// <summary>
247         /// ログメッセージを出力しつつ、指定された記事の指定された言語コードへの言語間リンクを返す。
248         /// </summary>
249         /// <param name="title">記事名。</param>
250         /// <param name="code">言語コード。</param>
251         /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
252         protected string GetInterWiki(string title, string code)
253         {
254             MediaWikiPage page = this.GetPage(title, Resources.LogMessage_LinkArticleNothing);
255
256             // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
257             if (page != null && page.IsRedirect())
258             {
259                 this.Log += Resources.LogMessage_Redirect + " [[" + page.Redirect.Title + "]] " + Resources.RightArrow + " ";
260                 page = this.GetPage(page.Redirect.Title, Resources.LogMessage_LinkArticleNothing);
261             }
262
263             // 記事があればその言語間リンクを取得
264             string interWiki = null;
265             if (page != null)
266             {
267                 interWiki = page.GetInterWiki(this.To.Language.Code);
268                 if (!String.IsNullOrEmpty(interWiki))
269                 {
270                     Log += "[[" + interWiki + "]]";
271                 }
272                 else
273                 {
274                     Log += Resources.LogMessage_InterWikiNothing;
275                 }
276             }
277
278             return interWiki;
279         }
280
281         /// <summary>
282         /// ログメッセージを出力しつつ、指定された記事の指定された言語コードへの言語間リンクを返す。
283         /// </summary>
284         /// <param name="title">記事名。</param>
285         /// <param name="code">言語コード。</param>
286         /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
287         /// <remarks>対訳表が指定されている場合、その内容を使用する。また取得結果を対訳表に追加する。</remarks>
288         protected string GetInterWikiUseTable(string title, string code)
289         {
290             if (this.ItemTable == null)
291             {
292                 // 対訳表が指定されていない場合は、普通に記事を取得
293                 return this.GetInterWiki(title, code);
294             }
295
296             string interWiki = null;
297             lock (this.ItemTable)
298             {
299                 TranslationDictionary.Item item;
300                 if (this.ItemTable.TryGetValue(title, out item))
301                 {
302                     // 対訳表に存在する場合はその値を使用
303                     // リダイレクトがあれば、そのメッセージも表示
304                     if (!String.IsNullOrWhiteSpace(item.Alias))
305                     {
306                         this.Log += Resources.LogMessage_Redirect + " [[" + item.Alias + "]] " + Resources.RightArrow + " ";
307                     }
308
309                     if (!String.IsNullOrEmpty(item.Word))
310                     {
311                         interWiki = item.Word;
312                         Log += "[[" + interWiki + "]]";
313                     }
314                     else
315                     {
316                         interWiki = String.Empty;
317                         Log += Resources.LogMessage_InterWikiNothing;
318                     }
319
320                     Log += Resources.LogMessageTranslation;
321                     return interWiki;
322                 }
323
324                 // 対訳表に存在しない場合は、普通に取得し表に記録
325                 // ※ nullも存在しないことの記録として格納
326                 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
327                 MediaWikiPage page = this.GetPage(title, Resources.LogMessage_LinkArticleNothing);
328
329                 // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
330                 if (page != null && page.IsRedirect())
331                 {
332                     item.Alias = page.Redirect.Title;
333                     this.Log += Resources.LogMessage_Redirect + " [[" + page.Redirect.Title + "]] " + Resources.RightArrow + " ";
334                     page = this.GetPage(page.Redirect.Title, Resources.LogMessage_LinkArticleNothing);
335                 }
336
337                 // 記事があればその言語間リンクを取得
338                 if (page != null)
339                 {
340                     interWiki = page.GetInterWiki(this.To.Language.Code);
341                     if (!String.IsNullOrEmpty(interWiki))
342                     {
343                         Log += "[[" + interWiki + "]]";
344                     }
345                     else
346                     {
347                         Log += Resources.LogMessage_InterWikiNothing;
348                     }
349
350                     item.Word = interWiki;
351                     this.ItemTable[title] = item;
352                 }
353             }
354
355             return interWiki;
356         }
357         
358         /// <summary>
359         /// 指定された記事を取得し、言語間リンクを確認、返す。
360         /// </summary>
361         /// <param name="title">記事名。</param>
362         /// <param name="template"><c>true</c> テンプレート。</param>
363         /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
364         protected string GetInterWiki(string title, bool template)
365         {
366             // 指定された記事の生データをWikipediaから取得
367             // ※記事自体が存在しない場合、NULLを返す
368             if (!template)
369             {
370                 Log += "[[" + title + "]] " + Resources.RightArrow + " ";
371             }
372             else
373             {
374                 Log += "{{" + title + "}} " + Resources.RightArrow + " ";
375             }
376
377             // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
378             string interWiki = this.GetInterWikiUseTable(title, this.To.Language.Code);
379
380             // 改行が出力されていない場合(正常時)、改行
381             if (!Log.EndsWith(ENTER))
382             {
383                 Log += ENTER;
384             }
385
386             return interWiki;
387         }
388
389         /// <summary>
390         /// 指定された記事を取得し、言語間リンクを確認、返す(テンプレート以外)。
391         /// </summary>
392         /// <param name="name">記事名。</param>
393         /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
394         protected string GetInterWiki(string name)
395         {
396             return this.GetInterWiki(name, false);
397         }
398
399         /// <summary>
400         /// 渡されたテキストを解析し、言語間リンク・見出し等の変換を行う。
401         /// </summary>
402         /// <param name="text">記事テキスト。</param>
403         /// <param name="parent">元記事タイトル。</param>
404         /// <param name="headingEnable">見出しのチェックを行うか?</param>
405         /// <returns>変換後の記事テキスト。</returns>
406         protected string ReplaceText(string text, string parent, bool headingEnable)
407         {
408             // 指定された記事の言語間リンク・見出しを探索し、翻訳先言語での名称に変換し、それに置換した文字列を返す
409             StringBuilder b = new StringBuilder();
410             bool enterFlag = true;
411             MediaWikiPage wikiAP = new MediaWikiPage(this.From, "dummy", null);
412             for (int i = 0; i < text.Length; i++)
413             {
414                 // ユーザーからの中止要求をチェック
415                 if (CancellationPending == true)
416                 {
417                     break;
418                 }
419
420                 char c = text[i];
421
422                 // 見出しも処理対象の場合
423                 if (headingEnable)
424                 {
425                     // 改行の場合、次のループで見出し行チェックを行う
426                     if (c == '\n')
427                     {
428                         enterFlag = true;
429                         b.Append(c);
430                         continue;
431                     }
432
433                     // 行の始めでは、その行が見出しの行かのチェックを行う
434                     if (enterFlag)
435                     {
436                         string newTitleLine;
437                         int index2 = this.ChkTitleLine(out newTitleLine, text, i);
438                         if (index2 != -1)
439                         {
440                             // 行の終わりまでインデックスを移動
441                             i = index2;
442
443                             // 置き換えられた見出し行を出力
444                             b.Append(newTitleLine);
445                             continue;
446                         }
447                         else
448                         {
449                             enterFlag = false;
450                         }
451                     }
452                 }
453
454                 // コメント(<!--)のチェック
455                 string comment;
456                 int index = MediaWikiTranslator.ChkComment(out comment, text, i);
457                 if (index != -1)
458                 {
459                     i = index;
460                     b.Append(comment);
461                     if (comment.Contains("\n") == true)
462                     {
463                         enterFlag = true;
464                     }
465
466                     continue;
467                 }
468
469                 // nowikiのチェック
470                 string nowiki;
471                 index = MediaWikiTranslator.ChkNowiki(out nowiki, text, i);
472                 if (index != -1)
473                 {
474                     i = index;
475                     b.Append(nowiki);
476                     continue;
477                 }
478
479                 // 変数({{{1}}}とか)のチェック
480                 string variable;
481                 string value;
482                 index = wikiAP.ChkVariable(out variable, out value, text, i);
483                 if (index != -1)
484                 {
485                     i = index;
486
487                     // 変数の | 以降に値が記述されている場合、それに対して再帰的に処理を行う
488                     int valueIndex = variable.IndexOf('|');
489                     if (valueIndex != -1 && !String.IsNullOrEmpty(value))
490                     {
491                         variable = variable.Substring(0, valueIndex + 1) + this.ReplaceText(value, parent) + "}}}";
492                     }
493
494                     b.Append(variable);
495                     continue;
496                 }
497
498                 // 内部リンク・テンプレートのチェック&変換、言語間リンクを取得し出力する
499                 string subtext;
500                 index = this.ReplaceLink(out subtext, text, i, parent);
501                 if (index != -1)
502                 {
503                     i = index;
504                     b.Append(subtext);
505                     continue;
506                 }
507
508                 // 通常はそのままコピー
509                 b.Append(text[i]);
510             }
511
512             return b.ToString();
513         }
514
515         /// <summary>
516         /// 渡されたテキストを解析し、言語間リンク・見出し等の変換を行う。
517         /// </summary>
518         /// <param name="text">記事テキスト。</param>
519         /// <param name="parent">元記事タイトル。</param>
520         /// <returns>変換後の記事テキスト。</returns>
521         protected string ReplaceText(string text, string parent)
522         {
523             return this.ReplaceText(text, parent, true);
524         }
525
526         /// <summary>
527         /// リンクの解析・置換を行う。
528         /// </summary>
529         /// <param name="link">解析したリンク。</param>
530         /// <param name="text">解析するテキスト。</param>
531         /// <param name="index">解析開始インデックス。</param>
532         /// <param name="parent">元記事タイトル。</param>
533         /// <returns>リンクの場合、終了位置のインデックスを返す。それ以外は-1。</returns>
534         protected int ReplaceLink(out string link, string text, int index, string parent)
535         {
536             // 出力値初期化
537             int lastIndex = -1;
538             link = String.Empty;
539             MediaWikiPage.Link l;
540
541             // 内部リンク・テンプレートの確認と解析
542             MediaWikiPage wikiAP = new MediaWikiPage(this.From, "dummy", null);
543             lastIndex = wikiAP.ChkLinkText(out l, text, index);
544             if (lastIndex != -1)
545             {
546                 // 記事名に変数が使われている場合があるので、そのチェックと展開
547                 int subindex = l.Title.IndexOf("{{{");
548                 if (subindex != -1)
549                 {
550                     string variable;
551                     string value;
552                     int lastIndex2 = wikiAP.ChkVariable(out variable, out value, l.Title, subindex);
553                     if (lastIndex2 != -1 && !String.IsNullOrEmpty(value))
554                     {
555                         // 変数の | 以降に値が記述されている場合、それに置き換える
556                         string newArticle = l.Title.Substring(0, subindex) + value;
557                         if (lastIndex2 + 1 < l.Title.Length)
558                         {
559                             newArticle += l.Title.Substring(lastIndex2 + 1);
560                         }
561
562                         l.Title = newArticle;
563                     }
564                     else
565                     {
566                         // 値が設定されていない場合、処理してもしょうがないので、除外
567                         System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceLink > 対象外 : " + l.OriginalText);
568                         return -1;
569                     }
570                 }
571
572                 string newText = null;
573
574                 // 内部リンクの場合
575                 if (text[index] == '[')
576                 {
577                     // 内部リンクの変換後文字列を取得
578                     newText = this.ReplaceInnerLink(l, parent);
579                 }
580                 else if (text[index] == '{')
581                 {
582                     // テンプレートの場合
583                     // テンプレートの変換後文字列を取得
584                     newText = this.ReplaceTemplate(l, parent);
585                 }
586                 else
587                 {
588                     // 上記以外の場合は、対象外
589                     System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceLink > プログラムミス : " + l.OriginalText);
590                 }
591
592                 // 変換後文字列がNULL以外
593                 if (newText != null)
594                 {
595                     link = newText;
596                 }
597                 else
598                 {
599                     lastIndex = -1;
600                 }
601             }
602
603             return lastIndex;
604         }
605
606         /// <summary>
607         /// 内部リンクの文字列を変換する。
608         /// </summary>
609         /// <param name="link">変換元リンク文字列。</param>
610         /// <param name="parent">元記事タイトル。</param>
611         /// <returns>変換済みリンク文字列。</returns>
612         protected string ReplaceInnerLink(MediaWikiPage.Link link, string parent)
613         {
614             // 変数初期設定
615             StringBuilder b = new StringBuilder("[[");
616             string comment = String.Empty;
617             MediaWikiPage.Link l = link;
618
619             // 記事内を指している場合([[#関連項目]]だけとか)以外
620             if (!String.IsNullOrEmpty(l.Title) &&
621                !(l.Title == parent && String.IsNullOrEmpty(l.Code) && !String.IsNullOrEmpty(l.Section)))
622             {
623                 // 変換の対象外とするリンクかをチェック
624                 MediaWikiPage article = new MediaWikiPage(this.From, l.Title);
625
626                 // サブページの場合、記事名を補填
627                 if (l.IsSubpage)
628                 {
629                     l.Title = parent + l.Title;
630                 }
631                 else if (!String.IsNullOrEmpty(l.Code))
632                 {
633                     // 言語間リンク・姉妹プロジェクトへのリンクは対象外
634                     // 先頭が : でない、翻訳先言語への言語間リンクの場合
635                     if (!l.IsColon && l.Code == this.To.Language.Code)
636                     {
637                         // 削除する。正常終了で、置換後文字列なしを返す
638                         System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceInnerLink > " + l.OriginalText + " を削除");
639                         return String.Empty;
640                     }
641
642                     // それ以外は対象外
643                     System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceInnerLink > 対象外 : " + l.OriginalText);
644                     return null;
645                 }
646                 else if (article.IsFile())
647                 {
648                     // 画像も対象外だが、名前空間だけ翻訳先言語の書式に変換
649                     return this.ReplaceFileLink(l);
650                 }
651
652                 // リンクを辿り、対象記事の言語間リンクを取得
653                 string interWiki = this.GetInterWiki(l.Title);
654
655                 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
656                 if (interWiki == null)
657                 {
658                     b.Append(l.Title);
659                 }
660                 else if (interWiki == String.Empty)
661                 {
662                     // 言語間リンクが存在しない場合、[[:en:xxx]]みたいな形式に置換
663                     b.Append(":");
664                     b.Append(this.From.Language.Code);
665                     b.Append(":");
666                     b.Append(l.Title);
667                 }
668                 else
669                 {
670                     // 言語間リンクが存在する場合、そちらを指すように置換
671                     // 前の文字列を復元
672                     if (l.IsSubpage)
673                     {
674                         int index = interWiki.IndexOf('/');
675                         if (index == -1)
676                         {
677                             index = 0;
678                         }
679
680                         b.Append(interWiki.Substring(index));
681                     }
682                     else if (l.IsColon)
683                     {
684                         b.Append(":");
685                         b.Append(interWiki);
686                     }
687                     else
688                     {
689                         b.Append(interWiki);
690                     }
691                 }
692
693                 // カテゴリーの場合は、コメントで元の文字列を追加する
694                 if (article.IsCategory() && !l.IsColon)
695                 {
696                     comment = "<!-- " + l.OriginalText + " -->";
697
698                     // カテゴリーで[[:en:xxx]]みたいな形式にした場合、| 以降は不要なので削除
699                     if (interWiki == String.Empty)
700                     {
701                         l.PipeTexts = new List<string>();
702                     }
703                 }
704                 else if (l.PipeTexts.Count == 0 && interWiki != null)
705                 {
706                     // 表示名が存在しない場合、元の名前を表示名に設定
707                     l.PipeTexts.Add(article.Title);
708                 }
709             }
710
711             // 見出し([[#関連項目]]とか)を出力
712             if (!String.IsNullOrEmpty(l.Section))
713             {
714                 // 見出しは、定型句変換を通す
715                 string heading = this.GetHeading(l.Section);
716                 b.Append("#");
717                 b.Append(heading != null ? heading : l.Section);
718             }
719
720             // 表示名を出力
721             foreach (string text in l.PipeTexts)
722             {
723                 b.Append("|");
724                 if (!String.IsNullOrEmpty(text))
725                 {
726                     // 画像の場合、| の後に内部リンクやテンプレートが書かれている場合があるが、
727                     // 画像は処理対象外でありその中のリンクは個別に再度処理されるため、ここでは特に何もしない
728                     b.Append(text);
729                 }
730             }
731
732             // リンクを閉じる
733             b.Append("]]");
734
735             // コメントを付加
736             if (comment != String.Empty)
737             {
738                 b.Append(comment);
739             }
740
741             System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceInnerLink > " + l.OriginalText);
742             return b.ToString();
743         }
744
745         /// <summary>
746         /// テンプレートの文字列を変換する。
747         /// </summary>
748         /// <param name="link">変換元テンプレート文字列。</param>
749         /// <param name="parent">元記事タイトル。</param>
750         /// <returns>変換済みテンプレート文字列。</returns>
751         protected string ReplaceTemplate(MediaWikiPage.Link link, string parent)
752         {
753             // 変数初期設定
754             MediaWikiPage.Link l = link;
755
756             // テンプレートは記事名が必須
757             if (String.IsNullOrEmpty(l.Title))
758             {
759                 System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceTemplate > 対象外 : " + l.OriginalText);
760                 return null;
761             }
762
763             // システム変数の場合は対象外
764             if (this.From.IsMagicWord(l.Title))
765             {
766                 System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceTemplate > システム変数 : " + l.OriginalText);
767                 return null;
768             }
769
770             // テンプレート名前空間か、普通の記事かを判定
771             if (!l.IsColon && !l.IsSubpage)
772             {
773                 string prefix = null;
774                 IList<string> prefixes = this.From.Namespaces[this.From.TemplateNamespace];
775                 if (prefixes != null && prefixes.Count > 0)
776                 {
777                     prefix = prefixes[0];
778                 }
779
780                 if (!String.IsNullOrEmpty(prefix) && !l.Title.StartsWith(prefix + ":"))
781                 {
782                     // 頭にTemplate:を付けた記事名でアクセスし、テンプレートが存在するかをチェック
783                     string title = prefix + ":" + l.Title;
784                     MediaWikiPage page = null;
785                     try
786                     {
787                         page = this.From.GetPage(title) as MediaWikiPage;
788                     }
789                     catch (WebException e)
790                     {
791                         if (e.Status == WebExceptionStatus.ProtocolError
792                             && (e.Response as HttpWebResponse).StatusCode != HttpStatusCode.NotFound)
793                         {
794                             // 記事が取得できない場合も、404でない場合は存在するとして処理
795                             this.LogLine(String.Format(Resources.LogMessage_TemplateUnknown, l.Title, prefix, e.Message));
796                             l.Title = title;
797                         }
798                     }
799                     catch (Exception e)
800                     {
801                         System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.ReplaceTemplate > " + e.Message);
802                     }
803
804                     if (page != null)
805                     {
806                         // 記事が存在する場合、テンプレートをつけた名前を使用
807                         l.Title = title;
808                     }
809                 }
810             }
811             else if (l.IsSubpage)
812             {
813                 // サブページの場合、記事名を補填
814                 l.Title = parent + l.Title;
815             }
816
817             // リンクを辿り、対象記事の言語間リンクを取得
818             string interWiki = this.GetInterWiki(l.Title, true);
819
820             // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
821             StringBuilder b = new StringBuilder();
822             if (interWiki == null)
823             {
824                 b.Append(l.OriginalText);
825             }
826             else if (interWiki == String.Empty)
827             {
828                 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
829                 // おまけで、元のテンプレートの状態をコメントでつける
830                 b.Append("[[:");
831                 b.Append(this.From.Language.Code);
832                 b.Append(":");
833                 b.Append(l.Title);
834                 b.Append("]]<!-- ");
835                 b.Append(l.OriginalText);
836                 b.Append(" -->");
837             }
838             else
839             {
840                 // 言語間リンクが存在する場合、そちらを指すように置換
841                 b.Append("{{");
842
843                 // 前の文字列を復元
844                 if (l.IsColon)
845                 {
846                     b.Append(":");
847                 }
848
849                 if (l.IsMsgnw)
850                 {
851                     b.Append(MediaWikiPage.Msgnw);
852                 }
853
854                 // : より前の部分を削除して出力(: が無いときは-1+1で0から)
855                 b.Append(interWiki.Substring(interWiki.IndexOf(':') + 1));
856
857                 // 改行を復元
858                 if (l.Enter)
859                 {
860                     b.Append("\n");
861                 }
862
863                 // | の後を付加
864                 foreach (string text in l.PipeTexts)
865                 {
866                     b.Append("|");
867                     if (!String.IsNullOrEmpty(text))
868                     {
869                         // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
870                         b.Append(this.ReplaceText(text, parent));
871                     }
872                 }
873
874                 // リンクを閉じる
875                 b.Append("}}");
876             }
877
878             System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceTemplate > " + l.OriginalText);
879             return b.ToString();
880         }
881
882         /// <summary>
883         /// 指定されたインデックスの位置に存在する見出し(==関連項目==みたいなの)を解析し、可能であれば変換して返す。
884         /// </summary>
885         /// <param name="heading">変換後の見出し。</param>
886         /// <param name="text">解析するテキスト。</param>
887         /// <param name="index">解析開始インデックス。</param>
888         /// <returns>見出しの場合、見出し終了位置のインデックスを返す。それ以外は-1。</returns>
889         protected virtual int ChkTitleLine(out string heading, string text, int index)
890         {
891             // 初期化
892             // ※見出しではない、構文がおかしいなどの場合、-1を返す
893             int lastIndex = -1;
894             
895             // 構文を解析して、1行の文字列と、=の個数を取得
896             // ※構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
897             // ※Wikipediaでは <!--test-.=<!--test-.=関連項目<!--test-.==<!--test-. みたいなのでも
898             //   正常に認識するので、できるだけ対応する
899             // ※変換が正常に行われた場合、コメントは削除される
900             bool startFlag = true;
901             int startSignCounter = 0;
902             string nonCommentLine = String.Empty;
903             StringBuilder b = new StringBuilder();
904             for (lastIndex = index; lastIndex < text.Length; lastIndex++)
905             {
906                 char c = text[lastIndex];
907
908                 // 改行まで
909                 if (c == '\n')
910                 {
911                     break;
912                 }
913
914                 // コメントは無視する
915                 string comment;
916                 int subindex = MediaWikiTranslator.ChkComment(out comment, text, lastIndex);
917                 if (subindex != -1)
918                 {
919                     b.Append(comment);
920                     lastIndex = subindex;
921                     continue;
922                 }
923                 else if (startFlag)
924                 {
925                     // 先頭部の場合、=の数を数える
926                     if (c == '=')
927                     {
928                         ++startSignCounter;
929                     }
930                     else
931                     {
932                         startFlag = false;
933                     }
934                 }
935
936                 nonCommentLine += c;
937                 b.Append(c);
938             }
939
940             heading = b.ToString();
941
942             // 改行文字、または文章の最後+1になっているはずなので、1文字戻す
943             --lastIndex;
944
945             // = で始まる行ではない場合、処理対象外
946             if (startSignCounter < 1)
947             {
948                 heading = String.Empty;
949                 return -1;
950             }
951
952             // 終わりの = の数を確認
953             // ※↓の処理だと中身の無い行(====とか)は弾かれてしまうが、どうせ処理できないので許容する
954             int endSignCounter = 0;
955             for (int i = nonCommentLine.Length - 1; i >= startSignCounter; i--)
956             {
957                 if (nonCommentLine[i] == '=')
958                 {
959                     ++endSignCounter;
960                 }
961                 else
962                 {
963                     break;
964                 }
965             }
966
967             // = で終わる行ではない場合、処理対象外
968             if (endSignCounter < 1)
969             {
970                 heading = String.Empty;
971                 return -1;
972             }
973
974             // 始まりと終わり、=の少ないほうにあわせる(==test===とか用の処理)
975             int signCounter = startSignCounter;
976             if (startSignCounter > endSignCounter)
977             {
978                 signCounter = endSignCounter;
979             }
980
981             // 定型句変換
982             string oldText = nonCommentLine.Substring(signCounter, nonCommentLine.Length - (signCounter * 2)).Trim();
983             string newText = this.GetHeading(oldText);
984             if (newText != null)
985             {
986                 string sign = "=";
987                 for (int i = 1; i < signCounter; i++)
988                 {
989                     sign += "=";
990                 }
991
992                 string newHeading = sign + newText + sign;
993                 this.LogLine(ENTER + heading + " " + Resources.RightArrow + " " + newHeading);
994                 heading = newHeading;
995             }
996             else
997             {
998                 this.LogLine(ENTER + heading);
999             }
1000
1001             return lastIndex;
1002         }
1003
1004         /// <summary>
1005         /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
1006         /// </summary>
1007         /// <param name="heading">翻訳元言語での見出し。</param>
1008         /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
1009         protected string GetHeading(string heading)
1010         {
1011             return this.HeadingTable.GetWord(heading);
1012         }
1013
1014         /// <summary>
1015         /// 指定した言語での言語名称を ページ名|略称 の形式で取得。
1016         /// </summary>
1017         /// <param name="site">サイト。</param>
1018         /// <param name="code">言語のコード。</param>
1019         /// <returns>ページ名|略称形式の言語名称。</returns>
1020         protected string GetFullName(Website site, string code)
1021         {
1022             if (site.Language.Names.ContainsKey(code))
1023             {
1024                 Language.LanguageName name = site.Language.Names[code];
1025                 if (!String.IsNullOrEmpty(name.ShortName))
1026                 {
1027                     return name.Name + "|" + name.ShortName;
1028                 }
1029                 else
1030                 {
1031                     return name.Name;
1032                 }
1033             }
1034
1035             return String.Empty;
1036         }
1037
1038         /// <summary>
1039         /// 画像などのファイルへの内部リンクの置き換えを行う。
1040         /// </summary>
1041         /// <param name="link">内部リンク。</param>
1042         /// <returns>置き換え後のリンク文字列、置き換えを行わない場合<c>null</c>。</returns>
1043         private string ReplaceFileLink(MediaWikiPage.Link link)
1044         {
1045             // 名前空間だけ翻訳先言語の書式に変換
1046             IList<string> names;
1047             if (!this.To.Namespaces.TryGetValue(this.To.FileNamespace, out names))
1048             {
1049                 // 翻訳先言語に相当する名前空間が無い場合、何もしない
1050                 return null;
1051             }
1052
1053             // 記事名の名前空間部分を置き換えて返す
1054             link.Title = names[0] + link.Title.Substring(link.Title.IndexOf(':'));
1055             return link.Text;
1056         }
1057
1058         #endregion
1059     }
1060 }