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
37 /// <see cref="Translator.ItemTable"/>用ロックオブジェクト。
39 private LockObject itemTableLock = new LockObject();
48 public MediaWikiTranslator()
50 // このクラス用のロガーと、デフォルトの確認処理としてメッセージダイアログ版を設定
51 this.Logger = new MediaWikiLogger();
52 this.IsContinueAtInterwikiExisted = this.IsContinueAtInterwikiExistedWithDialog;
60 /// 対象記事に言語間リンクが存在する場合の確認処理を表すデリゲート。
62 /// <param name="interwiki">言語間リンク先記事。</param>
63 /// <returns>処理を続行する場合<c>true</c>。</returns>
64 public delegate bool IsContinueAtInterwikiExistedDelegate(string interwiki);
73 public new MediaWiki From
77 return base.From as MediaWiki;
89 public new MediaWiki To
93 return base.To as MediaWiki;
103 /// 対象記事に言語間リンクが存在する場合の確認処理。
105 /// <remarks>確認を行わない場合<c>null</c>。</remarks>
106 public IsContinueAtInterwikiExistedDelegate IsContinueAtInterwikiExisted
118 /// ※継承クラスでは、この関数に処理を実装すること
120 /// <param name="name">記事名。</param>
121 /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Translator.Logger"/>に出力される。</exception>
122 protected override void RunBody(string name)
125 MediaWikiPage article = this.GetTargetPage(name);
128 throw new ApplicationException("article is not found");
131 // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
132 // ※ 言語間リンク取得中は、処理状態を解析中に変更
133 MediaWikiLink interlanguage = null;
134 this.ChangeStatusInExecuting(
135 () => interlanguage = article.GetInterlanguage(this.To.Language.Code),
136 Resources.StatusParsing);
137 if (interlanguage != null)
139 // 確認処理の最中は処理時間をカウントしない(ダイアログ等を想定するため)
140 this.Stopwatch.Stop();
141 if (this.IsContinueAtInterwikiExisted != null && !this.IsContinueAtInterwikiExisted(interlanguage.Title))
143 throw new ApplicationException("user canceled");
146 this.Stopwatch.Start();
147 this.Logger.AddResponse(Resources.LogMessageTargetArticleHadInterWiki, interlanguage.Title);
151 this.Text += this.CreateOpening(article.Title);
153 // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
154 this.Logger.AddSeparator();
155 this.Logger.AddResponse(Resources.LogMessageStartParseAndReplace);
156 this.ChangeStatusInExecuting(
157 () => this.Text += this.ReplaceElement(article.Element, article).ToString(),
158 Resources.StatusParsing);
160 // 記事の末尾に新しい言語間リンクと、コメントを追記
161 this.Text += this.CreateEnding(article);
163 // ダウンロードされるテキストがLFなので、最後にクライアント環境に合わせた改行コードに変換
164 // ※ダウンロード時に変換するような仕組みが見つかれば、そちらを使う
165 // その場合、上のように\nをべたに吐いている部分を修正する
166 this.Text = this.Text.Replace("\n", Environment.NewLine);
171 #region 他のクラスの処理をこのクラスにあわせて拡張したメソッド
174 /// ログ出力によるエラー処理を含んだページ取得処理。
176 /// <param name="title">ページタイトル。</param>
177 /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
178 /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
179 /// <exception cref="ApplicationException"><see cref="Translator.CancellationPending"/>が<c>true</c>の場合。</exception>
181 /// 本メソッドは、大きく3パターンの動作を行う。
182 /// <list type="number">
183 /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
184 /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
185 /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
186 /// or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
188 /// また、実行中は処理状態をサーバー接続中に更新する。
189 /// 実行前後には終了要求のチェックも行う。
191 protected bool TryGetPage(string title, out MediaWikiPage page)
193 // & 等の特殊文字をデコードして、親クラスのメソッドを呼び出し
195 bool success = base.TryGetPage(WebUtility.HtmlDecode(title), out p);
196 page = p as MediaWikiPage;
202 #region 冒頭/末尾ブロックの生成メソッド
205 /// 変換後記事冒頭用の「'''日本語記事名'''([[英語|英]]: '''{{Lang|en|英語記事名}}''')」みたいなのを作成する。
207 /// <param name="title">翻訳支援対象の記事名。</param>
208 /// <returns>冒頭部のテキスト。</returns>
209 protected virtual string CreateOpening(string title)
211 string langPart = String.Empty;
212 MediaWikiLink langLink = this.GetLanguageLink(this.From, this.To.Language.Code);
213 if (langLink != null)
215 langPart = langLink.ToString() + ": ";
218 string langBody = this.To.FormatLang(this.From.Language.Code, title);
219 if (String.IsNullOrEmpty(langBody))
224 StringBuilder b = new StringBuilder("'''xxx'''");
225 b.Append(this.To.Language.FormatBracket(langPart + "'''" + langBody + "'''"));
231 /// 変換後記事末尾用の新しい言語間リンクとコメントを作成する。
233 /// <param name="page">翻訳支援対象の記事。</param>
234 /// <returns>末尾部のテキスト。</returns>
235 protected virtual string CreateEnding(MediaWikiPage page)
237 MediaWikiLink link = new MediaWikiLink();
238 link.Title = page.Title;
239 link.Interwiki = this.From.Language.Code;
240 return "\n\n" + link.ToString() + "\n" + String.Format(
241 Resources.ArticleFooter,
242 FormUtils.ApplicationName(),
243 this.From.Language.Code,
245 page.Timestamp.HasValue ? page.Timestamp.Value.ToString("U") : String.Empty) + "\n";
255 /// <param name="element">ページ要素。</param>
256 /// <param name="parent">ページ要素を取得した変換元記事。</param>
257 /// <returns>変換後のページ要素。</returns>
258 protected virtual IElement ReplaceElement(IElement element, MediaWikiPage parent)
261 this.ThrowExceptionIfCanceled();
263 // 要素の型に応じて、必要な置き換えを行う
264 if (element is MediaWikiTemplate)
267 return this.ReplaceTemplate((MediaWikiTemplate)element, parent);
269 else if (element is MediaWikiLink)
272 return this.ReplaceLink((MediaWikiLink)element, parent);
274 else if (element is MediaWikiHeading)
277 return this.ReplaceHeading((MediaWikiHeading)element, parent);
279 else if (element is MediaWikiVariable)
282 return this.ReplaceVariable((MediaWikiVariable)element, parent);
284 else if (element is ListElement)
287 return this.ReplaceListElement((ListElement)element, parent);
290 // それ以外は、特に何もせず元の値を返す
295 /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する。
297 /// <param name="link">変換元リンク。</param>
298 /// <param name="parent">ページ要素を取得した変換元記事。</param>
299 /// <returns>変換済みリンク。</returns>
300 protected virtual IElement ReplaceLink(MediaWikiLink link, MediaWikiPage parent)
302 // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
303 if (!this.IsSectionLink(link, parent.Title))
306 MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
309 if (link.IsSubpage())
311 // サブページ(子)の場合だけ後で記事名を復元するので記録
312 child = link.Title.StartsWith("/");
315 string title = parent.Normalize(link);
316 if (parent.Title.StartsWith(title))
318 // サブページ(親)の場合、変換してもしょうがないのでセクションだけチェックして終了
319 if (!String.IsNullOrEmpty(link.Section))
321 link.Section = this.ReplaceLinkSection(link.Section);
322 link.ParsedString = null;
330 else if (!String.IsNullOrEmpty(link.Interwiki))
332 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
333 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
334 return this.ReplaceLinkInterwiki(link);
336 else if (article.IsFile())
338 // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
339 return this.ReplaceLinkFile(link, parent);
341 else if (article.IsCategory() && !link.IsColon)
343 // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
345 return this.ReplaceLinkCategory(link);
348 // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
349 string interWiki = this.GetInterlanguage(link);
350 if (interWiki == null)
352 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
354 else if (interWiki == String.Empty)
356 // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
357 if (!String.IsNullOrEmpty(this.To.LinkInterwikiFormat))
359 return this.ReplaceLinkLinkInterwiki(link);
362 // 設定が無ければ [[:en:xxx]] みたいな形式に置換
363 link.Title = this.From.Language.Code + ':' + link.Title;
368 // 言語間リンクが存在してサブページ(子)の場合、親ページ部分を消す
369 // TODO: 兄弟や叔父のパターンも対処したい(ややこしいので現状未対応)
370 link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
374 // 普通に言語間リンクが存在する場合、記事名を置き換え
375 link.Title = interWiki;
378 if (link.PipeTexts.Count == 0 && interWiki != null)
380 // 表示名が存在しない場合、元の名前を表示名に設定
383 new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
388 // セクション部分([[#関連項目]]とか)を変換
389 if (!String.IsNullOrEmpty(link.Section))
391 link.Section = this.ReplaceLinkSection(link.Section);
394 link.ParsedString = null;
399 /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
401 /// <param name="template">変換元テンプレート。</param>
402 /// <param name="parent">ページ要素を取得した変換元記事。</param>
403 /// <returns>変換済みテンプレート。</returns>
404 protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, MediaWikiPage parent)
406 // システム変数({{PAGENAME}}とか)の場合は対象外
407 if (this.From.IsMagicWord(template.Title))
412 // テンプレートは通常名前空間が省略されているので補完する
413 string filledTitle = this.FillTemplateName(template, parent);
415 // リンクを辿り、対象記事の言語間リンクを取得
416 string interWiki = this.GetInterlanguage(new MediaWikiTemplate(filledTitle));
417 if (interWiki == null)
419 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
422 else if (interWiki == String.Empty)
424 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
425 // おまけで、元のテンプレートの状態をコメントでつける
426 ListElement list = new ListElement();
427 MediaWikiLink link = new MediaWikiLink();
429 link.Title = this.From.Language.Code + ':' + filledTitle;
431 XmlCommentElement comment = new XmlCommentElement();
432 comment.Raw = ' ' + template.ToString() + ' ';
438 // 言語間リンクが存在する場合、そちらを指すように置換
439 // : より前の部分を削除して出力(: が無いときは-1+1で0から)
440 template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
442 // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
443 template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
444 template.ParsedString = null;
450 /// 指定された見出しに対して、対訳表による変換を行う。
452 /// <param name="heading">見出し。</param>
453 /// <param name="parent">ページ要素を取得した変換元記事。</param>
454 /// <returns>変換後の見出し。</returns>
455 protected virtual IElement ReplaceHeading(MediaWikiHeading heading, MediaWikiPage parent)
458 this.Logger.AddSource(heading);
461 StringBuilder oldText = new StringBuilder();
462 foreach (IElement e in heading)
464 oldText.Append(e.ToString());
467 string newText = this.GetHeading(oldText.ToString().Trim());
470 // 対訳表による変換が行えた場合、変換先をログ出力し処理終了
472 heading.ParsedString = null;
473 heading.Add(new XmlTextElement(newText));
474 this.Logger.AddDestination(heading);
478 // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
479 return this.ReplaceListElement(heading, parent);
483 /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
485 /// <param name="variable">変換元変数要素。</param>
486 /// <param name="parent">ページ要素を取得した変換元記事。</param>
487 /// <returns>変換済み変数要素。</returns>
488 protected virtual IElement ReplaceVariable(MediaWikiVariable variable, MediaWikiPage parent)
490 // 変数、これ自体は処理しないが、再帰的に探索
491 string old = variable.Value.ToString();
492 variable.Value = this.ReplaceElement(variable.Value, parent);
493 if (variable.Value.ToString() != old)
495 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
496 variable.ParsedString = null;
503 /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
505 /// <param name="listElement">変換元要素。</param>
506 /// <param name="parent">ページ要素を取得した変換元記事。</param>
507 /// <returns>変換済み要素。</returns>
508 protected virtual IElement ReplaceListElement(ListElement listElement, MediaWikiPage parent)
510 // 値を格納する要素、これ自体は処理しないが、再帰的に探索
511 for (int i = 0; i < listElement.Count; i++)
513 string old = listElement[i].ToString();
514 listElement[i] = this.ReplaceElement(listElement[i], parent);
515 if (listElement[i].ToString() != old)
517 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
518 listElement.ParsedString = null;
530 /// 対訳表に指定された記事名の情報が登録されているか?
532 /// <param name="title">記事名。</param>
533 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
534 /// <remarks>複数スレッドからのアクセスに対応する。また項目の対訳表が無い場合も動作する。</remarks>
535 protected bool ContainsAtItemTable(string title)
537 if (this.ItemTable == null)
542 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
543 // ※ 対訳表へのアクセス時は記事名をデコードしておく
544 string decodedTitle = WebUtility.HtmlDecode(title);
545 lock (this.itemTableLock.GetObject(decodedTitle.ToLower()))
547 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
548 return this.ItemTable.ContainsKey(decodedTitle);
553 /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
555 /// <param name="heading">翻訳元言語での見出し。</param>
556 /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
557 /// <remarks>見出しの対訳表が無い場合も動作する。</remarks>
558 protected string GetHeading(string heading)
560 if (this.HeadingTable == null)
565 return this.HeadingTable.GetWord(heading);
573 /// ロガーに取得結果を出力しつつ、指定された要素の記事の翻訳先言語への言語間リンクを返す。
575 /// <param name="element">内部リンク要素。</param>
576 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
577 /// <remarks>取得処理では対訳表を使用する。また新たな取得結果は対訳表に追加する。</remarks>
578 protected string GetInterlanguage(MediaWikiLink element)
581 this.Logger.AddSource(element);
582 string title = element.Title;
583 TranslationDictionary.Item item;
584 if (this.ItemTable == null)
586 // 対訳表が指定されていない場合は、使わずに言語間リンクを探索して終了
587 return this.GetInterlanguageWithCreateCache(title, out item);
590 // 対訳表を使用して言語間リンクを探索。
591 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック(記事名単位)。
592 // また、対訳表へのアクセス時は記事名をデコードしておく。
593 string decodedTitle = WebUtility.HtmlDecode(title);
594 lock (this.itemTableLock.GetObject(decodedTitle.ToLower()))
596 if (this.ItemTable.TryGetValue(decodedTitle, out item))
599 if (!String.IsNullOrWhiteSpace(item.Alias))
601 // リダイレクトがあれば、そのメッセージも表示
602 this.Logger.AddAlias(new MediaWikiLink(item.Alias));
605 if (!String.IsNullOrEmpty(item.Word))
607 this.Logger.AddDestination(new MediaWikiLink(item.Word), true);
612 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound), true);
617 // 対訳表に存在しない場合は、普通に取得し表に記録
618 // ※ こちらは内部でデコードしているためデコードした記事名を渡してはならない
619 string interlanguage = this.GetInterlanguageWithCreateCache(title, out item);
620 if (interlanguage != null)
622 // ページ自体が存在しない場合を除き、結果を対訳表に登録
623 // ※ キャッシュとしては登録すべきかもしれないが、一応"対訳表"であるので
624 this.ItemTable[decodedTitle] = item;
627 return interlanguage;
632 /// ロガーに取得結果を出力しつつ、指定された記事の翻訳先言語への言語間リンクを返す。
633 /// キャッシュ用の処理結果情報も出力する。
635 /// <param name="title">記事名。</param>
636 /// <param name="item">キャッシュ用の処理結果情報。</param>
637 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
638 private string GetInterlanguageWithCreateCache(string title, out TranslationDictionary.Item item)
641 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
642 MediaWikiPage page = this.GetDestinationPage(title);
643 if (page != null && page.IsRedirect())
645 // リダイレクトの場合、リダイレクトである旨出力し、その先の記事を取得
646 this.Logger.AddAlias(new MediaWikiLink(page.Redirect.Title));
647 item.Alias = page.Redirect.Title;
648 page = this.GetDestinationPage(page.Redirect.Title);
653 // ページ自体が存在しない場合はnull
658 MediaWikiLink interlanguage = page.GetInterlanguage(this.To.Language.Code);
659 if (interlanguage != null)
661 item.Word = interlanguage.Title;
662 this.Logger.AddDestination(interlanguage);
667 item.Word = String.Empty;
668 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound));
677 /// <param name="title">ページタイトル。</param>
678 /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
679 /// <remarks>記事が無い場合、通信エラーなど例外が発生した場合は、エラーログを出力する。</remarks>
680 private MediaWikiPage GetDestinationPage(string title)
683 if (this.TryGetPage(title, out page) && page == null)
685 // 記事が存在しない場合だけ、変換先に「記事無し」を出力
686 // ※ エラー時のログはTryGetPageが自動的に出力
687 this.Logger.AddDestination(new TextElement(Resources.LogMessageLinkArticleNotFound));
695 #region 要素の変換関連その他メソッド
698 /// 同記事内の別のセクションを指すリンク([[#関連項目]]とか[[自記事#関連項目]]とか)か?
700 /// <param name="link">判定する内部リンク。</param>
701 /// <param name="parent">内部リンクがあった記事。</param>
702 /// <returns>セクション部分のみ変換済みリンク。</returns>
703 private bool IsSectionLink(MediaWikiLink link, string parent)
705 // 記事名が指定されていない、または記事名が自分の記事名で
706 // 言語コード等も特に無く、かつセクションが指定されている場合
707 // (記事名もセクションも指定されていない・・・というケースもありえるが、
708 // その場合他に指定できるものも思いつかないので通す)
709 return String.IsNullOrEmpty(link.Title)
710 || (link.Title == parent && String.IsNullOrEmpty(link.Interwiki) && !String.IsNullOrEmpty(link.Section));
714 /// 内部リンクのセクション部分([[#関連項目]]とか)の定型句変換を行う。
716 /// <param name="section">セクション文字列。</param>
717 /// <returns>セクション部分のみ変換済みリンク。</returns>
718 private string ReplaceLinkSection(string section)
720 // セクションが指定されている場合、定型句変換を通す
721 string heading = this.GetHeading(section);
722 return heading != null ? heading : section;
726 /// 言語間リンク指定の内部リンクを解析し、不要であれば削除する。
728 /// <param name="link">変換元言語間リンク。</param>
729 /// <returns>変換済み言語間リンク。</returns>
730 private IElement ReplaceLinkInterwiki(MediaWikiLink link)
732 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
733 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
734 if (!link.IsColon && link.Interwiki == this.To.Language.Code)
736 return new TextElement();
743 /// カテゴリ指定の内部リンクを解析し、変換先言語のカテゴリへのリンクに変換する。
745 /// <param name="link">変換元カテゴリ。</param>
746 /// <returns>変換済みカテゴリ。</returns>
747 private IElement ReplaceLinkCategory(MediaWikiLink link)
749 // リンクを辿り、対象記事の言語間リンクを取得
750 string interWiki = this.GetInterlanguage(link);
751 if (interWiki == null)
753 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
756 else if (interWiki == String.Empty)
758 // 言語間リンクが存在しない場合、コメントで元の文字列を保存した後
759 // [[:en:xxx]]みたいな形式に置換。また | 以降は削除する
760 XmlCommentElement comment = new XmlCommentElement();
761 comment.Raw = ' ' + link.ToString() + ' ';
763 link.Title = this.From.Language.Code + ':' + link.Title;
765 link.PipeTexts.Clear();
766 link.ParsedString = null;
768 ListElement list = new ListElement();
775 // 普通に言語間リンクが存在する場合、記事名を置き換え
776 link.Title = interWiki;
777 link.ParsedString = null;
783 /// ファイル指定の内部リンクを解析し、変換先言語で参照可能なファイルへのリンクに変換する。
785 /// <param name="link">変換元リンク。</param>
786 /// <param name="parent">ページ要素を取得した変換元記事。</param>
787 /// <returns>変換済みリンク。</returns>
788 private IElement ReplaceLinkFile(MediaWikiLink link, MediaWikiPage parent)
790 // 名前空間を翻訳先言語の書式に変換、またパラメータ部を再帰的に処理
791 link.Title = this.ReplaceLinkNamespace(link.Title, this.To.FileNamespace);
792 link.PipeTexts = this.ReplaceElements(link.PipeTexts, parent);
793 link.ParsedString = null;
798 /// 記事名のうち名前空間部分の変換先言語への変換を行う。
800 /// <param name="title">変換元記事名。</param>
801 /// <param name="id">名前空間のID。</param>
802 /// <returns>変換済み記事名。</returns>
803 private string ReplaceLinkNamespace(string title, int id)
807 if (!this.To.Namespaces.TryGetValue(id, out names))
809 // 翻訳先言語に相当する名前空間が無い場合、何もしない
813 // 記事名の名前空間部分を置き換えて返す
814 return names.FirstOrDefault() + title.Substring(title.IndexOf(':'));
818 /// 内部リンクを他言語版への{{仮リンク}}等に変換する。。
820 /// <param name="link">変換元言語間リンク。</param>
821 /// <returns>変換済み言語間リンク。</returns>
822 private IElement ReplaceLinkLinkInterwiki(MediaWikiLink link)
824 // 仮リンクにはセクションの指定が可能なので、存在する場合付加する
825 // ※ 渡されたlinkをそのまま使わないのは、余計なゴミが含まれる可能性があるため
826 MediaWikiLink title = new MediaWikiLink { Title = link.Title, Section = link.Section };
827 string langTitle = title.GetLinkString();
828 if (!String.IsNullOrEmpty(title.Section))
830 // 変換先言語版のセクションは、セクションの変換を通したものにする
831 title.Section = this.ReplaceLinkSection(title.Section);
834 // 表示名は、設定されていればその値を、なければ変換元言語の記事名を使用
835 string label = langTitle;
836 if (link.PipeTexts.Count > 0)
838 label = link.PipeTexts.Last().ToString();
842 // ※ {{仮リンク}}を想定しているが、やろうと思えば何でもできるのでテキストで処理
843 return new TextElement(this.To.FormatLinkInterwiki(title.GetLinkString(), this.From.Language.Code, langTitle, label));
847 /// 渡された要素リストに対して<see cref="ReplaceElement"/>による変換を行う。
849 /// <param name="elements">変換元要素リスト。</param>
850 /// <param name="parent">ページ要素を取得した変換元記事。</param>
851 /// <returns>変換済み要素リスト。</returns>
852 private IList<IElement> ReplaceElements(IList<IElement> elements, MediaWikiPage parent)
854 if (elements == null)
859 IList<IElement> result = new List<IElement>();
860 foreach (IElement e in elements)
862 result.Add(this.ReplaceElement(e, parent));
869 /// テンプレート名に必要に応じて名前空間を補完する。
871 /// <param name="template">テンプレート。</param>
872 /// <param name="parent">ページ要素を取得した変換元記事。</param>
873 /// <returns>補完済みのテンプレート名。</returns>
874 private string FillTemplateName(MediaWikiTemplate template, MediaWikiPage parent)
877 string filledTitle = parent.Normalize(template);
878 if (filledTitle == template.Title || template.IsSubpage())
880 // 補完が不要な場合、またはサブページだった場合、ここで終了
884 // プレフィックスが付いた記事名が実際に存在するかを確認
885 // ※ 不要かもしれないが、マジックワードの漏れ等の誤検出を減らしたいので
886 if (this.ContainsAtItemTable(filledTitle))
888 // 対訳表に記事名が確認されている場合、既知の名前として確定
892 // 実際に頭にプレフィックスを付けた記事名でアクセスし、存在するかをチェック
893 // TODO: GetInterWikiの方とあわせ、テンプレートでは2度GetPageが呼ばれている。可能であれば共通化する
894 MediaWikiPage page = null;
897 // 記事が存在する場合、プレフィックスをつけた名前を使用
898 page = this.From.GetPage(WebUtility.HtmlDecode(filledTitle)) as MediaWikiPage;
901 catch (FileNotFoundException)
903 // 記事が存在しない場合、元のページ名を使用
904 return template.Title;
909 if (!Settings.Default.IgnoreError)
911 // エラーを無視しない場合、ここで翻訳支援処理を中断する
912 this.Logger.AddError(e);
913 throw new ApplicationException(e.Message, e);
916 // 続行する場合は、とりあえずプレフィックスをつけた名前で処理
917 this.Logger.AddResponse(Resources.LogMessageTemplateNameUnidentified, template.Title, filledTitle, e.Message);
929 /// <param name="title">翻訳支援対象の記事名。</param>
930 /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
931 private MediaWikiPage GetTargetPage(string title)
933 // 指定された記事をWikipediaから取得、リダイレクトの場合その先まで探索
934 // ※ この処理ではキャッシュは使用しない。
935 // ※ 万が一相互にリダイレクトしていると無限ループとなるが、特に判定はしない。
936 // ユーザーが画面上から止めることを期待。
937 this.Logger.AddMessage(Resources.LogMessageGetTargetArticle, this.From.Location, title);
939 for (string s = title; this.TryGetPage(s, out page); s = page.Redirect.Title)
943 // 記事が存在しない場合、メッセージを出力して終了
944 this.Logger.AddResponse(Resources.LogMessageTargetArticleNotFound);
947 else if (!page.IsRedirect())
953 // リダイレクトであれば、さらにその先の記事を取得
954 this.Logger.AddResponse(Resources.LogMessageRedirect
955 + " " + new MediaWikiLink(page.Redirect.Title).ToString());
962 /// 指定した言語での言語名称を [[言語名称|略称]]の内部リンクで取得。
964 /// <param name="site">サイト。</param>
965 /// <param name="code">言語のコード。</param>
966 /// <returns>[[言語名称|略称]]の内部リンク。登録されていない場合<c>null</c>。</returns>
967 private MediaWikiLink GetLanguageLink(Website site, string code)
969 if (!site.Language.Names.ContainsKey(code))
974 Language.LanguageName name = site.Language.Names[code];
975 MediaWikiLink link = new MediaWikiLink(name.Name);
976 if (!String.IsNullOrEmpty(name.ShortName))
978 link.PipeTexts.Add(new TextElement(name.ShortName));
985 /// 対象記事に言語間リンクが存在する場合にメッセージダイアログでユーザーに確認する処理。
987 /// <param name="interwiki">言語間リンク先記事。</param>
988 /// <returns>処理を続行する場合<c>true</c>。</returns>
989 private bool IsContinueAtInterwikiExistedWithDialog(string interwiki)
993 String.Format(Resources.QuestionMessageArticleExisted, interwiki),
994 Resources.QuestionTitle,
995 MessageBoxButtons.YesNo,
996 MessageBoxIcon.Question)
999 // 中断の場合、同じメッセージをログにも表示
1000 this.Logger.AddSeparator();
1001 this.Logger.AddMessage(Resources.QuestionMessageArticleExisted, interwiki);