1 // ================================================================================================
3 // Wikipedia用の翻訳支援処理実装クラスソース</summary>
5 // <copyright file="MediaWikiTranslator.cs" company="honeplusのメモ帳">
6 // Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
9 // ================================================================================================
11 namespace Honememo.Wptscs.Logics
14 using System.Collections.Generic;
19 using System.Windows.Forms;
20 using Honememo.Models;
21 using Honememo.Parsers;
22 using Honememo.Utilities;
23 using Honememo.Wptscs.Models;
24 using Honememo.Wptscs.Parsers;
25 using Honememo.Wptscs.Properties;
26 using Honememo.Wptscs.Utilities;
27 using Honememo.Wptscs.Websites;
30 /// Wikipedia用の翻訳支援処理実装クラスです。
32 public class MediaWikiTranslator : Translator
39 public MediaWikiTranslator()
41 // このクラス用のロガーと、デフォルトの確認処理としてメッセージダイアログ版を設定
42 this.Logger = new MediaWikiLogger();
43 this.IsContinueAtInterwikiExisted = this.IsContinueAtInterwikiExistedWithDialog;
51 /// 対象記事に言語間リンクが存在する場合の確認処理を表すデリゲート。
53 /// <param name="interwiki">言語間リンク先記事。</param>
54 /// <returns>処理を続行する場合<c>true</c>。</returns>
55 public delegate bool IsContinueAtInterwikiExistedDelegate(string interwiki);
64 public new MediaWiki From
68 return base.From as MediaWiki;
80 public new MediaWiki To
84 return base.To as MediaWiki;
94 /// 対象記事に言語間リンクが存在する場合の確認処理。
96 /// <remarks>確認を行わない場合<c>null</c>。</remarks>
97 public IsContinueAtInterwikiExistedDelegate IsContinueAtInterwikiExisted
109 /// ※継承クラスでは、この関数に処理を実装すること
111 /// <param name="name">記事名。</param>
112 /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Translator.Logger"/>に出力される。</exception>
113 protected override void RunBody(string name)
116 MediaWikiPage article = this.GetTargetPage(name);
119 throw new ApplicationException("article is not found");
122 // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
123 // ※ 言語間リンク取得中は、処理状態を解析中に変更
124 string interWiki = null;
125 this.ChangeStatusInExecuting(
126 () => interWiki = article.GetInterlanguage(this.To.Language.Code),
127 Resources.StatusParsing);
128 if (!String.IsNullOrEmpty(interWiki))
130 // 確認処理の最中は処理時間をカウントしない(ダイアログ等を想定するため)
131 this.Stopwatch.Stop();
132 if (this.IsContinueAtInterwikiExisted != null && !this.IsContinueAtInterwikiExisted(interWiki))
134 throw new ApplicationException("user canceled");
137 this.Stopwatch.Start();
138 this.Logger.AddResponse(Resources.LogMessageTargetArticleHadInterWiki, interWiki);
142 this.Text += this.CreateOpening(article.Title);
144 // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
145 this.Logger.AddSeparator();
146 this.Logger.AddResponse(Resources.LogMessageStartParseAndReplace, interWiki);
147 this.ChangeStatusInExecuting(
148 () => this.Text += this.ReplaceElement(new MediaWikiParser(this.From).Parse(article.Text), article.Title).ToString(),
149 Resources.StatusParsing);
151 // 記事の末尾に新しい言語間リンクと、コメントを追記
152 this.Text += this.CreateEnding(article);
154 // ダウンロードされるテキストがLFなので、最後にクライアント環境に合わせた改行コードに変換
155 // ※ダウンロード時に変換するような仕組みが見つかれば、そちらを使う
156 // その場合、上のように\nをべたに吐いている部分を修正する
157 this.Text = this.Text.Replace("\n", Environment.NewLine);
162 #region 他のクラスの処理をこのクラスにあわせて拡張したメソッド
165 /// ログ出力によるエラー処理を含んだページ取得処理。
167 /// <param name="title">ページタイトル。</param>
168 /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
169 /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
170 /// <exception cref="ApplicationException"><see cref="Translator.CancellationPending"/>が<c>true</c>の場合。</exception>
172 /// 本メソッドは、大きく3パターンの動作を行う。
173 /// <list type="number">
174 /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
175 /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
176 /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
177 /// or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
179 /// また、実行中は処理状態をサーバー接続中に更新する。
180 /// 実行前後には終了要求のチェックも行う。
182 protected bool TryGetPage(string title, out MediaWikiPage page)
184 // & 等の特殊文字をデコードして、親クラスのメソッドを呼び出し
186 bool success = base.TryGetPage(WebUtility.HtmlDecode(title), out p);
187 page = p as MediaWikiPage;
193 #region 冒頭/末尾ブロックの生成メソッド
196 /// 変換後記事冒頭用の「'''日本語記事名'''([[英語|英]]: '''{{Lang|en|英語記事名}}''')」みたいなのを作成する。
198 /// <param name="title">翻訳支援対象の記事名。</param>
199 /// <returns>冒頭部のテキスト。</returns>
200 protected virtual string CreateOpening(string title)
202 string langPart = String.Empty;
203 MediaWikiLink langLink = this.GetLanguageLink(this.From, this.To.Language.Code);
204 if (langLink != null)
206 langPart = langLink.ToString() + ": ";
209 string langBody = this.To.FormatLang(this.From.Language.Code, title);
210 if (String.IsNullOrEmpty(langBody))
215 StringBuilder b = new StringBuilder("'''xxx'''");
216 b.Append(this.To.Language.FormatBracket(langPart + "'''" + langBody + "'''"));
222 /// 変換後記事末尾用の新しい言語間リンクとコメントを作成する。
224 /// <param name="page">翻訳支援対象の記事。</param>
225 /// <returns>末尾部のテキスト。</returns>
226 protected virtual string CreateEnding(MediaWikiPage page)
228 MediaWikiLink link = new MediaWikiLink();
229 link.Title = page.Title;
230 link.Interwiki = this.From.Language.Code;
231 return "\n\n" + link.ToString() + "\n" + String.Format(
232 Resources.ArticleFooter,
233 FormUtils.ApplicationName(),
234 this.From.Language.Code,
236 page.Timestamp.HasValue ? page.Timestamp.Value.ToString("U") : String.Empty) + "\n";
246 /// <param name="element">ページ要素。</param>
247 /// <param name="parent">サブページ用の親記事タイトル。</param>
248 /// <returns>変換後のページ要素。</returns>
249 protected virtual IElement ReplaceElement(IElement element, string parent)
252 this.ThrowExceptionIfCanceled();
254 // 要素の型に応じて、必要な置き換えを行う
255 if (element is MediaWikiTemplate)
258 return this.ReplaceTemplate((MediaWikiTemplate)element, parent);
260 else if (element is MediaWikiLink)
263 return this.ReplaceLink((MediaWikiLink)element, parent);
265 else if (element is MediaWikiHeading)
268 return this.ReplaceHeading((MediaWikiHeading)element, parent);
270 else if (element is MediaWikiVariable)
273 return this.ReplaceVariable((MediaWikiVariable)element, parent);
275 else if (element is ListElement)
278 return this.ReplaceListElement((ListElement)element, parent);
281 // それ以外は、特に何もせず元の値を返す
286 /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する。
288 /// <param name="link">変換元リンク。</param>
289 /// <param name="parent">サブページ用の親記事タイトル。</param>
290 /// <returns>変換済みリンク。</returns>
291 protected virtual IElement ReplaceLink(MediaWikiLink link, string parent)
293 // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
294 if (!this.IsSectionLink(link, parent))
297 MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
302 link.Title = parent + link.Title;
304 else if (!String.IsNullOrEmpty(link.Interwiki))
306 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
307 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
308 return this.ReplaceLinkInterwiki(link);
310 else if (article.IsFile())
312 // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
313 return this.ReplaceLinkFile(link, parent);
315 else if (article.IsCategory() && !link.IsColon)
317 // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
319 return this.ReplaceLinkCategory(link);
321 else if (StringUtils.DefaultString(link.Title).StartsWith("../"))
323 // ..形式のサブページが処理できない既知の不具合への対応、警告メッセージを出す
324 // ※ 2012年2月現在、..形式のサブページはIsSubpageも立たない
325 this.Logger.AddSource(link);
326 this.Logger.AddResponse(Resources.LogMessageErrorPageName, link.Title);
330 // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
331 string interWiki = this.GetInterlanguage(link);
332 if (interWiki == null)
334 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
336 else if (interWiki == String.Empty)
338 // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
339 if (!String.IsNullOrEmpty(this.To.LinkInterwikiFormat))
341 return this.ReplaceLinkLinkInterwiki(link);
344 // 設定が無ければ [[:en:xxx]] みたいな形式に置換
345 link.Title = this.From.Language.Code + ':' + link.Title;
348 else if (link.IsSubpage)
350 // 言語間リンクが存在してサブページの場合、親ページ部分を消す
351 link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
355 // 普通に言語間リンクが存在する場合、記事名を置き換え
356 link.Title = interWiki;
359 if (link.PipeTexts.Count == 0 && interWiki != null)
361 // 表示名が存在しない場合、元の名前を表示名に設定
364 new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
369 // セクション部分([[#関連項目]]とか)を変換
370 if (!String.IsNullOrEmpty(link.Section))
372 link.Section = this.ReplaceLinkSection(link.Section);
375 link.ParsedString = null;
380 /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
382 /// <param name="template">変換元テンプレート。</param>
383 /// <param name="parent">サブページ用の親記事タイトル。</param>
384 /// <returns>変換済みテンプレート。</returns>
385 protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, string parent)
387 // システム変数({{PAGENAME}}とか)の場合は対象外
388 if (this.From.IsMagicWord(template.Title))
393 // テンプレートは通常名前空間が省略されているので補完する
394 string filledTitle = this.FillTemplateName(template, parent);
396 // リンクを辿り、対象記事の言語間リンクを取得
397 string interWiki = this.GetInterlanguage(new MediaWikiTemplate(filledTitle));
398 if (interWiki == null)
400 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
403 else if (interWiki == String.Empty)
405 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
406 // おまけで、元のテンプレートの状態をコメントでつける
407 ListElement list = new ListElement();
408 MediaWikiLink link = new MediaWikiLink();
410 link.Title = this.From.Language.Code + ':' + filledTitle;
412 XmlCommentElement comment = new XmlCommentElement();
413 comment.Raw = ' ' + template.ToString() + ' ';
419 // 言語間リンクが存在する場合、そちらを指すように置換
420 // : より前の部分を削除して出力(: が無いときは-1+1で0から)
421 template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
423 // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
424 template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
425 template.ParsedString = null;
431 /// 指定された見出しに対して、対訳表による変換を行う。
433 /// <param name="heading">見出し。</param>
434 /// <param name="parent">サブページ用の親記事タイトル。</param>
435 /// <returns>変換後の見出し。</returns>
436 protected virtual IElement ReplaceHeading(MediaWikiHeading heading, string parent)
439 this.Logger.AddSource(heading);
442 StringBuilder oldText = new StringBuilder();
443 foreach (IElement e in heading)
445 oldText.Append(e.ToString());
448 string newText = this.GetHeading(oldText.ToString().Trim());
451 // 対訳表による変換が行えた場合、変換先をログ出力し処理終了
453 heading.ParsedString = null;
454 heading.Add(new XmlTextElement(newText));
455 this.Logger.AddDestination(heading);
459 // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
460 return this.ReplaceListElement(heading, parent);
464 /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
466 /// <param name="variable">変換元変数要素。</param>
467 /// <param name="parent">サブページ用の親記事タイトル。</param>
468 /// <returns>変換済み変数要素。</returns>
469 protected virtual IElement ReplaceVariable(MediaWikiVariable variable, string parent)
471 // 変数、これ自体は処理しないが、再帰的に探索
472 string old = variable.Value.ToString();
473 variable.Value = this.ReplaceElement(variable.Value, parent);
474 if (variable.Value.ToString() != old)
476 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
477 variable.ParsedString = null;
484 /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
486 /// <param name="listElement">変換元要素。</param>
487 /// <param name="parent">サブページ用の親記事タイトル。</param>
488 /// <returns>変換済み要素。</returns>
489 protected virtual IElement ReplaceListElement(ListElement listElement, string parent)
491 // 値を格納する要素、これ自体は処理しないが、再帰的に探索
492 for (int i = 0; i < listElement.Count; i++)
494 string old = listElement[i].ToString();
495 listElement[i] = this.ReplaceElement(listElement[i], parent);
496 if (listElement[i].ToString() != old)
498 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
499 listElement.ParsedString = null;
511 /// 対訳表に指定された記事名の情報が登録されているか?
513 /// <param name="title">記事名。</param>
514 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
515 /// <remarks>複数スレッドからのアクセスに対応する。また項目の対訳表が無い場合も動作する。</remarks>
516 protected bool ContainsAtItemTable(string title)
518 if (this.ItemTable == null)
523 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
524 lock (this.ItemTable)
526 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
527 return this.ItemTable.ContainsKey(WebUtility.HtmlDecode(title));
532 /// 対訳表から指定された記事名の情報を取得する。
534 /// <param name="title">記事名。</param>
535 /// <param name="item">翻訳先情報。</param>
536 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
537 /// <remarks>複数スレッドからのアクセスに対応する。</remarks>
538 protected bool TryGetValueAtItemTable(string title, out TranslationDictionary.Item item)
540 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
541 // ※ 同時アクセスを防いでいるだけで、更新処理とは同期していない。
542 // 現状では同じページを同時に解析してしまう可能性があり、効率が良いソースではない。
543 // また、効率を目指すならItemTableではなく記事名ごとに一意なオブジェクトをロックすべき
544 lock (this.ItemTable)
546 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
547 return this.ItemTable.TryGetValue(WebUtility.HtmlDecode(title), out item);
552 /// 対訳表に指定された記事名の情報を登録する。
554 /// <param name="title">記事名。</param>
555 /// <param name="item">翻訳先情報。</param>
556 /// <remarks>複数スレッドからのアクセスに対応する。</remarks>
557 protected void PutValueAtItemTable(string title, TranslationDictionary.Item item)
559 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
560 // ※ 同時アクセスを防いでいるだけで、読込処理とは同期していない。
561 // 現状では同じページを同時に解析してしまう可能性があり、効率が良いソースではない。
562 // また、効率を目指すならItemTableではなく記事名ごとに一意なオブジェクトをロックすべき
563 lock (this.ItemTable)
565 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
566 this.ItemTable[WebUtility.HtmlDecode(title)] = item;
571 /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
573 /// <param name="heading">翻訳元言語での見出し。</param>
574 /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
575 /// <remarks>見出しの対訳表が無い場合も動作する。</remarks>
576 protected string GetHeading(string heading)
578 if (this.HeadingTable == null)
583 return this.HeadingTable.GetWord(heading);
591 /// ロガーに取得結果を出力しつつ、指定された要素の記事の翻訳先言語への言語間リンクを返す。
593 /// <param name="element">内部リンク要素。</param>
594 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
595 /// <remarks>取得処理では対訳表を使用する。また新たな取得結果は対訳表に追加する。</remarks>
596 protected string GetInterlanguage(MediaWikiLink element)
599 this.Logger.AddSource(element);
600 string title = element.Title;
601 TranslationDictionary.Item item;
602 if (this.ItemTable == null)
604 // 対訳表が指定されていない場合は、使わずに言語間リンクを探索して終了
605 return this.GetInterlanguageWithCreateCache(title, out item);
609 if (this.TryGetValueAtItemTable(title, out item))
612 if (!String.IsNullOrWhiteSpace(item.Alias))
614 // リダイレクトがあれば、そのメッセージも表示
615 this.Logger.AddAlias(new MediaWikiLink(item.Alias));
618 if (!String.IsNullOrEmpty(item.Word))
620 this.Logger.AddDestination(new MediaWikiLink(item.Word), true);
625 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound), true);
630 // 対訳表に存在しない場合は、普通に取得し表に記録
631 string interWiki = this.GetInterlanguageWithCreateCache(title, out item);
632 if (interWiki != null)
634 // ページ自体が存在しない場合を除き、結果を対訳表に登録
635 // ※ キャッシュとしては登録すべきかもしれないが、一応"対訳表"であるので
636 this.PutValueAtItemTable(title, item);
643 /// ロガーに取得結果を出力しつつ、指定された記事の翻訳先言語への言語間リンクを返す。
644 /// キャッシュ用の処理結果情報も出力する。
646 /// <param name="title">記事名。</param>
647 /// <param name="item">キャッシュ用の処理結果情報。</param>
648 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
649 private string GetInterlanguageWithCreateCache(string title, out TranslationDictionary.Item item)
652 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
653 MediaWikiPage page = this.GetDestinationPage(title);
654 if (page != null && page.IsRedirect())
656 // リダイレクトの場合、リダイレクトである旨出力し、その先の記事を取得
657 this.Logger.AddAlias(new MediaWikiLink(page.Redirect.Title));
658 item.Alias = page.Redirect.Title;
659 page = this.GetDestinationPage(page.Redirect.Title);
663 string interWiki = null;
666 interWiki = page.GetInterlanguage(this.To.Language.Code);
667 item.Word = interWiki;
668 if (!String.IsNullOrEmpty(interWiki))
670 this.Logger.AddDestination(new MediaWikiLink(interWiki));
674 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound));
684 /// <param name="title">ページタイトル。</param>
685 /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
686 /// <remarks>記事が無い場合、通信エラーなど例外が発生した場合は、エラーログを出力する。</remarks>
687 private MediaWikiPage GetDestinationPage(string title)
690 if (this.TryGetPage(title, out page) && page == null)
692 // 記事が存在しない場合だけ、変換先に「記事無し」を出力
693 // ※ エラー時のログはTryGetPageが自動的に出力
694 this.Logger.AddDestination(new TextElement(Resources.LogMessageLinkArticleNotFound));
702 #region 要素の変換関連その他メソッド
705 /// 同記事内の別のセクションを指すリンク([[#関連項目]]とか[[自記事#関連項目]]とか)か?
707 /// <param name="link">判定する内部リンク。</param>
708 /// <param name="parent">内部リンクがあった記事。</param>
709 /// <returns>セクション部分のみ変換済みリンク。</returns>
710 private bool IsSectionLink(MediaWikiLink link, string parent)
712 // 記事名が指定されていない、または記事名が自分の記事名で
713 // 言語コード等も特に無く、かつセクションが指定されている場合
714 // (記事名もセクションも指定されていない・・・というケースもありえるが、
715 // その場合他に指定できるものも思いつかないので通す)
716 return String.IsNullOrEmpty(link.Title)
717 || (link.Title == parent && String.IsNullOrEmpty(link.Interwiki) && !String.IsNullOrEmpty(link.Section));
721 /// 内部リンクのセクション部分([[#関連項目]]とか)の定型句変換を行う。
723 /// <param name="section">セクション文字列。</param>
724 /// <returns>セクション部分のみ変換済みリンク。</returns>
725 private string ReplaceLinkSection(string section)
727 // セクションが指定されている場合、定型句変換を通す
728 string heading = this.GetHeading(section);
729 return heading != null ? heading : section;
733 /// 言語間リンク指定の内部リンクを解析し、不要であれば削除する。
735 /// <param name="link">変換元言語間リンク。</param>
736 /// <returns>変換済み言語間リンク。</returns>
737 private IElement ReplaceLinkInterwiki(MediaWikiLink link)
739 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
740 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
741 if (!link.IsColon && link.Interwiki == this.To.Language.Code)
743 return new TextElement();
750 /// カテゴリ指定の内部リンクを解析し、変換先言語のカテゴリへのリンクに変換する。
752 /// <param name="link">変換元カテゴリ。</param>
753 /// <returns>変換済みカテゴリ。</returns>
754 private IElement ReplaceLinkCategory(MediaWikiLink link)
756 // リンクを辿り、対象記事の言語間リンクを取得
757 string interWiki = this.GetInterlanguage(link);
758 if (interWiki == null)
760 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
763 else if (interWiki == String.Empty)
765 // 言語間リンクが存在しない場合、コメントで元の文字列を保存した後
766 // [[:en:xxx]]みたいな形式に置換。また | 以降は削除する
767 XmlCommentElement comment = new XmlCommentElement();
768 comment.Raw = ' ' + link.ToString() + ' ';
770 link.Title = this.From.Language.Code + ':' + link.Title;
772 link.PipeTexts.Clear();
773 link.ParsedString = null;
775 ListElement list = new ListElement();
782 // 普通に言語間リンクが存在する場合、記事名を置き換え
783 link.Title = interWiki;
784 link.ParsedString = null;
790 /// ファイル指定の内部リンクを解析し、変換先言語で参照可能なファイルへのリンクに変換する。
792 /// <param name="link">変換元リンク。</param>
793 /// <param name="parent">サブページ用の親記事タイトル。</param>
794 /// <returns>変換済みリンク。</returns>
795 private IElement ReplaceLinkFile(MediaWikiLink link, string parent)
797 // 名前空間を翻訳先言語の書式に変換、またパラメータ部を再帰的に処理
798 link.Title = this.ReplaceLinkNamespace(link.Title, this.To.FileNamespace);
799 link.PipeTexts = this.ReplaceElements(link.PipeTexts, parent);
800 link.ParsedString = null;
805 /// 記事名のうち名前空間部分の変換先言語への変換を行う。
807 /// <param name="title">変換元記事名。</param>
808 /// <param name="id">名前空間のID。</param>
809 /// <returns>変換済み記事名。</returns>
810 private string ReplaceLinkNamespace(string title, int id)
814 if (!this.To.Namespaces.TryGetValue(id, out names))
816 // 翻訳先言語に相当する名前空間が無い場合、何もしない
820 // 記事名の名前空間部分を置き換えて返す
821 return names.FirstOrDefault() + title.Substring(title.IndexOf(':'));
825 /// 内部リンクを他言語版への{{仮リンク}}等に変換する。。
827 /// <param name="link">変換元言語間リンク。</param>
828 /// <returns>変換済み言語間リンク。</returns>
829 private IElement ReplaceLinkLinkInterwiki(MediaWikiLink link)
831 // 仮リンクにはセクションの指定が可能なので、存在する場合付加する
832 // ※ 渡されたlinkをそのまま使わないのは、余計なゴミが含まれる可能性があるため
833 MediaWikiLink title = new MediaWikiLink { Title = link.Title, Section = link.Section };
834 string langTitle = title.GetLinkString();
835 if (!String.IsNullOrEmpty(title.Section))
837 // 変換先言語版のセクションは、セクションの変換を通したものにする
838 title.Section = this.ReplaceLinkSection(title.Section);
841 // 表示名は、設定されていればその値を、なければ変換元言語の記事名を使用
842 string label = langTitle;
843 if (link.PipeTexts.Count > 0)
845 label = link.PipeTexts.Last().ToString();
849 // ※ {{仮リンク}}を想定しているが、やろうと思えば何でもできるのでテキストで処理
850 return new TextElement(this.To.FormatLinkInterwiki(title.GetLinkString(), this.From.Language.Code, langTitle, label));
854 /// 渡された要素リストに対して<see cref="ReplaceElement"/>による変換を行う。
856 /// <param name="elements">変換元要素リスト。</param>
857 /// <param name="parent">サブページ用の親記事タイトル。</param>
858 /// <returns>変換済み要素リスト。</returns>
859 private IList<IElement> ReplaceElements(IList<IElement> elements, string parent)
861 if (elements == null)
866 IList<IElement> result = new List<IElement>();
867 foreach (IElement e in elements)
869 result.Add(this.ReplaceElement(e, parent));
876 /// テンプレート名に必要に応じて名前空間を補完する。
878 /// <param name="template">テンプレート。</param>
879 /// <param name="parent">サブページ用の親記事タイトル。</param>
880 /// <returns>補完済みのテンプレート名。</returns>
881 private string FillTemplateName(MediaWikiTemplate template, string parent)
883 if (template.IsColon || !new MediaWikiPage(this.From, template.Title).IsMain())
885 // 標準名前空間が指定されている(先頭にコロンが無い)
886 // または何かしらの名前空間が指定されている場合、補完不要
887 return template.Title;
889 else if (template.IsSubpage)
891 // サブページの場合、親記事名での補完のみ
892 return parent + template.Title;
895 // 補完する必要がある場合、名前空間のプレフィックス(Template等)を取得
896 string prefix = this.GetTemplatePrefix();
897 if (String.IsNullOrEmpty(prefix))
899 // 名前空間の設定が存在しない場合、何も出来ないため終了
900 return template.Title;
903 // 頭にプレフィックスを付けた記事名で実在するかをチェック
904 string filledTitle = prefix + ":" + template.Title;
906 // 既に対訳表にプレフィックス付きの記事名が確認されているか?
907 if (this.ContainsAtItemTable(filledTitle))
909 // 記事が存在する場合、プレフィックスをつけた名前を使用
913 // 未確認の場合、実際に頭にプレフィックスを付けた記事名でアクセスし、存在するかをチェック
914 // TODO: GetInterWikiの方とあわせ、テンプレートでは2度GetPageが呼ばれている。可能であれば共通化する
915 MediaWikiPage page = null;
918 // 記事が存在する場合、プレフィックスをつけた名前を使用
919 page = this.From.GetPage(WebUtility.HtmlDecode(filledTitle)) as MediaWikiPage;
922 catch (FileNotFoundException)
924 // 記事が存在しない場合、元のページ名を使用
925 return template.Title;
930 if (!Settings.Default.IgnoreError)
932 // エラーを無視しない場合、ここで翻訳支援処理を中断する
933 throw new ApplicationException(e.Message, e);
936 // 続行する場合は、とりあえずプレフィックスをつけた名前で処理
937 this.Logger.AddMessage(Resources.LogMessageTemplateNameUnidentified, template.Title, prefix, e.Message);
943 /// テンプレート名前空間のプレフィックスを取得。
945 /// <returns>プレフィックス。取得できない場合<c>null</c></returns>
946 private string GetTemplatePrefix()
948 ISet<string> prefixes = this.From.Namespaces[this.From.TemplateNamespace];
949 if (prefixes != null)
951 return prefixes.FirstOrDefault();
964 /// <param name="title">翻訳支援対象の記事名。</param>
965 /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
966 private MediaWikiPage GetTargetPage(string title)
968 // 指定された記事をWikipediaから取得、リダイレクトの場合その先まで探索
969 // ※ この処理ではキャッシュは使用しない。
970 // ※ 万が一相互にリダイレクトしていると無限ループとなるが、特に判定はしない。
971 // ユーザーが画面上から止めることを期待。
972 this.Logger.AddMessage(Resources.LogMessageGetTargetArticle, this.From.Location, title);
974 for (string s = title; this.TryGetPage(s, out page); s = page.Redirect.Title)
978 // 記事が存在しない場合、メッセージを出力して終了
979 this.Logger.AddResponse(Resources.LogMessageTargetArticleNotFound);
982 else if (!page.IsRedirect())
988 // リダイレクトであれば、さらにその先の記事を取得
989 this.Logger.AddResponse(Resources.LogMessageRedirect
990 + " " + new MediaWikiLink(page.Redirect.Title).ToString());
997 /// 指定した言語での言語名称を [[言語名称|略称]]の内部リンクで取得。
999 /// <param name="site">サイト。</param>
1000 /// <param name="code">言語のコード。</param>
1001 /// <returns>[[言語名称|略称]]の内部リンク。登録されていない場合<c>null</c>。</returns>
1002 private MediaWikiLink GetLanguageLink(Website site, string code)
1004 if (!site.Language.Names.ContainsKey(code))
1009 Language.LanguageName name = site.Language.Names[code];
1010 MediaWikiLink link = new MediaWikiLink(name.Name);
1011 if (!String.IsNullOrEmpty(name.ShortName))
1013 link.PipeTexts.Add(new TextElement(name.ShortName));
1020 /// 対象記事に言語間リンクが存在する場合にメッセージダイアログでユーザーに確認する処理。
1022 /// <param name="interwiki">言語間リンク先記事。</param>
1023 /// <returns>処理を続行する場合<c>true</c>。</returns>
1024 private bool IsContinueAtInterwikiExistedWithDialog(string interwiki)
1027 if (MessageBox.Show(
1028 String.Format(Resources.QuestionMessageArticleExisted, interwiki),
1029 Resources.QuestionTitle,
1030 MessageBoxButtons.YesNo,
1031 MessageBoxIcon.Question)
1034 // 中断の場合、同じメッセージをログにも表示
1035 this.Logger.AddSeparator();
1036 this.Logger.AddMessage(Resources.QuestionMessageArticleExisted, interwiki);