OSDN Git Service

Wikipedia翻訳支援ツール Ver1.10時点のソース
[wptscs/wpts.git] / Wptscs / Logics / MediaWikiTranslator.cs
1 // ================================================================================================
2 // <summary>
3 //      Wikipedia用の翻訳支援処理実装クラスソース</summary>
4 //
5 // <copyright file="MediaWikiTranslator.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2012 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.Linq;
17     using System.Net;
18     using System.Text;
19     using System.Windows.Forms;
20     using Honememo.Parsers;
21     using Honememo.Utilities;
22     using Honememo.Wptscs.Models;
23     using Honememo.Wptscs.Parsers;
24     using Honememo.Wptscs.Properties;
25     using Honememo.Wptscs.Utilities;
26     using Honememo.Wptscs.Websites;
27
28     /// <summary>
29     /// Wikipedia用の翻訳支援処理実装クラスです。
30     /// </summary>
31     public class MediaWikiTranslator : Translator
32     {
33         #region プロパティ
34
35         /// <summary>
36         /// 翻訳元言語のサイト。
37         /// </summary>
38         public new MediaWiki From
39         {
40             get
41             {
42                 return base.From as MediaWiki;
43             }
44
45             set
46             {
47                 base.From = value;
48             }
49         }
50
51         /// <summary>
52         /// 翻訳先言語のサイト。
53         /// </summary>
54         public new MediaWiki To
55         {
56             get
57             {
58                 return base.To as MediaWiki;
59             }
60
61             set
62             {
63                 base.To = value;
64             }
65         }
66         
67         #endregion
68
69         #region メイン処理メソッド
70
71         /// <summary>
72         /// 翻訳支援処理実行部の本体。
73         /// ※継承クラスでは、この関数に処理を実装すること
74         /// </summary>
75         /// <param name="name">記事名。</param>
76         /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Translator.Log"/>に出力される。</exception>
77         protected override void RunBody(string name)
78         {
79             // 対象記事を取得
80             MediaWikiPage article = this.GetTargetPage(name);
81             if (article == null)
82             {
83                 throw new ApplicationException("article is not found");
84             }
85
86             // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
87             // ※ 言語間リンク取得中は、処理状態を解析中に変更
88             string interWiki = null;
89             this.ChangeStatusInExecuting(
90                 () => interWiki = article.GetInterWiki(this.To.Language.Code),
91                 Resources.StatusParsing);
92             if (!String.IsNullOrEmpty(interWiki))
93             {
94                 // ※ 確認ダイアログの表示中は処理時間をカウントしない
95                 this.Stopwatch.Stop();
96                 if (MessageBox.Show(
97                         String.Format(Resources.QuestionMessageArticleExisted, interWiki),
98                         Resources.QuestionTitle,
99                         MessageBoxButtons.YesNo,
100                         MessageBoxIcon.Question)
101                    == DialogResult.No)
102                 {
103                     this.LogLine(ENTER + String.Format(Resources.QuestionMessageArticleExisted, interWiki));
104                     throw new ApplicationException("user canceled");
105                 }
106
107                 // OKが選択された場合、処理続行
108                 this.Stopwatch.Start();
109                 this.LogLine(Resources.RightArrow + " " + String.Format(Resources.LogMessageTargetArticleHadInterWiki, interWiki));
110             }
111
112             // 冒頭部を作成
113             this.Text += this.CreateOpening(article.Title);
114
115             // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
116             this.LogLine(ENTER + Resources.RightArrow + " " + String.Format(Resources.LogMessageStartParseAndReplace, interWiki));
117             this.ChangeStatusInExecuting(
118                 () => this.Text += this.ReplaceElement(new MediaWikiParser(this.From).Parse(article.Text), article.Title).ToString(),
119                 Resources.StatusParsing);
120
121             // 記事の末尾に新しい言語間リンクと、コメントを追記
122             this.Text += this.CreateEnding(article);
123
124             // ダウンロードされるテキストがLFなので、最後に全てCRLFに変換
125             // ※ダウンロード時にCRLFにするような仕組みが見つかれば、そちらを使う
126             //   その場合、上のように\nをべたに吐いている部分を修正する
127             this.Text = this.Text.Replace("\n", ENTER);
128         }
129
130         #endregion
131
132         #region 他のクラスの処理をこのクラスにあわせて拡張したメソッド
133
134         /// <summary>
135         /// ログメッセージを出力しつつページを取得。
136         /// </summary>
137         /// <param name="title">ページタイトル。</param>
138         /// <param name="notFoundMsg">取得できない場合に出力するメッセージ。</param>
139         /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
140         /// <remarks>通信エラーなど例外が発生した場合は、別途エラーログを出力する。</remarks>
141         protected new MediaWikiPage GetPage(string title, string notFoundMsg)
142         {
143             // &amp; &nbsp; 等の特殊文字をデコードして、親クラスのメソッドを呼び出し
144             return base.GetPage(WebUtility.HtmlDecode(title), notFoundMsg) as MediaWikiPage;
145         }
146
147         #endregion
148
149         #region 冒頭/末尾ブロックの生成メソッド
150
151         /// <summary>
152         /// 変換後記事冒頭用の「'''日本語記事名'''([[英語|英]]: '''英語記事名''')」みたいなのを作成する。
153         /// </summary>
154         /// <param name="title">翻訳支援対象の記事名。</param>
155         /// <returns>冒頭部のテキスト。</returns>
156         protected virtual string CreateOpening(string title)
157         {
158             StringBuilder b = new StringBuilder("'''xxx'''");
159             string langPart = String.Empty;
160             string langTitle = this.GetFullName(this.From, this.To.Language.Code);
161             if (!String.IsNullOrEmpty(langTitle))
162             {
163                 langPart = new MediaWikiLink(langTitle).ToString() + ": ";
164             }
165
166             b.Append(this.To.Language.FormatBracket(langPart + "'''" + title + "'''"));
167             b.Append("\n\n");
168             return b.ToString();
169         }
170
171         /// <summary>
172         /// 変換後記事末尾用の新しい言語間リンクとコメントを作成する。
173         /// </summary>
174         /// <param name="page">翻訳支援対象の記事。</param>
175         /// <returns>末尾部のテキスト。</returns>
176         protected virtual string CreateEnding(MediaWikiPage page)
177         {
178             MediaWikiLink link = new MediaWikiLink();
179             link.Title = page.Title;
180             link.Code = this.From.Language.Code;
181             return "\n\n" + link.ToString() + "\n" + String.Format(
182                 Resources.ArticleFooter,
183                 FormUtils.ApplicationName(),
184                 this.From.Language.Code,
185                 page.Title,
186                 page.Timestamp.HasValue ? page.Timestamp.Value.ToString("U") : String.Empty) + "\n";
187         }
188
189         #endregion
190
191         #region 言語間リンクの取得メソッド
192
193         /// <summary>
194         /// 指定された記事を取得し、言語間リンクを確認、返す(テンプレート以外)。
195         /// </summary>
196         /// <param name="name">記事名。</param>
197         /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
198         protected string GetInterWiki(string name)
199         {
200             return this.GetInterWiki(name, false);
201         }
202
203         /// <summary>
204         /// 指定された記事を取得し、言語間リンクを確認、返す。
205         /// </summary>
206         /// <param name="title">記事名。</param>
207         /// <param name="template"><c>true</c> テンプレート。</param>
208         /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
209         protected string GetInterWiki(string title, bool template)
210         {
211             MediaWikiLink element;
212             if (template)
213             {
214                 element = new MediaWikiTemplate(title);
215             }
216             else
217             {
218                 element = new MediaWikiLink(title);
219             }
220
221             Log += element.ToString() + " " + Resources.RightArrow + " ";
222             string interWiki = this.GetInterWikiUseTable(title, this.To.Language.Code);
223
224             // 改行が出力されていない場合(正常時)、改行
225             if (!Log.EndsWith(ENTER))
226             {
227                 Log += ENTER;
228             }
229
230             return interWiki;
231         }
232
233         /// <summary>
234         /// ログメッセージを出力しつつ、指定された記事の指定された言語コードへの言語間リンクを返す。
235         /// </summary>
236         /// <param name="title">記事名。</param>
237         /// <param name="code">言語コード。</param>
238         /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
239         /// <remarks>対訳表が指定されている場合、その内容を使用する。また取得結果を対訳表に追加する。</remarks>
240         protected string GetInterWikiUseTable(string title, string code)
241         {
242             if (this.ItemTable == null)
243             {
244                 // 対訳表が指定されていない場合は、普通に記事を取得
245                 return this.GetInterWiki(title, code);
246             }
247
248             string interWiki = null;
249             lock (this.ItemTable)
250             {
251                 // 対訳表を使用して言語間リンクを確認
252                 // ※ 対訳表へのキーとしてはHTMLデコードした記事名を使用する
253                 TranslationDictionary.Item item;
254                 if (this.ItemTable.TryGetValue(WebUtility.HtmlDecode(title), out item))
255                 {
256                     // 対訳表に存在する場合はその値を使用
257                     // リダイレクトがあれば、そのメッセージも表示
258                     if (!String.IsNullOrWhiteSpace(item.Alias))
259                     {
260                         this.Log += Resources.LogMessageRedirect + " "
261                             + new MediaWikiLink(item.Alias).ToString() + " " + Resources.RightArrow + " ";
262                     }
263
264                     if (!String.IsNullOrEmpty(item.Word))
265                     {
266                         interWiki = item.Word;
267                         Log += new MediaWikiLink(interWiki).ToString();
268                     }
269                     else
270                     {
271                         interWiki = String.Empty;
272                         Log += Resources.LogMessageInterWikiNotFound;
273                     }
274
275                     // ログ上に対訳表を使用した旨通知
276                     Log += Resources.LogMessageNoteTranslation;
277                     return interWiki;
278                 }
279
280                 // 対訳表に存在しない場合は、普通に取得し表に記録
281                 // ※ nullも存在しないことの記録として格納
282                 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
283                 MediaWikiPage page = this.GetPage(title, Resources.LogMessageLinkArticleNotFound);
284
285                 // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
286                 if (page != null && page.IsRedirect())
287                 {
288                     item.Alias = page.Redirect.Title;
289                     this.Log += Resources.LogMessageRedirect + " "
290                         + new MediaWikiLink(page.Redirect.Title).ToString() + " " + Resources.RightArrow + " ";
291                     page = this.GetPage(page.Redirect.Title, Resources.LogMessageLinkArticleNotFound);
292                 }
293
294                 // 記事があればその言語間リンクを取得
295                 if (page != null)
296                 {
297                     interWiki = page.GetInterWiki(this.To.Language.Code);
298                     if (!String.IsNullOrEmpty(interWiki))
299                     {
300                         Log += new MediaWikiLink(interWiki).ToString();
301                     }
302                     else
303                     {
304                         Log += Resources.LogMessageInterWikiNotFound;
305                     }
306
307                     item.Word = interWiki;
308                     this.ItemTable[WebUtility.HtmlDecode(title)] = item;
309                 }
310             }
311
312             return interWiki;
313         }
314
315         /// <summary>
316         /// ログメッセージを出力しつつ、指定された記事の指定された言語コードへの言語間リンクを返す。
317         /// </summary>
318         /// <param name="title">記事名。</param>
319         /// <param name="code">言語コード。</param>
320         /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
321         protected string GetInterWiki(string title, string code)
322         {
323             MediaWikiPage page = this.GetPage(title, Resources.LogMessageLinkArticleNotFound);
324
325             // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
326             if (page != null && page.IsRedirect())
327             {
328                 this.Log += Resources.LogMessageRedirect + " "
329                     + new MediaWikiLink(page.Redirect.Title).ToString() + " " + Resources.RightArrow + " ";
330                 page = this.GetPage(page.Redirect.Title, Resources.LogMessageLinkArticleNotFound);
331             }
332
333             // 記事があればその言語間リンクを取得
334             string interWiki = null;
335             if (page != null)
336             {
337                 interWiki = page.GetInterWiki(this.To.Language.Code);
338                 if (!String.IsNullOrEmpty(interWiki))
339                 {
340                     Log += new MediaWikiLink(interWiki).ToString();
341                 }
342                 else
343                 {
344                     Log += Resources.LogMessageInterWikiNotFound;
345                 }
346             }
347
348             return interWiki;
349         }
350
351         #endregion
352
353         #region 要素の変換メソッド
354
355         /// <summary>
356         /// 渡されたページ要素の変換を行う。
357         /// </summary>
358         /// <param name="element">ページ要素。</param>
359         /// <param name="parent">サブページ用の親記事タイトル。</param>
360         /// <returns>変換後のページ要素。</returns>
361         protected virtual IElement ReplaceElement(IElement element, string parent)
362         {
363             // ユーザーからの中止要求をチェック
364             this.ThrowExceptionIfCanceled();
365
366             // 要素の型に応じて、必要な置き換えを行う
367             if (element is MediaWikiTemplate)
368             {
369                 // テンプレート
370                 return this.ReplaceTemplate((MediaWikiTemplate)element, parent);
371             }
372             else if (element is MediaWikiLink)
373             {
374                 // 内部リンク
375                 return this.ReplaceLink((MediaWikiLink)element, parent);
376             }
377             else if (element is MediaWikiHeading)
378             {
379                 // 見出し
380                 return this.ReplaceHeading((MediaWikiHeading)element, parent);
381             }
382             else if (element is MediaWikiVariable)
383             {
384                 // 変数
385                 return this.ReplaceVariable((MediaWikiVariable)element, parent);
386             }
387             else if (element is ListElement)
388             {
389                 // 値を格納する要素
390                 return this.ReplaceListElement((ListElement)element, parent);
391             }
392
393             // それ以外は、特に何もせず元の値を返す
394             return element;
395         }
396
397         /// <summary>
398         /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する。
399         /// </summary>
400         /// <param name="link">変換元リンク。</param>
401         /// <param name="parent">サブページ用の親記事タイトル。</param>
402         /// <returns>変換済みリンク。</returns>
403         protected virtual IElement ReplaceLink(MediaWikiLink link, string parent)
404         {
405             // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
406             if (!this.IsSectionLink(link, parent))
407             {
408                 // 記事名の種類に応じて処理を実施
409                 MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
410
411                 if (link.IsSubpage)
412                 {
413                     // サブページの場合、記事名を補完
414                     link.Title = parent + link.Title;
415                 }
416                 else if (!String.IsNullOrEmpty(link.Code))
417                 {
418                     // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
419                     // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
420                     return this.ReplaceLinkInterwiki(link);
421                 }
422                 else if (article.IsFile())
423                 {
424                     // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
425                     return this.ReplaceLinkFile(link, parent);
426                 }
427                 else if (article.IsCategory() && !link.IsColon)
428                 {
429                     // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
430                     // カテゴリ用の変換を実施
431                     return this.ReplaceLinkCategory(link);
432                 }
433
434                 // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
435                 string interWiki = this.GetInterWiki(link.Title);
436                 if (interWiki == null)
437                 {
438                     // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
439                 }
440                 else if (interWiki == String.Empty)
441                 {
442                     // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
443                     if (!String.IsNullOrEmpty(this.To.LinkInterwikiFormat))
444                     {
445                         return this.ReplaceLinkLinkInterwiki(link);
446                     }
447
448                     // 設定が無ければ [[:en:xxx]] みたいな形式に置換
449                     link.Title = this.From.Language.Code + ':' + link.Title;
450                     link.IsColon = true;
451                 }
452                 else if (link.IsSubpage)
453                 {
454                     // 言語間リンクが存在してサブページの場合、親ページ部分を消す
455                     link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
456                 }
457                 else
458                 {
459                     // 普通に言語間リンクが存在する場合、記事名を置き換え
460                     link.Title = interWiki;
461                 }
462
463                 if (link.PipeTexts.Count == 0 && interWiki != null)
464                 {
465                     // 表示名が存在しない場合、元の名前を表示名に設定
466                     // 元の名前にはあればセクションも含む
467                     link.PipeTexts.Add(
468                         new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
469                             .GetLinkString()));
470                 }
471             }
472
473             // セクション部分([[#関連項目]]とか)を変換
474             if (!String.IsNullOrEmpty(link.Section))
475             {
476                 link.Section = this.ReplaceLinkSection(link.Section);
477             }
478
479             link.ParsedString = null;
480             return link;
481         }
482
483         /// <summary>
484         /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
485         /// </summary>
486         /// <param name="template">変換元テンプレート。</param>
487         /// <param name="parent">サブページ用の親記事タイトル。</param>
488         /// <returns>変換済みテンプレート。</returns>
489         protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, string parent)
490         {
491             // システム変数({{PAGENAME}}とか)の場合は対象外
492             if (this.From.IsMagicWord(template.Title))
493             {
494                 return template;
495             }
496
497             // テンプレートは通常名前空間が省略されているので補完する
498             string filledTitle = this.FillTemplateName(template, parent);
499
500             // リンクを辿り、対象記事の言語間リンクを取得
501             string interWiki = this.GetInterWiki(filledTitle, true);
502             if (interWiki == null)
503             {
504                 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
505                 return template;
506             }
507             else if (interWiki == String.Empty)
508             {
509                 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
510                 // おまけで、元のテンプレートの状態をコメントでつける
511                 ListElement list = new ListElement();
512                 MediaWikiLink link = new MediaWikiLink();
513                 link.IsColon = true;
514                 link.Title = this.From.Language.Code + ':' + filledTitle;
515                 list.Add(link);
516                 XmlCommentElement comment = new XmlCommentElement();
517                 comment.Raw = ' ' + template.ToString() + ' ';
518                 list.Add(comment);
519                 return list;
520             }
521             else
522             {
523                 // 言語間リンクが存在する場合、そちらを指すように置換
524                 // : より前の部分を削除して出力(: が無いときは-1+1で0から)
525                 template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
526
527                 // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
528                 template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
529                 template.ParsedString = null;
530                 return template;
531             }
532         }
533
534         /// <summary>
535         /// 指定された見出しに対して、対訳表による変換を行う。
536         /// </summary>
537         /// <param name="heading">見出し。</param>
538         /// <param name="parent">サブページ用の親記事タイトル。</param>
539         /// <returns>変換後の見出し。</returns>
540         protected virtual IElement ReplaceHeading(MediaWikiHeading heading, string parent)
541         {
542             // 定型句変換
543             StringBuilder oldText = new StringBuilder();
544             foreach (IElement e in heading)
545             {
546                 oldText.Append(e.ToString());
547             }
548
549             string oldHeading = heading.ToString();
550             string newText = this.GetHeading(oldText.ToString().Trim());
551             if (newText != null)
552             {
553                 // 対訳表による変換が行えた場合、そこで処理終了
554                 heading.Clear();
555                 heading.ParsedString = null;
556                 heading.Add(new XmlTextElement(newText));
557                 this.LogLine(ENTER + oldHeading + " " + Resources.RightArrow + " " + heading.ToString());
558                 return heading;
559             }
560
561             // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
562             this.LogLine(ENTER + heading.ToString());
563             return this.ReplaceListElement(heading, parent);
564         }
565
566         /// <summary>
567         /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
568         /// </summary>
569         /// <param name="variable">変換元変数要素。</param>
570         /// <param name="parent">サブページ用の親記事タイトル。</param>
571         /// <returns>変換済み変数要素。</returns>
572         protected virtual IElement ReplaceVariable(MediaWikiVariable variable, string parent)
573         {
574             // 変数、これ自体は処理しないが、再帰的に探索
575             string old = variable.Value.ToString();
576             variable.Value = this.ReplaceElement(variable.Value, parent);
577             if (variable.Value.ToString() != old)
578             {
579                 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
580                 variable.ParsedString = null;
581             }
582
583             return variable;
584         }
585
586         /// <summary>
587         /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
588         /// </summary>
589         /// <param name="listElement">変換元要素。</param>
590         /// <param name="parent">サブページ用の親記事タイトル。</param>
591         /// <returns>変換済み要素。</returns>
592         protected virtual IElement ReplaceListElement(ListElement listElement, string parent)
593         {
594             // 値を格納する要素、これ自体は処理しないが、再帰的に探索
595             for (int i = 0; i < listElement.Count; i++)
596             {
597                 string old = listElement[i].ToString();
598                 listElement[i] = this.ReplaceElement(listElement[i], parent);
599                 if (listElement[i].ToString() != old)
600                 {
601                     // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
602                     listElement.ParsedString = null;
603                 }
604             }
605
606             return listElement;
607         }
608
609         #endregion
610
611         #region その他内部処理用メソッド
612
613         /// <summary>
614         /// 翻訳支援対象のページを取得。
615         /// </summary>
616         /// <param name="title">翻訳支援対象の記事名。</param>
617         /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
618         protected MediaWikiPage GetTargetPage(string title)
619         {
620             // 指定された記事のXMLデータをWikipediaから取得
621             this.LogLine(String.Format(Resources.LogMessageGetTargetArticle, this.From.Location, title));
622             MediaWikiPage page = this.GetPage(title, Resources.RightArrow + " " + Resources.LogMessageTargetArticleNotFound);
623
624             // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
625             if (page != null && page.IsRedirect())
626             {
627                 this.LogLine(Resources.RightArrow + " " + Resources.LogMessageRedirect
628                     + " " + new MediaWikiLink(page.Redirect.Title).ToString());
629                 page = this.GetPage(
630                     page.Redirect.Title,
631                     Resources.RightArrow + " " + Resources.LogMessageTargetArticleNotFound);
632             }
633
634             return page;
635         }
636
637         /// <summary>
638         /// 同記事内の別のセクションを指すリンク([[#関連項目]]とか[[自記事#関連項目]]とか)か?
639         /// </summary>
640         /// <param name="link">判定する内部リンク。</param>
641         /// <param name="parent">内部リンクがあった記事。</param>
642         /// <returns>セクション部分のみ変換済みリンク。</returns>
643         private bool IsSectionLink(MediaWikiLink link, string parent)
644         {
645             // 記事名が指定されていない、または記事名が自分の記事名で
646             // 言語コード等も特に無く、かつセクションが指定されている場合
647             // (記事名もセクションも指定されていない・・・というケースもありえるが、
648             //   その場合他に指定できるものも思いつかないので通す)
649             return String.IsNullOrEmpty(link.Title)
650                 || (link.Title == parent && String.IsNullOrEmpty(link.Code) && !String.IsNullOrEmpty(link.Section));
651         }
652
653         /// <summary>
654         /// 内部リンクのセクション部分([[#関連項目]]とか)の定型句変換を行う。
655         /// </summary>
656         /// <param name="section">セクション文字列。</param>
657         /// <returns>セクション部分のみ変換済みリンク。</returns>
658         private string ReplaceLinkSection(string section)
659         {
660             // セクションが指定されている場合、定型句変換を通す
661             string heading = this.GetHeading(section);
662             return heading != null ? heading : section;
663         }
664
665         /// <summary>
666         /// 言語間リンク指定の内部リンクを解析し、不要であれば削除する。
667         /// </summary>
668         /// <param name="link">変換元言語間リンク。</param>
669         /// <returns>変換済み言語間リンク。</returns>
670         private IElement ReplaceLinkInterwiki(MediaWikiLink link)
671         {
672             // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
673             // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
674             if (!link.IsColon && link.Code == this.To.Language.Code)
675             {
676                 return new TextElement();
677             }
678
679             return link;
680         }
681
682         /// <summary>
683         /// カテゴリ指定の内部リンクを解析し、変換先言語のカテゴリへのリンクに変換する。
684         /// </summary>
685         /// <param name="link">変換元カテゴリ。</param>
686         /// <returns>変換済みカテゴリ。</returns>
687         private IElement ReplaceLinkCategory(MediaWikiLink link)
688         {
689             // リンクを辿り、対象記事の言語間リンクを取得
690             string interWiki = this.GetInterWiki(link.Title);
691             if (interWiki == null)
692             {
693                 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
694                 return link;
695             }
696             else if (interWiki == String.Empty)
697             {
698                 // 言語間リンクが存在しない場合、コメントで元の文字列を保存した後
699                 // [[:en:xxx]]みたいな形式に置換。また | 以降は削除する
700                 XmlCommentElement comment = new XmlCommentElement();
701                 comment.Raw = ' ' + link.ToString() + ' ';
702
703                 link.Title = this.From.Language.Code + ':' + link.Title;
704                 link.IsColon = true;
705                 link.PipeTexts.Clear();
706                 link.ParsedString = null;
707
708                 ListElement list = new ListElement();
709                 list.Add(link);
710                 list.Add(comment);
711                 return list;
712             }
713             else
714             {
715                 // 普通に言語間リンクが存在する場合、記事名を置き換え
716                 link.Title = interWiki;
717                 link.ParsedString = null;
718                 return link;
719             }
720         }
721
722         /// <summary>
723         /// ファイル指定の内部リンクを解析し、変換先言語で参照可能なファイルへのリンクに変換する。
724         /// </summary>
725         /// <param name="link">変換元リンク。</param>
726         /// <param name="parent">サブページ用の親記事タイトル。</param>
727         /// <returns>変換済みリンク。</returns>
728         private IElement ReplaceLinkFile(MediaWikiLink link, string parent)
729         {
730             // 名前空間を翻訳先言語の書式に変換、またパラメータ部を再帰的に処理
731             link.Title = this.ReplaceLinkNamespace(link.Title, this.To.FileNamespace);
732             link.PipeTexts = this.ReplaceElements(link.PipeTexts, parent);
733             link.ParsedString = null;
734             return link;
735         }
736
737         /// <summary>
738         /// 記事名のうち名前空間部分の変換先言語への変換を行う。
739         /// </summary>
740         /// <param name="title">変換元記事名。</param>
741         /// <param name="id">名前空間のID。</param>
742         /// <returns>変換済み記事名。</returns>
743         private string ReplaceLinkNamespace(string title, int id)
744         {
745             // 名前空間だけ翻訳先言語の書式に変換
746             IList<string> names;
747             if (!this.To.Namespaces.TryGetValue(id, out names))
748             {
749                 // 翻訳先言語に相当する名前空間が無い場合、何もしない
750                 return title;
751             }
752
753             // 記事名の名前空間部分を置き換えて返す
754             return names[0] + title.Substring(title.IndexOf(':'));
755         }
756
757         /// <summary>
758         /// 内部リンクを他言語版への{{仮リンク}}等に変換する。。
759         /// </summary>
760         /// <param name="link">変換元言語間リンク。</param>
761         /// <returns>変換済み言語間リンク。</returns>
762         private IElement ReplaceLinkLinkInterwiki(MediaWikiLink link)
763         {
764             // 仮リンクにはセクションの指定が可能なので、存在する場合付加する
765             // ※ 渡されたlinkをそのまま使わないのは、余計なゴミが含まれる可能性があるため
766             MediaWikiLink title = new MediaWikiLink { Title = link.Title, Section = link.Section };
767             string langTitle = title.GetLinkString();
768             if (!String.IsNullOrEmpty(title.Section))
769             {
770                 // 変換先言語版のセクションは、セクションの変換を通したものにする
771                 title.Section = this.ReplaceLinkSection(title.Section);
772             }
773             
774             // 表示名は、設定されていればその値を、なければ変換元言語の記事名を使用
775             string label = langTitle;
776             if (link.PipeTexts.Count > 0)
777             {
778                 label = link.PipeTexts.Last().ToString();
779             }
780
781             // 書式化した文字列を返す
782             // ※ {{仮リンク}}を想定しているが、やろうと思えば何でもできるのでテキストで処理
783             return new TextElement(this.To.FormatLinkInterwiki(title.GetLinkString(), this.From.Language.Code, langTitle, label));
784         }
785
786         /// <summary>
787         /// 渡された要素リストに対して<see cref="ReplaceElement"/>による変換を行う。
788         /// </summary>
789         /// <param name="elements">変換元要素リスト。</param>
790         /// <param name="parent">サブページ用の親記事タイトル。</param>
791         /// <returns>変換済み要素リスト。</returns>
792         private IList<IElement> ReplaceElements(IList<IElement> elements, string parent)
793         {
794             if (elements == null)
795             {
796                 return null;
797             }
798
799             IList<IElement> result = new List<IElement>();
800             foreach (IElement e in elements)
801             {
802                 result.Add(this.ReplaceElement(e, parent));
803             }
804
805             return result;
806         }
807
808         /// <summary>
809         /// テンプレート名に必要に応じて名前空間を補完する。
810         /// </summary>
811         /// <param name="template">テンプレート。</param>
812         /// <param name="parent">サブページ用の親記事タイトル。</param>
813         /// <returns>補完済みのテンプレート名。</returns>
814         private string FillTemplateName(MediaWikiTemplate template, string parent)
815         {
816             if (template.IsColon || !new MediaWikiPage(this.From, template.Title).IsMain())
817             {
818                 // 標準名前空間が指定されている(先頭にコロンが無い)
819                 // または何かしらの名前空間が指定されている場合、補完不要
820                 return template.Title;
821             }
822             else if (template.IsSubpage)
823             {
824                 // サブページの場合、親記事名での補完のみ
825                 return parent + template.Title;
826             }
827
828             // 補完する必要がある場合、名前空間のプレフィックス(Template等)を取得
829             string prefix = this.GetTemplatePrefix();
830             if (String.IsNullOrEmpty(prefix))
831             {
832                 // 名前空間の設定が存在しない場合、何も出来ないため終了
833                 return template.Title;
834             }
835
836             // 頭にプレフィックスを付けた記事名で実在するかをチェック
837             string filledTitle = prefix + ":" + template.Title;
838
839             // 既に対訳表にプレフィックス付きの記事名が確認されているか?
840             // ※ 対訳表へのキーとしてはHTMLデコードした記事名を使用する
841             if (this.ItemTable != null && this.ItemTable.ContainsKey(WebUtility.HtmlDecode(filledTitle)))
842             {
843                 // 記事が存在する場合、プレフィックスをつけた名前を使用
844                 return filledTitle;
845             }
846
847             // 未確認の場合、実際に頭にプレフィックスを付けた記事名でアクセスし、存在するかをチェック
848             // TODO: GetInterWikiの方とあわせ、テンプレートでは2度GetPageが呼ばれている。可能であれば共通化する
849             MediaWikiPage page = null;
850             try
851             {
852                 page = this.From.GetPage(filledTitle) as MediaWikiPage;
853             }
854             catch (WebException e)
855             {
856                 if (e.Status == WebExceptionStatus.ProtocolError
857                     && (e.Response as HttpWebResponse).StatusCode != HttpStatusCode.NotFound)
858                 {
859                     // 記事が取得できない場合も、404でない場合は存在するものとして処理
860                     this.LogLine(String.Format(Resources.LogMessageTemplateNameUnidentified, template.Title, prefix, e.Message));
861                     return filledTitle;
862                 }
863             }
864             catch (Exception e)
865             {
866                 // それ以外のエラー(Webではなくfileでのエラーとか)は存在しないものと扱う
867                 System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.FillTemplateName > " + e.Message);
868             }
869
870             if (page != null)
871             {
872                 // 記事が存在する場合、プレフィックスをつけた名前を使用
873                 return filledTitle;
874             }
875
876             return template.Title;
877         }
878
879         /// <summary>
880         /// テンプレート名前空間のプレフィックスを取得。
881         /// </summary>
882         /// <returns>プレフィックス。取得できない場合<c>null</c></returns>
883         private string GetTemplatePrefix()
884         {
885             IList<string> prefixes = this.From.Namespaces[this.From.TemplateNamespace];
886             if (prefixes != null)
887             {
888                 return prefixes.FirstOrDefault();
889             }
890
891             return null;
892         }
893
894         /// <summary>
895         /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
896         /// </summary>
897         /// <param name="heading">翻訳元言語での見出し。</param>
898         /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
899         private string GetHeading(string heading)
900         {
901             return this.HeadingTable.GetWord(heading);
902         }
903
904         /// <summary>
905         /// 指定した言語での言語名称を ページ名|略称 の形式で取得。
906         /// </summary>
907         /// <param name="site">サイト。</param>
908         /// <param name="code">言語のコード。</param>
909         /// <returns>ページ名|略称形式の言語名称。</returns>
910         private string GetFullName(Website site, string code)
911         {
912             if (site.Language.Names.ContainsKey(code))
913             {
914                 Language.LanguageName name = site.Language.Names[code];
915                 if (!String.IsNullOrEmpty(name.ShortName))
916                 {
917                     return name.Name + "|" + name.ShortName;
918                 }
919                 else
920                 {
921                     return name.Name;
922                 }
923             }
924
925             return String.Empty;
926         }
927
928         #endregion
929     }
930 }