1 // ================================================================================================
3 // Wikipedia用の翻訳支援処理実装クラスソース</summary>
5 // <copyright file="MediaWikiTranslator.cs" company="honeplusのメモ帳">
6 // Copyright (C) 2013 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 /// MediaWiki用の翻訳支援処理実装クラスです。
32 public class MediaWikiTranslator : Translator
37 /// <see cref="Translator.ItemTable"/>用ロックオブジェクト。
39 private LockObject itemTableLock = new LockObject();
46 /// MediaWikiでの翻訳支援処理を行うトランスレータを作成。
49 /// 別途プロパティに必要なパラメータを設定する必要あり。
50 /// 通常は<see cref="Translator.Create"/>にて設定ファイルから作成する。
52 public MediaWikiTranslator()
54 // このクラス用のロガーと、デフォルトの確認処理としてメッセージダイアログ版を設定
55 this.Logger = new MediaWikiLogger();
56 this.IsContinueAtInterwikiExisted = this.IsContinueAtInterwikiExistedWithDialog;
64 /// 対象記事に言語間リンクが存在する場合の確認処理を表すデリゲート。
66 /// <param name="interwiki">言語間リンク先記事。</param>
67 /// <returns>処理を続行する場合<c>true</c>。</returns>
68 public delegate bool IsContinueAtInterwikiExistedDelegate(string interwiki);
77 public new MediaWiki From
81 return base.From as MediaWiki;
93 public new MediaWiki To
97 return base.To as MediaWiki;
107 /// 対象記事に言語間リンクが存在する場合の確認処理。
109 /// <remarks>確認を行わない場合<c>null</c>。</remarks>
110 public IsContinueAtInterwikiExistedDelegate IsContinueAtInterwikiExisted
122 /// ※継承クラスでは、この関数に処理を実装すること
124 /// <param name="name">記事名。</param>
125 /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Translator.Logger"/>に出力される。</exception>
126 protected override void RunBody(string name)
129 MediaWikiPage article = this.GetTargetPage(name);
132 throw new ApplicationException("article is not found");
135 // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
136 // ※ 言語間リンク取得中は、処理状態を解析中に変更
137 string interlanguage;
138 using (var sm = this.StatusManager.Switch(Resources.StatusParsing))
140 interlanguage = article.GetInterlanguage(this.To.Language.Code);
143 if (!string.IsNullOrEmpty(interlanguage))
145 // 確認処理の最中は処理時間をカウントしない(ダイアログ等を想定するため)
146 this.Stopwatch.Stop();
147 if (this.IsContinueAtInterwikiExisted != null && !this.IsContinueAtInterwikiExisted(interlanguage))
149 throw new ApplicationException("user canceled");
152 this.Stopwatch.Start();
153 this.Logger.AddResponse(Resources.LogMessageTargetArticleHadInterWiki, interlanguage);
157 this.Text += this.CreateOpening(article.Title);
159 // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
160 this.Logger.AddSeparator();
161 this.Logger.AddResponse(Resources.LogMessageStartParseAndReplace);
162 using (var sm = this.StatusManager.Switch(Resources.StatusParsing))
165 using (MediaWikiParser parser = new MediaWikiParser(this.From))
167 element = parser.Parse(article.Text);
170 this.Text += this.ReplaceElement(element, article).ToString();
173 // 記事の末尾に新しい言語間リンクと、コメントを追記
174 this.Text += this.CreateEnding(article);
176 // ダウンロードされるテキストがLFなので、最後にクライアント環境に合わせた改行コードに変換
177 // ※ダウンロード時に変換するような仕組みが見つかれば、そちらを使う
178 // その場合、上のように\nをべたに吐いている部分を修正する
179 this.Text = this.Text.Replace("\n", Environment.NewLine);
184 #region 他のクラスの処理をこのクラスにあわせて拡張したメソッド
187 /// ログ出力によるエラー処理を含んだページ取得処理。
189 /// <param name="title">ページタイトル。</param>
190 /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
191 /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
192 /// <exception cref="ApplicationException"><see cref="Translator.CancellationPending"/>が<c>true</c>の場合。</exception>
194 /// 本メソッドは、大きく3パターンの動作を行う。
195 /// <list type="number">
196 /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
197 /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
198 /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
199 /// or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
201 /// また、実行中は処理状態をサーバー接続中に更新する。
202 /// 実行前後には終了要求のチェックも行う。
204 protected bool TryGetPage(string title, out MediaWikiPage page)
206 // & 等の特殊文字をデコードして、親クラスのメソッドを呼び出し
208 bool success = base.TryGetPage(WebUtility.HtmlDecode(title), out p);
209 page = p as MediaWikiPage;
215 #region 冒頭/末尾ブロックの生成メソッド
218 /// 変換後記事冒頭用の「'''日本語記事名'''([[英語|英]]: '''{{Lang|en|英語記事名}}''')」みたいなのを作成する。
220 /// <param name="title">翻訳支援対象の記事名。</param>
221 /// <returns>冒頭部のテキスト。</returns>
222 protected virtual string CreateOpening(string title)
224 string langPart = string.Empty;
225 IElement langLink = this.GetLanguageLink();
226 if (langLink != null)
228 langPart = langLink.ToString() + ": ";
231 string langBody = this.To.FormatLang(this.From.Language.Code, title);
232 if (string.IsNullOrEmpty(langBody))
237 StringBuilder b = new StringBuilder("'''xxx'''");
238 b.Append(this.To.Language.FormatBracket(langPart + "'''" + langBody + "'''"));
244 /// 変換後記事末尾用の新しい言語間リンクとコメントを作成する。
246 /// <param name="page">翻訳支援対象の記事。</param>
247 /// <returns>末尾部のテキスト。</returns>
248 protected virtual string CreateEnding(MediaWikiPage page)
250 MediaWikiLink link = new MediaWikiLink();
251 link.Title = page.Title;
252 link.Interwiki = this.From.Language.Code;
253 return "\n\n" + link.ToString() + "\n" + string.Format(
254 Resources.ArticleFooter,
255 FormUtils.ApplicationName(),
256 this.From.Language.Code,
258 page.Timestamp.HasValue ? page.Timestamp.Value.ToString("U") : string.Empty) + "\n";
268 /// <param name="element">ページ要素。</param>
269 /// <param name="parent">ページ要素を取得した変換元記事。</param>
270 /// <returns>変換後のページ要素。</returns>
271 protected virtual IElement ReplaceElement(IElement element, MediaWikiPage parent)
274 this.ThrowExceptionIfCanceled();
276 // 要素の型に応じて、必要な置き換えを行う
277 if (element is MediaWikiTemplate)
280 return this.ReplaceTemplate((MediaWikiTemplate)element, parent);
282 else if (element is MediaWikiLink)
285 return this.ReplaceLink((MediaWikiLink)element, parent);
287 else if (element is MediaWikiHeading)
290 return this.ReplaceHeading((MediaWikiHeading)element, parent);
292 else if (element is MediaWikiVariable)
295 return this.ReplaceVariable((MediaWikiVariable)element, parent);
297 else if (element is ListElement)
300 return this.ReplaceListElement((ListElement)element, parent);
303 // それ以外は、特に何もせず元の値を返す
308 /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する。
310 /// <param name="link">変換元リンク。</param>
311 /// <param name="parent">ページ要素を取得した変換元記事。</param>
312 /// <returns>変換済みリンク。</returns>
313 protected virtual IElement ReplaceLink(MediaWikiLink link, MediaWikiPage parent)
315 // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
316 if (!this.IsSectionLink(link, parent.Title))
319 MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
322 if (link.IsSubpage())
324 // サブページ(子)の場合だけ後で記事名を復元するので記録
325 child = link.Title.StartsWith("/");
328 string title = parent.Normalize(link);
329 if (parent.Title.StartsWith(title))
331 // サブページ(親)の場合、変換してもしょうがないのでセクションだけチェックして終了
332 if (!string.IsNullOrEmpty(link.Section))
334 link.Section = this.ReplaceLinkSection(link.Section);
335 link.ParsedString = null;
343 else if (!string.IsNullOrEmpty(link.Interwiki))
345 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
346 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
347 return this.ReplaceLinkInterwiki(link);
349 else if (article.IsFile())
351 // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
352 return this.ReplaceLinkFile(link, parent);
354 else if (article.IsCategory() && !link.IsColon)
356 // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
358 return this.ReplaceLinkCategory(link);
361 // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
362 string interWiki = this.GetInterlanguage(link);
363 if (interWiki == null)
365 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
367 else if (interWiki == string.Empty)
369 // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
370 if (!string.IsNullOrEmpty(this.To.LinkInterwikiFormat))
372 return this.ReplaceLinkLinkInterwiki(link);
375 // 設定が無ければ [[:en:xxx]] みたいな形式に置換
376 link.Title = this.From.Language.Code + ':' + link.Title;
381 // 言語間リンクが存在してサブページ(子)の場合、親ページ部分を消す
382 // TODO: 兄弟や叔父のパターンも対処したい(ややこしいので現状未対応)
383 link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
387 // 普通に言語間リンクが存在する場合、記事名を置き換え
388 link.Title = interWiki;
391 if (link.PipeTexts.Count == 0 && interWiki != null)
393 // 表示名が存在しない場合、元の名前を表示名に設定
396 new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
401 // セクション部分([[#関連項目]]とか)を変換
402 if (!string.IsNullOrEmpty(link.Section))
404 link.Section = this.ReplaceLinkSection(link.Section);
407 link.ParsedString = null;
412 /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
414 /// <param name="template">変換元テンプレート。</param>
415 /// <param name="parent">ページ要素を取得した変換元記事。</param>
416 /// <returns>変換済みテンプレート。</returns>
417 protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, MediaWikiPage parent)
419 // システム変数({{PAGENAME}}とか)の場合は対象外
420 if (this.From.IsMagicWord(template.Title))
425 // テンプレートは通常名前空間が省略されているので補完する
426 string filledTitle = this.FillTemplateName(template, parent);
428 // リンクを辿り、対象記事の言語間リンクを取得
429 string interWiki = this.GetInterlanguage(new MediaWikiTemplate(filledTitle));
430 if (interWiki == null)
432 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
435 else if (interWiki == string.Empty)
437 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
438 // おまけで、元のテンプレートの状態をコメントでつける
439 ListElement list = new ListElement();
440 MediaWikiLink link = new MediaWikiLink();
442 link.Title = this.From.Language.Code + ':' + filledTitle;
444 XmlCommentElement comment = new XmlCommentElement();
445 comment.Raw = ' ' + template.ToString() + ' ';
451 // 言語間リンクが存在する場合、そちらを指すように置換
452 template.Title = interWiki;
453 if (new MediaWikiPage(this.To, interWiki).IsTemplate())
455 // 言語間リンク先がテンプレートの場合、: より前の部分を削除
456 template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
459 // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
460 template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
461 template.ParsedString = null;
467 /// 指定された見出しに対して、対訳表による変換を行う。
469 /// <param name="heading">見出し。</param>
470 /// <param name="parent">ページ要素を取得した変換元記事。</param>
471 /// <returns>変換後の見出し。</returns>
472 protected virtual IElement ReplaceHeading(MediaWikiHeading heading, MediaWikiPage parent)
475 this.Logger.AddSource(heading);
478 StringBuilder oldText = new StringBuilder();
479 foreach (IElement e in heading)
481 oldText.Append(e.ToString());
484 string newText = this.GetHeading(oldText.ToString().Trim());
487 // 対訳表による変換が行えた場合、変換先をログ出力し処理終了
489 heading.ParsedString = null;
490 heading.Add(new XmlTextElement(newText));
491 this.Logger.AddDestination(heading);
495 // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
496 return this.ReplaceListElement(heading, parent);
500 /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
502 /// <param name="variable">変換元変数要素。</param>
503 /// <param name="parent">ページ要素を取得した変換元記事。</param>
504 /// <returns>変換済み変数要素。</returns>
505 protected virtual IElement ReplaceVariable(MediaWikiVariable variable, MediaWikiPage parent)
507 // 変数、これ自体は処理しないが、再帰的に探索
508 if (variable.Value != null)
510 string old = variable.Value.ToString();
511 variable.Value = this.ReplaceElement(variable.Value, parent);
512 if (variable.Value.ToString() != old)
514 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
515 variable.ParsedString = null;
523 /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
525 /// <param name="listElement">変換元要素。</param>
526 /// <param name="parent">ページ要素を取得した変換元記事。</param>
527 /// <returns>変換済み要素。</returns>
528 protected virtual IElement ReplaceListElement(ListElement listElement, MediaWikiPage parent)
530 // 値を格納する要素、これ自体は処理しないが、再帰的に探索
531 for (int i = 0; i < listElement.Count; i++)
533 string old = listElement[i].ToString();
534 listElement[i] = this.ReplaceElement(listElement[i], parent);
535 if (listElement[i].ToString() != old)
537 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
538 listElement.ParsedString = null;
550 /// 対訳表に指定された記事名の情報が登録されているか?
552 /// <param name="title">記事名。</param>
553 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
554 /// <remarks>複数スレッドからのアクセスに対応する。また項目の対訳表が無い場合も動作する。</remarks>
555 protected bool ContainsAtItemTable(string title)
557 if (this.ItemTable == null)
562 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
563 // ※ 対訳表へのアクセス時は記事名をデコードしておく
564 string decodedTitle = WebUtility.HtmlDecode(title);
565 lock (this.itemTableLock.GetObject(decodedTitle.ToLower()))
567 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
568 return this.ItemTable.ContainsKey(decodedTitle);
573 /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
575 /// <param name="heading">翻訳元言語での見出し。</param>
576 /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
577 /// <remarks>見出しの対訳表が無い場合も動作する。</remarks>
578 protected string GetHeading(string heading)
580 if (this.HeadingTable == null)
585 return this.HeadingTable.GetWord(heading);
593 /// ロガーに取得結果を出力しつつ、指定された要素の記事の翻訳先言語への言語間リンクを返す。
595 /// <param name="element">内部リンク要素。</param>
596 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
597 /// <remarks>取得処理では対訳表を使用する。また新たな取得結果は対訳表に追加する。</remarks>
598 protected string GetInterlanguage(MediaWikiLink element)
601 this.Logger.AddSource(element);
602 string title = element.Title;
603 TranslationDictionary.Item item;
604 if (this.ItemTable == null)
606 // 対訳表が指定されていない場合は、使わずに言語間リンクを探索して終了
607 return this.GetInterlanguageWithCreateCache(title, out item);
610 // 対訳表を使用して言語間リンクを探索。
611 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック(記事名単位)。
612 // また、対訳表へのアクセス時は記事名をデコードしておく。
613 string decodedTitle = WebUtility.HtmlDecode(title);
614 lock (this.itemTableLock.GetObject(decodedTitle.ToLower()))
616 if (this.ItemTable.TryGetValue(decodedTitle, out item))
619 if (!string.IsNullOrWhiteSpace(item.Alias))
621 // リダイレクトがあれば、そのメッセージも表示
622 this.Logger.AddAlias(new MediaWikiLink(item.Alias));
625 if (!string.IsNullOrEmpty(item.Word))
627 this.Logger.AddDestination(new MediaWikiLink(item.Word), true);
632 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound), true);
637 // 対訳表に存在しない場合は、普通に取得し表に記録
638 // ※ こちらは内部でデコードしているためデコードした記事名を渡してはならない
639 string interlanguage = this.GetInterlanguageWithCreateCache(title, out item);
640 if (interlanguage != null)
642 // ページ自体が存在しない場合を除き、結果を対訳表に登録
643 // ※ キャッシュとしては登録すべきかもしれないが、一応"対訳表"であるので
644 this.ItemTable[decodedTitle] = item;
647 return interlanguage;
652 /// ロガーに取得結果を出力しつつ、指定された記事の翻訳先言語への言語間リンクを返す。
653 /// キャッシュ用の処理結果情報も出力する。
655 /// <param name="title">記事名。</param>
656 /// <param name="item">キャッシュ用の処理結果情報。</param>
657 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
658 private string GetInterlanguageWithCreateCache(string title, out TranslationDictionary.Item item)
661 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
662 MediaWikiPage page = this.GetDestinationPage(title);
665 // ページ自体が存在しない場合はnull
669 if (page.IsRedirect())
671 // リダイレクトの場合、リダイレクトである旨出力
672 this.Logger.AddAlias(new MediaWikiLink(page.Title));
673 item.Alias = page.Title;
677 string interlanguage = page.GetInterlanguage(this.To.Language.Code);
678 if (!string.IsNullOrEmpty(interlanguage))
680 item.Word = interlanguage;
681 MediaWikiLink link = new MediaWikiLink(interlanguage);
682 link.Interwiki = this.To.Language.Code;
683 this.Logger.AddDestination(link);
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>
953 /// ここで取得した記事のURIを、以後の翻訳支援処理で使用するRefererとして登録
954 /// (ここで処理しているのは、リダイレクトの場合のリダイレクト先への
955 /// アクセス時にもRefererを入れたかったから。
956 /// リダイレクトの場合は、最終的には転送先ページのURIとなる)。
958 private MediaWikiPage GetTargetPage(string title)
960 // 指定された記事をWikipediaから取得、リダイレクトの場合その先まで探索
961 // ※ この処理ではキャッシュは使用しない。
962 this.Logger.AddMessage(Resources.LogMessageGetTargetArticle, this.From.Location, title);
964 if (!this.TryGetPage(title, out page))
969 else if (page == null)
971 // 記事が存在しない場合、メッセージを出力して終了
972 this.Logger.AddResponse(Resources.LogMessageTargetArticleNotFound);
976 if (page.IsRedirect())
978 // リダイレクトであれば、その旨メッセージを表示
979 this.Logger.AddResponse(Resources.LogMessageRedirect
980 + " " + new MediaWikiLink(page.Title).ToString());
983 // ページ本文にアクセスして遅延読み込みを実行させる
984 if (!this.TryLoadPage(page))
990 // 取得した記事のURIを以後のアクセスで用いるRefererとして登録
991 this.From.WebProxy.Referer = page.Uri.AbsoluteUri;
992 this.To.WebProxy.Referer = page.Uri.AbsoluteUri;
998 /// 翻訳支援対象のページの本文・タイムスタンプを読み込み。
1000 /// <param name="page">翻訳支援対象のページ。</param>
1001 /// <returns>読み込み成功の場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
1002 private bool TryLoadPage(MediaWikiPage page)
1004 // ページ本文にアクセスして遅延読み込みを実行させる
1007 string dummy = page.Text;
1011 // その他例外の場合、エラー情報を出力
1012 this.Logger.AddError(e);
1020 /// 指定した言語での言語名称を [[言語名称|略称]]の内部リンクで取得。
1023 /// [[言語名称|略称]]の内部リンク。登録されていない場合<c>null</c>。
1024 /// サーバーにそうした記事が存在しない場合、リンクではなく言語名称or略称の文字列を返す。
1026 private IElement GetLanguageLink()
1029 Language.LanguageName name;
1030 if (!this.From.Language.Names.TryGetValue(this.To.Language.Code, out name))
1036 IElement shortName = null;
1037 if (!string.IsNullOrEmpty(name.ShortName))
1039 shortName = new TextElement(name.ShortName);
1042 if (this.To.HasLanguagePage)
1044 // サーバーにこの言語の記事が存在することが期待される場合、
1046 MediaWikiLink link = new MediaWikiLink(name.Name);
1047 if (shortName != null)
1049 link.PipeTexts.Add(shortName);
1054 else if (shortName != null)
1056 // 存在しない場合、まずあれば略称を返す
1062 return new TextElement(name.Name);
1067 /// 対象記事に言語間リンクが存在する場合にメッセージダイアログでユーザーに確認する処理。
1069 /// <param name="interwiki">言語間リンク先記事。</param>
1070 /// <returns>処理を続行する場合<c>true</c>。</returns>
1071 private bool IsContinueAtInterwikiExistedWithDialog(string interwiki)
1074 if (MessageBox.Show(
1075 string.Format(Resources.QuestionMessageArticleExisted, interwiki),
1076 Resources.QuestionTitle,
1077 MessageBoxButtons.YesNo,
1078 MessageBoxIcon.Question)
1081 // 中断の場合、同じメッセージをログにも表示
1082 this.Logger.AddSeparator();
1083 this.Logger.AddMessage(Resources.QuestionMessageArticleExisted, interwiki);