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 MediaWikiLink interlanguage = null;
125 this.ChangeStatusInExecuting(
126 () => interlanguage = article.GetInterlanguage(this.To.Language.Code),
127 Resources.StatusParsing);
128 if (interlanguage != null)
130 // 確認処理の最中は処理時間をカウントしない(ダイアログ等を想定するため)
131 this.Stopwatch.Stop();
132 if (this.IsContinueAtInterwikiExisted != null && !this.IsContinueAtInterwikiExisted(interlanguage.Title))
134 throw new ApplicationException("user canceled");
137 this.Stopwatch.Start();
138 this.Logger.AddResponse(Resources.LogMessageTargetArticleHadInterWiki, interlanguage.Title);
142 this.Text += this.CreateOpening(article.Title);
144 // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
145 this.Logger.AddSeparator();
146 this.Logger.AddResponse(Resources.LogMessageStartParseAndReplace);
147 this.ChangeStatusInExecuting(
148 () => this.Text += this.ReplaceElement(article.Element, article).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, MediaWikiPage 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, MediaWikiPage parent)
293 // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
294 if (!this.IsSectionLink(link, parent.Title))
297 MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
300 if (link.IsSubpage())
302 // サブページ(子)の場合だけ後で記事名を復元するので記録
303 child = link.Title.StartsWith("/");
306 string title = parent.Normalize(link);
307 if (parent.Title.StartsWith(title))
309 // サブページ(親)の場合、変換してもしょうがないのでセクションだけチェックして終了
310 if (!String.IsNullOrEmpty(link.Section))
312 link.Section = this.ReplaceLinkSection(link.Section);
313 link.ParsedString = null;
321 else if (!String.IsNullOrEmpty(link.Interwiki))
323 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
324 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
325 return this.ReplaceLinkInterwiki(link);
327 else if (article.IsFile())
329 // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
330 return this.ReplaceLinkFile(link, parent);
332 else if (article.IsCategory() && !link.IsColon)
334 // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
336 return this.ReplaceLinkCategory(link);
339 // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
340 string interWiki = this.GetInterlanguage(link);
341 if (interWiki == null)
343 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
345 else if (interWiki == String.Empty)
347 // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
348 if (!String.IsNullOrEmpty(this.To.LinkInterwikiFormat))
350 return this.ReplaceLinkLinkInterwiki(link);
353 // 設定が無ければ [[:en:xxx]] みたいな形式に置換
354 link.Title = this.From.Language.Code + ':' + link.Title;
359 // 言語間リンクが存在してサブページ(子)の場合、親ページ部分を消す
360 // TODO: 兄弟や叔父のパターンも対処したい(ややこしいので現状未対応)
361 link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
365 // 普通に言語間リンクが存在する場合、記事名を置き換え
366 link.Title = interWiki;
369 if (link.PipeTexts.Count == 0 && interWiki != null)
371 // 表示名が存在しない場合、元の名前を表示名に設定
374 new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
379 // セクション部分([[#関連項目]]とか)を変換
380 if (!String.IsNullOrEmpty(link.Section))
382 link.Section = this.ReplaceLinkSection(link.Section);
385 link.ParsedString = null;
390 /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
392 /// <param name="template">変換元テンプレート。</param>
393 /// <param name="parent">ページ要素を取得した変換元記事。</param>
394 /// <returns>変換済みテンプレート。</returns>
395 protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, MediaWikiPage parent)
397 // システム変数({{PAGENAME}}とか)の場合は対象外
398 if (this.From.IsMagicWord(template.Title))
403 // テンプレートは通常名前空間が省略されているので補完する
404 string filledTitle = this.FillTemplateName(template, parent);
406 // リンクを辿り、対象記事の言語間リンクを取得
407 string interWiki = this.GetInterlanguage(new MediaWikiTemplate(filledTitle));
408 if (interWiki == null)
410 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
413 else if (interWiki == String.Empty)
415 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
416 // おまけで、元のテンプレートの状態をコメントでつける
417 ListElement list = new ListElement();
418 MediaWikiLink link = new MediaWikiLink();
420 link.Title = this.From.Language.Code + ':' + filledTitle;
422 XmlCommentElement comment = new XmlCommentElement();
423 comment.Raw = ' ' + template.ToString() + ' ';
429 // 言語間リンクが存在する場合、そちらを指すように置換
430 // : より前の部分を削除して出力(: が無いときは-1+1で0から)
431 template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
433 // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
434 template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
435 template.ParsedString = null;
441 /// 指定された見出しに対して、対訳表による変換を行う。
443 /// <param name="heading">見出し。</param>
444 /// <param name="parent">ページ要素を取得した変換元記事。</param>
445 /// <returns>変換後の見出し。</returns>
446 protected virtual IElement ReplaceHeading(MediaWikiHeading heading, MediaWikiPage parent)
449 this.Logger.AddSource(heading);
452 StringBuilder oldText = new StringBuilder();
453 foreach (IElement e in heading)
455 oldText.Append(e.ToString());
458 string newText = this.GetHeading(oldText.ToString().Trim());
461 // 対訳表による変換が行えた場合、変換先をログ出力し処理終了
463 heading.ParsedString = null;
464 heading.Add(new XmlTextElement(newText));
465 this.Logger.AddDestination(heading);
469 // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
470 return this.ReplaceListElement(heading, parent);
474 /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
476 /// <param name="variable">変換元変数要素。</param>
477 /// <param name="parent">ページ要素を取得した変換元記事。</param>
478 /// <returns>変換済み変数要素。</returns>
479 protected virtual IElement ReplaceVariable(MediaWikiVariable variable, MediaWikiPage parent)
481 // 変数、これ自体は処理しないが、再帰的に探索
482 string old = variable.Value.ToString();
483 variable.Value = this.ReplaceElement(variable.Value, parent);
484 if (variable.Value.ToString() != old)
486 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
487 variable.ParsedString = null;
494 /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
496 /// <param name="listElement">変換元要素。</param>
497 /// <param name="parent">ページ要素を取得した変換元記事。</param>
498 /// <returns>変換済み要素。</returns>
499 protected virtual IElement ReplaceListElement(ListElement listElement, MediaWikiPage parent)
501 // 値を格納する要素、これ自体は処理しないが、再帰的に探索
502 for (int i = 0; i < listElement.Count; i++)
504 string old = listElement[i].ToString();
505 listElement[i] = this.ReplaceElement(listElement[i], parent);
506 if (listElement[i].ToString() != old)
508 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
509 listElement.ParsedString = null;
521 /// 対訳表に指定された記事名の情報が登録されているか?
523 /// <param name="title">記事名。</param>
524 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
525 /// <remarks>複数スレッドからのアクセスに対応する。また項目の対訳表が無い場合も動作する。</remarks>
526 protected bool ContainsAtItemTable(string title)
528 if (this.ItemTable == null)
533 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
534 lock (this.ItemTable)
536 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
537 return this.ItemTable.ContainsKey(WebUtility.HtmlDecode(title));
542 /// 対訳表から指定された記事名の情報を取得する。
544 /// <param name="title">記事名。</param>
545 /// <param name="item">翻訳先情報。</param>
546 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
547 /// <remarks>複数スレッドからのアクセスに対応する。</remarks>
548 protected bool TryGetValueAtItemTable(string title, out TranslationDictionary.Item item)
550 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
551 // ※ 同時アクセスを防いでいるだけで、更新処理とは同期していない。
552 // 現状では同じページを同時に解析してしまう可能性があり、効率が良いソースではない。
553 // また、効率を目指すならItemTableではなく記事名ごとに一意なオブジェクトをロックすべき
554 lock (this.ItemTable)
556 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
557 return this.ItemTable.TryGetValue(WebUtility.HtmlDecode(title), out item);
562 /// 対訳表に指定された記事名の情報を登録する。
564 /// <param name="title">記事名。</param>
565 /// <param name="item">翻訳先情報。</param>
566 /// <remarks>複数スレッドからのアクセスに対応する。</remarks>
567 protected void PutValueAtItemTable(string title, TranslationDictionary.Item item)
569 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
570 // ※ 同時アクセスを防いでいるだけで、読込処理とは同期していない。
571 // 現状では同じページを同時に解析してしまう可能性があり、効率が良いソースではない。
572 // また、効率を目指すならItemTableではなく記事名ごとに一意なオブジェクトをロックすべき
573 lock (this.ItemTable)
575 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
576 this.ItemTable[WebUtility.HtmlDecode(title)] = item;
581 /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
583 /// <param name="heading">翻訳元言語での見出し。</param>
584 /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
585 /// <remarks>見出しの対訳表が無い場合も動作する。</remarks>
586 protected string GetHeading(string heading)
588 if (this.HeadingTable == null)
593 return this.HeadingTable.GetWord(heading);
601 /// ロガーに取得結果を出力しつつ、指定された要素の記事の翻訳先言語への言語間リンクを返す。
603 /// <param name="element">内部リンク要素。</param>
604 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
605 /// <remarks>取得処理では対訳表を使用する。また新たな取得結果は対訳表に追加する。</remarks>
606 protected string GetInterlanguage(MediaWikiLink element)
609 this.Logger.AddSource(element);
610 string title = element.Title;
611 TranslationDictionary.Item item;
612 if (this.ItemTable == null)
614 // 対訳表が指定されていない場合は、使わずに言語間リンクを探索して終了
615 return this.GetInterlanguageWithCreateCache(title, out item);
619 if (this.TryGetValueAtItemTable(title, out item))
622 if (!String.IsNullOrWhiteSpace(item.Alias))
624 // リダイレクトがあれば、そのメッセージも表示
625 this.Logger.AddAlias(new MediaWikiLink(item.Alias));
628 if (!String.IsNullOrEmpty(item.Word))
630 this.Logger.AddDestination(new MediaWikiLink(item.Word), true);
635 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound), true);
640 // 対訳表に存在しない場合は、普通に取得し表に記録
641 string interlanguage = this.GetInterlanguageWithCreateCache(title, out item);
642 if (interlanguage != null)
644 // ページ自体が存在しない場合を除き、結果を対訳表に登録
645 // ※ キャッシュとしては登録すべきかもしれないが、一応"対訳表"であるので
646 this.PutValueAtItemTable(title, item);
649 return interlanguage;
653 /// ロガーに取得結果を出力しつつ、指定された記事の翻訳先言語への言語間リンクを返す。
654 /// キャッシュ用の処理結果情報も出力する。
656 /// <param name="title">記事名。</param>
657 /// <param name="item">キャッシュ用の処理結果情報。</param>
658 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
659 private string GetInterlanguageWithCreateCache(string title, out TranslationDictionary.Item item)
662 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
663 MediaWikiPage page = this.GetDestinationPage(title);
664 if (page != null && page.IsRedirect())
666 // リダイレクトの場合、リダイレクトである旨出力し、その先の記事を取得
667 this.Logger.AddAlias(new MediaWikiLink(page.Redirect.Title));
668 item.Alias = page.Redirect.Title;
669 page = this.GetDestinationPage(page.Redirect.Title);
674 // ページ自体が存在しない場合はnull
679 MediaWikiLink interlanguage = page.GetInterlanguage(this.To.Language.Code);
680 if (interlanguage != null)
682 item.Word = interlanguage.Title;
683 this.Logger.AddDestination(interlanguage);
688 item.Word = String.Empty;
689 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound));
698 /// <param name="title">ページタイトル。</param>
699 /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
700 /// <remarks>記事が無い場合、通信エラーなど例外が発生した場合は、エラーログを出力する。</remarks>
701 private MediaWikiPage GetDestinationPage(string title)
704 if (this.TryGetPage(title, out page) && page == null)
706 // 記事が存在しない場合だけ、変換先に「記事無し」を出力
707 // ※ エラー時のログはTryGetPageが自動的に出力
708 this.Logger.AddDestination(new TextElement(Resources.LogMessageLinkArticleNotFound));
716 #region 要素の変換関連その他メソッド
719 /// 同記事内の別のセクションを指すリンク([[#関連項目]]とか[[自記事#関連項目]]とか)か?
721 /// <param name="link">判定する内部リンク。</param>
722 /// <param name="parent">内部リンクがあった記事。</param>
723 /// <returns>セクション部分のみ変換済みリンク。</returns>
724 private bool IsSectionLink(MediaWikiLink link, string parent)
726 // 記事名が指定されていない、または記事名が自分の記事名で
727 // 言語コード等も特に無く、かつセクションが指定されている場合
728 // (記事名もセクションも指定されていない・・・というケースもありえるが、
729 // その場合他に指定できるものも思いつかないので通す)
730 return String.IsNullOrEmpty(link.Title)
731 || (link.Title == parent && String.IsNullOrEmpty(link.Interwiki) && !String.IsNullOrEmpty(link.Section));
735 /// 内部リンクのセクション部分([[#関連項目]]とか)の定型句変換を行う。
737 /// <param name="section">セクション文字列。</param>
738 /// <returns>セクション部分のみ変換済みリンク。</returns>
739 private string ReplaceLinkSection(string section)
741 // セクションが指定されている場合、定型句変換を通す
742 string heading = this.GetHeading(section);
743 return heading != null ? heading : section;
747 /// 言語間リンク指定の内部リンクを解析し、不要であれば削除する。
749 /// <param name="link">変換元言語間リンク。</param>
750 /// <returns>変換済み言語間リンク。</returns>
751 private IElement ReplaceLinkInterwiki(MediaWikiLink link)
753 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
754 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
755 if (!link.IsColon && link.Interwiki == this.To.Language.Code)
757 return new TextElement();
764 /// カテゴリ指定の内部リンクを解析し、変換先言語のカテゴリへのリンクに変換する。
766 /// <param name="link">変換元カテゴリ。</param>
767 /// <returns>変換済みカテゴリ。</returns>
768 private IElement ReplaceLinkCategory(MediaWikiLink link)
770 // リンクを辿り、対象記事の言語間リンクを取得
771 string interWiki = this.GetInterlanguage(link);
772 if (interWiki == null)
774 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
777 else if (interWiki == String.Empty)
779 // 言語間リンクが存在しない場合、コメントで元の文字列を保存した後
780 // [[:en:xxx]]みたいな形式に置換。また | 以降は削除する
781 XmlCommentElement comment = new XmlCommentElement();
782 comment.Raw = ' ' + link.ToString() + ' ';
784 link.Title = this.From.Language.Code + ':' + link.Title;
786 link.PipeTexts.Clear();
787 link.ParsedString = null;
789 ListElement list = new ListElement();
796 // 普通に言語間リンクが存在する場合、記事名を置き換え
797 link.Title = interWiki;
798 link.ParsedString = null;
804 /// ファイル指定の内部リンクを解析し、変換先言語で参照可能なファイルへのリンクに変換する。
806 /// <param name="link">変換元リンク。</param>
807 /// <param name="parent">ページ要素を取得した変換元記事。</param>
808 /// <returns>変換済みリンク。</returns>
809 private IElement ReplaceLinkFile(MediaWikiLink link, MediaWikiPage parent)
811 // 名前空間を翻訳先言語の書式に変換、またパラメータ部を再帰的に処理
812 link.Title = this.ReplaceLinkNamespace(link.Title, this.To.FileNamespace);
813 link.PipeTexts = this.ReplaceElements(link.PipeTexts, parent);
814 link.ParsedString = null;
819 /// 記事名のうち名前空間部分の変換先言語への変換を行う。
821 /// <param name="title">変換元記事名。</param>
822 /// <param name="id">名前空間のID。</param>
823 /// <returns>変換済み記事名。</returns>
824 private string ReplaceLinkNamespace(string title, int id)
828 if (!this.To.Namespaces.TryGetValue(id, out names))
830 // 翻訳先言語に相当する名前空間が無い場合、何もしない
834 // 記事名の名前空間部分を置き換えて返す
835 return names.FirstOrDefault() + title.Substring(title.IndexOf(':'));
839 /// 内部リンクを他言語版への{{仮リンク}}等に変換する。。
841 /// <param name="link">変換元言語間リンク。</param>
842 /// <returns>変換済み言語間リンク。</returns>
843 private IElement ReplaceLinkLinkInterwiki(MediaWikiLink link)
845 // 仮リンクにはセクションの指定が可能なので、存在する場合付加する
846 // ※ 渡されたlinkをそのまま使わないのは、余計なゴミが含まれる可能性があるため
847 MediaWikiLink title = new MediaWikiLink { Title = link.Title, Section = link.Section };
848 string langTitle = title.GetLinkString();
849 if (!String.IsNullOrEmpty(title.Section))
851 // 変換先言語版のセクションは、セクションの変換を通したものにする
852 title.Section = this.ReplaceLinkSection(title.Section);
855 // 表示名は、設定されていればその値を、なければ変換元言語の記事名を使用
856 string label = langTitle;
857 if (link.PipeTexts.Count > 0)
859 label = link.PipeTexts.Last().ToString();
863 // ※ {{仮リンク}}を想定しているが、やろうと思えば何でもできるのでテキストで処理
864 return new TextElement(this.To.FormatLinkInterwiki(title.GetLinkString(), this.From.Language.Code, langTitle, label));
868 /// 渡された要素リストに対して<see cref="ReplaceElement"/>による変換を行う。
870 /// <param name="elements">変換元要素リスト。</param>
871 /// <param name="parent">ページ要素を取得した変換元記事。</param>
872 /// <returns>変換済み要素リスト。</returns>
873 private IList<IElement> ReplaceElements(IList<IElement> elements, MediaWikiPage parent)
875 if (elements == null)
880 IList<IElement> result = new List<IElement>();
881 foreach (IElement e in elements)
883 result.Add(this.ReplaceElement(e, parent));
890 /// テンプレート名に必要に応じて名前空間を補完する。
892 /// <param name="template">テンプレート。</param>
893 /// <param name="parent">ページ要素を取得した変換元記事。</param>
894 /// <returns>補完済みのテンプレート名。</returns>
895 private string FillTemplateName(MediaWikiTemplate template, MediaWikiPage parent)
898 string filledTitle = parent.Normalize(template);
899 if (filledTitle == template.Title || template.IsSubpage())
901 // 補完が不要な場合、またはサブページだった場合、ここで終了
905 // プレフィックスが付いた記事名が実際に存在するかを確認
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 this.Logger.AddError(e);
934 throw new ApplicationException(e.Message, e);
937 // 続行する場合は、とりあえずプレフィックスをつけた名前で処理
938 this.Logger.AddResponse(Resources.LogMessageTemplateNameUnidentified, template.Title, filledTitle, e.Message);
950 /// <param name="title">翻訳支援対象の記事名。</param>
951 /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
952 private MediaWikiPage GetTargetPage(string title)
954 // 指定された記事をWikipediaから取得、リダイレクトの場合その先まで探索
955 // ※ この処理ではキャッシュは使用しない。
956 // ※ 万が一相互にリダイレクトしていると無限ループとなるが、特に判定はしない。
957 // ユーザーが画面上から止めることを期待。
958 this.Logger.AddMessage(Resources.LogMessageGetTargetArticle, this.From.Location, title);
960 for (string s = title; this.TryGetPage(s, out page); s = page.Redirect.Title)
964 // 記事が存在しない場合、メッセージを出力して終了
965 this.Logger.AddResponse(Resources.LogMessageTargetArticleNotFound);
968 else if (!page.IsRedirect())
974 // リダイレクトであれば、さらにその先の記事を取得
975 this.Logger.AddResponse(Resources.LogMessageRedirect
976 + " " + new MediaWikiLink(page.Redirect.Title).ToString());
983 /// 指定した言語での言語名称を [[言語名称|略称]]の内部リンクで取得。
985 /// <param name="site">サイト。</param>
986 /// <param name="code">言語のコード。</param>
987 /// <returns>[[言語名称|略称]]の内部リンク。登録されていない場合<c>null</c>。</returns>
988 private MediaWikiLink GetLanguageLink(Website site, string code)
990 if (!site.Language.Names.ContainsKey(code))
995 Language.LanguageName name = site.Language.Names[code];
996 MediaWikiLink link = new MediaWikiLink(name.Name);
997 if (!String.IsNullOrEmpty(name.ShortName))
999 link.PipeTexts.Add(new TextElement(name.ShortName));
1006 /// 対象記事に言語間リンクが存在する場合にメッセージダイアログでユーザーに確認する処理。
1008 /// <param name="interwiki">言語間リンク先記事。</param>
1009 /// <returns>処理を続行する場合<c>true</c>。</returns>
1010 private bool IsContinueAtInterwikiExistedWithDialog(string interwiki)
1013 if (MessageBox.Show(
1014 String.Format(Resources.QuestionMessageArticleExisted, interwiki),
1015 Resources.QuestionTitle,
1016 MessageBoxButtons.YesNo,
1017 MessageBoxIcon.Question)
1020 // 中断の場合、同じメッセージをログにも表示
1021 this.Logger.AddSeparator();
1022 this.Logger.AddMessage(Resources.QuestionMessageArticleExisted, interwiki);