1 // ================================================================================================
3 // MediaWikiのページをあらわすモデルクラスソース</summary>
5 // <copyright file="MediaWikiPage.cs" company="honeplusのメモ帳">
6 // Copyright (C) 2013 Honeplus. All rights reserved.</copyright>
9 // ================================================================================================
11 namespace Honememo.Wptscs.Websites
14 using System.Collections.Generic;
17 using System.Xml.Linq;
18 using Honememo.Models;
19 using Honememo.Utilities;
20 using Honememo.Wptscs.Parsers;
23 /// MediaWikiのページをあらわすモデルクラスです。
25 public class MediaWikiPage : Page
30 /// 指定されたMediaWikiの渡されたタイトル, 本文, タイムスタンプのページを作成。
32 /// <param name="website">ページが所属するウェブサイト。</param>
33 /// <param name="title">ページタイトル。</param>
34 /// <param name="text">ページの本文。</param>
35 /// <param name="timestamp">ページのタイムスタンプ。</param>
36 /// <param name="uri">ページのURI。</param>
37 /// <exception cref="ArgumentNullException"><paramref name="website"/>または<paramref name="title"/>が<c>null</c>の場合。</exception>
38 /// <exception cref="ArgumentException"><paramref name="title"/>が空の文字列の場合。</exception>
39 public MediaWikiPage(MediaWiki website, string title, string text, DateTime? timestamp, Uri uri)
40 : base(website, title, text, timestamp, uri)
42 this.Interlanguages = new Dictionary<string, string>();
46 /// 指定されたMediaWikiの渡されたタイトル, 本文のページを作成。
48 /// <param name="website">ページが所属するウェブサイト。</param>
49 /// <param name="title">ページタイトル。</param>
50 /// <param name="text">ページの本文。</param>
51 /// <remarks>ページのタイムスタンプ, URIには<c>null</c>を設定。</remarks>
52 /// <exception cref="ArgumentNullException"><paramref name="website"/>または<paramref name="title"/>が<c>null</c>の場合。</exception>
53 /// <exception cref="ArgumentException"><paramref name="title"/>が空の文字列の場合。</exception>
54 public MediaWikiPage(MediaWiki website, string title, string text)
55 : base(website, title, text)
57 this.Interlanguages = new Dictionary<string, string>();
61 /// 指定されたMediaWikiの渡されたタイトルのページを作成。
63 /// <param name="website">ページが所属するウェブサイト。</param>
64 /// <param name="title">ページタイトル。</param>
65 /// <remarks>ページの本文, タイムスタンプ, URIには<c>null</c>を設定。</remarks>
66 /// <exception cref="ArgumentNullException"><paramref name="website"/>または<paramref name="title"/>が<c>null</c>の場合。</exception>
67 /// <exception cref="ArgumentException"><paramref name="title"/>が空の文字列の場合。</exception>
68 public MediaWikiPage(MediaWiki website, string title)
69 : base(website, title)
71 this.Interlanguages = new Dictionary<string, string>();
81 public new MediaWiki Website
85 return base.Website as MediaWiki;
98 /// get時に値が設定されていない場合、サーバーから本文を取得する。
99 /// ページの取得に失敗した場合(通信エラーなど)は、その状況に応じた例外を投げる。
101 public override string Text
105 if (base.Text == null)
107 this.SetPageBodyAndTimestamp();
123 /// get時に値が設定されていない場合、サーバーからタイムスタンプを取得する。
124 /// ページの取得に失敗した場合(通信エラーなど)は、その状況に応じた例外を投げる。
126 public override DateTime? Timestamp
130 if (base.Timestamp == null)
132 this.SetPageBodyAndTimestamp();
135 return base.Timestamp;
140 base.Timestamp = value;
147 public string Redirect
156 protected IDictionary<string, string> Interlanguages
167 /// APIから取得した言語間リンク情報から、ページを取得する。
169 /// <param name="website">ページが所属するウェブサイト。</param>
170 /// <param name="uri">クエリーを取得したURI。</param>
171 /// <param name="query">
172 /// MediaWiki APIから取得した言語間リンク情報。
173 /// <c>pages/page (ns="0"), redirects/r</c> を使用する。
175 /// <returns>言語間リンク情報から取得したページ。</returns>
176 /// <exception cref="InvalidDataException">XMLのフォーマットが想定外。</exception>
177 /// <exception cref="NullReferenceException">XMLのフォーマットが想定外。</exception>
178 /// <exception cref="FileNotFoundException">ページが存在しない場合。</exception>
179 public static MediaWikiPage GetFromQuery(MediaWiki website, Uri uri, XElement query)
182 // ※ この問い合わせでは、ページが無い場合も要素自体は毎回ある模様
183 // 一件しか返らないはずなので先頭データを対象とする
187 pe = (from pages in query.Elements("pages")
188 from n in pages.Elements("page")
191 catch (InvalidOperationException)
193 throw new InvalidOperationException("parse failed : pages/page element is not found");
197 if (pe.Attribute("missing") != null)
199 // missing属性が存在する場合、ページ無し
200 throw new FileNotFoundException("page not found");
203 // ページ名、URI、リダイレクト、言語間リンク情報を詰めたオブジェクトを返す
204 // ※ ページ名以外はデータがあれば格納
205 MediaWikiPage page = new MediaWikiPage(website, pe.Attribute("title").Value);
207 var le = from links in pe.Elements("langlinks")
208 from n in links.Elements("ll")
210 foreach (var ll in le)
212 page.Interlanguages.Add(ll.Attribute("lang").Value, ll.Value);
215 var re = from redirects in query.Elements("redirects")
216 from n in redirects.Elements("r")
218 foreach (var r in re)
220 page.Redirect = r.Attribute("from").Value;
231 /// 指定された言語コードへの言語間リンクを返す。
233 /// <param name="code">言語コード。</param>
234 /// <returns>言語間リンク。見つからない場合は<c>null</c>。</returns>
235 public virtual string GetInterlanguage(string code)
238 string interlanguage;
239 if (this.Interlanguages.TryGetValue(code, out interlanguage))
241 return interlanguage;
248 /// ページがリダイレクトかをチェック。
250 /// <returns><c>true</c> リダイレクト。</returns>
251 public bool IsRedirect()
253 return this.Redirect != null;
257 /// ページがテンプレートかをチェック。
259 /// <returns><c>true</c> テンプレート。</returns>
260 public bool IsTemplate()
262 // ページ名がカテゴリー(Category:等で始まる)かをチェック
263 return this.IsNamespacePage(this.Website.TemplateNamespace);
269 /// <returns><c>true</c> カテゴリー。</returns>
270 public bool IsCategory()
272 // ページ名がカテゴリー(Category:等で始まる)かをチェック
273 return this.IsNamespacePage(this.Website.CategoryNamespace);
279 /// <returns><c>true</c> 画像。</returns>
282 // ページ名がファイル(Image:等で始まる)かをチェック
283 return this.IsNamespacePage(this.Website.FileNamespace);
287 /// ページが標準名前空間かをチェック。
289 /// <returns><c>true</c> 標準名前空間。</returns>
292 // ページ名が標準名前空間以外のなんらかの名前空間かをチェック
293 return !this.Website.IsNamespace(this.Title);
297 /// このページ内のリンクの記事名(サブページ等)を完全な記事名にする。
299 /// <param name="link">このページ内のリンク。</param>
300 /// <returns>変換した記事名。</returns>
301 public virtual string Normalize(MediaWikiLink link)
303 string title = StringUtils.DefaultString(link.Title);
304 if (link.IsSubpage())
307 title = this.NormalizeSubpage(title);
309 else if (link is MediaWikiTemplate)
311 // テンプレート関連の正規化(サブページの場合は不要)
312 title = this.NormalizeTemplate((MediaWikiTemplate)link);
323 /// ページの本文・タイムスタンプをサーバーから取得。
325 /// <remarks>ページの取得に失敗した場合(通信エラーなど)は、その状況に応じた例外を投げる。</remarks>
326 protected void SetPageBodyAndTimestamp()
328 Page body = this.Website.GetPageBodyAndTimestamp(this.Title);
329 this.Text = body.Text;
330 this.Timestamp = body.Timestamp;
335 /// ページが指定された番号の名前空間に所属するかをチェック。
337 /// <param name="id">名前空間のID。</param>
338 /// <returns>所属する場合<c>true</c>。</returns>
339 /// <remarks>大文字小文字は区別しない。</remarks>
340 protected bool IsNamespacePage(int id)
342 // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
343 int index = this.Title.IndexOf(':');
349 string title = this.Title.Remove(index);
350 IgnoreCaseSet prefixes = this.Website.Namespaces[id];
351 return prefixes != null && prefixes.Contains(title);
355 /// このページ内のリンクのサブページ形式の記事名を完全な記事名にする。
357 /// <param name="subpage">サブページ形式の記事名。</param>
358 /// <returns>変換した記事名。</returns>
359 private string NormalizeSubpage(string subpage)
361 string title = subpage;
362 if (subpage.StartsWith("/"))
364 // サブページ(子)へのリンクの場合、親の記事名を補填
365 title = this.Title + subpage;
367 else if (subpage.StartsWith("../"))
369 // サブページ(親・兄弟・おじ)へのリンクの場合、各階層の記事名を補填
370 string subtitle = subpage;
372 while (subtitle.StartsWith("../"))
376 subtitle = subtitle.Substring("../".Length);
380 string parent = this.Title;
381 for (int i = 0; i < count; i++)
383 int index = parent.LastIndexOf('/');
386 // 階層が足りない場合、補填できないので元の記事名を返す
390 parent = parent.Remove(index);
393 // 親記事名と子記事名(あれば)を結合して完了
395 if (!string.IsNullOrEmpty(subtitle))
397 title += "/" + subtitle;
401 // 末尾に / が付いている場合、表示名に関する指定なので除去
402 return title.TrimEnd('/');
406 /// このページ内のテンプレート形式のリンクの記事名を完全な記事名にする。
408 /// <param name="template">このページ内のテンプレート形式のリンク。</param>
409 /// <returns>変換した記事名。</returns>
410 private string NormalizeTemplate(MediaWikiTemplate template)
412 if (template.IsColon || this.Website.IsNamespace(template.Title)
413 || this.Website.IsMagicWord(template.Title))
415 // 標準名前空間が指定されている(先頭にコロン)
416 // または何かしらの名前空間が指定されている、
417 // またはテンプレート呼び出しではなくマジックナンバーの場合、補完不要
418 return template.Title;
421 // 補完する必要がある場合、名前空間のプレフィックス(Template等)を取得
422 string prefix = this.GetTemplatePrefix();
423 if (string.IsNullOrEmpty(prefix))
425 // 名前空間の設定が存在しない場合、何も出来ないため終了
426 return template.Title;
429 // 頭にプレフィックスを付けた記事名を返す
430 return prefix + ":" + template.Title;
434 /// テンプレート名前空間のプレフィックスを取得。
436 /// <returns>プレフィックス。取得できない場合<c>null</c></returns>
437 private string GetTemplatePrefix()
439 ISet<string> prefixes = this.Website.Namespaces[this.Website.TemplateNamespace];
440 if (prefixes != null)
442 return prefixes.FirstOrDefault();