1 // ================================================================================================
3 // MediaWikiのウェブサイト(システム)をあらわすモデルクラスソース</summary>
5 // <copyright file="MediaWiki.cs" company="honeplusのメモ帳">
6 // Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
9 // ================================================================================================
11 namespace Honememo.Wptscs.Models
14 using System.Collections.Generic;
18 using System.Xml.Serialization;
19 using Honememo.Utilities;
20 using Honememo.Wptscs.Properties;
23 /// MediaWikiのウェブサイト(システム)をあらわすモデルクラスです。
25 public class MediaWiki : Website, IXmlSerializable
30 /// 名前空間情報取得用にアクセスするAPI。
32 private string namespacePath;
37 private string exportPath;
42 private string redirect;
47 private int? templateNamespace;
52 private int? categoryNamespace;
57 private int? fileNamespace;
60 /// Wikipedia書式のシステム定義変数。
62 /// <remarks>初期値は http://www.mediawiki.org/wiki/Help:Magic_words を参照</remarks>
63 private IList<string> magicWords;
66 /// MediaWikiの名前空間の情報。
68 private IDictionary<int, IList<string>> namespaces = new Dictionary<int, IList<string>>();
75 /// コンストラクタ(MediaWiki全般)。
77 /// <param name="language">ウェブサイトの言語。</param>
78 /// <param name="location">ウェブサイトの場所。</param>
79 public MediaWiki(Language language, string location)
82 this.Language = language;
83 this.Location = location;
87 /// コンストラクタ(Wikipedia用)。
89 /// <param name="language">ウェブサイトの言語。</param>
90 public MediaWiki(Language language)
93 // ※ オーバーロードメソッドを呼んでいないのは、languageがnullのときに先にエラーになるから
94 this.Language = language;
95 this.Location = String.Format(Settings.Default.WikipediaLocation, language.Code);
99 /// コンストラクタ(シリアライズ or 拡張用)。
101 protected MediaWiki()
107 #region 設定ファイルに初期値を持つプロパティ
110 /// MediaWiki名前空間情報取得用にアクセスするAPI。
112 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
113 public string NamespacePath
117 if (String.IsNullOrEmpty(this.namespacePath))
119 return Settings.Default.MediaWikiNamespacePath;
122 return this.namespacePath;
127 this.namespacePath = value;
132 /// 記事のXMLデータが存在するパス。
134 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
135 public string ExportPath
139 if (String.IsNullOrEmpty(this.exportPath))
141 return Settings.Default.MediaWikiExportPath;
144 return this.exportPath;
149 this.exportPath = value;
156 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
157 public string Redirect
161 if (String.IsNullOrEmpty(this.redirect))
163 return Settings.Default.MediaWikiRedirect;
166 return this.redirect;
171 this.redirect = value;
176 /// テンプレートの名前空間を示す番号。
178 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
179 public int TemplateNamespace
183 return this.templateNamespace ?? Settings.Default.MediaWikiTemplateNamespace;
188 this.templateNamespace = value;
195 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
196 public int CategoryNamespace
200 return this.categoryNamespace ?? Settings.Default.MediaWikiCategoryNamespace;
205 this.categoryNamespace = value;
212 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
213 public int FileNamespace
217 return this.fileNamespace ?? Settings.Default.MediaWikiFileNamespace;
222 this.fileNamespace = value;
227 /// Wikipedia書式のシステム定義変数。
229 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
230 public IList<string> MagicWords
234 if (this.magicWords == null)
236 string[] w = new string[Settings.Default.MediaWikiMagicWords.Count];
237 Settings.Default.MediaWikiMagicWords.CopyTo(w, 0);
241 return this.magicWords;
246 this.magicWords = value;
255 /// MediaWikiの名前空間の情報。
257 /// <remarks>値が指定されていない場合、サーバーから情報を取得。</remarks>
258 public IDictionary<int, IList<string>> Namespaces
262 lock (this.namespaces)
264 // 値が設定されていない場合、サーバーから取得して初期化する
265 // ※ コンストラクタ等で初期化していないのは、通信の準備が整うまで行えないため
266 // ※ MagicWordsがnullでこちらが空で若干条件が違うのは、あちらは設定ファイルに
267 // 保存する設定だが、こちらは設定ファイルに保存しない基本的に読み込み用の設定だから。
268 if (this.namespaces.Count > 0)
270 return this.namespaces;
273 // APIのXMLデータをMediaWikiサーバーから取得
274 XmlDocument xml = new XmlDocument();
275 using (Stream reader = this.GetStream(new Uri(new Uri(this.Location), this.NamespacePath)))
280 // ルートエレメントまで取得し、フォーマットをチェック
281 XmlElement rootElement = xml["api"];
282 if (rootElement == null)
284 // XMLは取得できたが空 or フォーマットが想定外
285 throw new InvalidDataException("parse failed");
289 XmlElement queryElement = rootElement["query"];
290 if (queryElement == null)
293 throw new InvalidDataException("parse failed");
296 // ネームスペースブロックを取得、ネームスペースブロックまでは必須
297 XmlElement namespacesElement = queryElement["namespaces"];
298 if (namespacesElement == null)
301 throw new InvalidDataException("parse failed");
305 foreach (XmlNode node in namespacesElement.ChildNodes)
307 XmlElement namespaceElement = node as XmlElement;
308 if (namespaceElement != null)
312 int id = Decimal.ToInt16(Decimal.Parse(namespaceElement.GetAttribute("id")));
313 IList<string> values = new List<string>();
314 values.Add(namespaceElement.InnerText);
315 this.namespaces[id] = values;
318 string canonical = namespaceElement.GetAttribute("canonical");
319 if (!String.IsNullOrEmpty(canonical))
321 values.Add(canonical);
326 // キャッチしているのは、万が一想定外の書式が返された場合に、完璧に動かなくなるのを防ぐため
327 System.Diagnostics.Debug.WriteLine("MediaWiki.Namespaces > 例外発生 : " + e);
332 // ネームスペースエイリアスブロックを取得、無い場合も想定
333 XmlElement aliasesElement = queryElement["namespacealiases"];
334 if (aliasesElement != null)
337 foreach (XmlNode node in aliasesElement.ChildNodes)
339 XmlElement namespaceElement = node as XmlElement;
340 if (namespaceElement != null)
344 int id = Decimal.ToInt16(Decimal.Parse(namespaceElement.GetAttribute("id")));
345 IList<string> values = new List<string>();
346 if (this.namespaces.ContainsKey(id))
348 values = this.namespaces[id];
351 values.Add(namespaceElement.InnerText);
355 // キャッチしているのは、万が一想定外の書式が返された場合に、完璧に動かなくなるのを防ぐため
356 System.Diagnostics.Debug.WriteLine("MediaWiki.Namespaces > 例外発生 : " + e);
363 return this.namespaces;
368 // ※必須な情報が設定されていない場合、ArgumentNullExceptionを返す
371 throw new ArgumentNullException("namespaces");
374 this.namespaces = value;
379 /// Template:Documentation(言語間リンク等を別ページに記述するためのテンプレート)に相当するページ名。
381 /// <remarks>空の場合、その言語版にはこれに相当する機能は無いものとして扱う。</remarks>
382 public string DocumentationTemplate
389 /// Template:Documentationで指定が無い場合に参照するページ名。
392 /// ほとんどの言語では[[/Doc]]の模様。
393 /// 空の場合、明示的な指定が無い場合は参照不能として扱う。
395 public string DocumentationTemplateDefaultPage
408 /// <param name="title">ページタイトル。</param>
409 /// <returns>取得したページ。</returns>
410 /// <remarks>取得できない場合(通信エラーなど)は例外を投げる。</remarks>
411 public override Page GetPage(string title)
413 // & 等の特殊文字をデコード
414 // ※ 本当は呼び元側ですべき処理の気がするが、現状手ごろな場所が無いので
415 string decodeTitle = HttpUtility.HtmlDecode(title);
417 // fileスキームの場合、記事名からファイルに使えない文字をエスケープ
418 // ※ 仕組み的な処理はWebsite側に置きたいが、向こうではタイトルだけを抽出できないので
419 string escapeTitle = decodeTitle;
420 if (new Uri(this.Location).Scheme == "file")
422 escapeTitle = FormUtils.ReplaceInvalidFileNameChars(title);
425 // ページのXMLデータをMediaWikiサーバーから取得
426 XmlDocument xml = new XmlDocument();
427 using (Stream reader = this.GetStream(
428 new Uri(new Uri(this.Location), String.Format(this.ExportPath, escapeTitle))))
433 // ルートエレメントまで取得し、フォーマットをチェック
434 XmlElement rootElement = xml["mediawiki"];
435 if (rootElement == null)
437 // XMLは取得できたが空 or フォーマットが想定外
438 throw new InvalidDataException("parse failed");
442 XmlElement pageElement = rootElement["page"];
443 if (pageElement == null)
446 throw new FileNotFoundException("page not found");
450 // ※ 一応、各項目が無くても動作するようにする
451 string pageTitle = XmlUtils.InnerText(pageElement["title"], title);
453 DateTime? time = null;
454 XmlElement revisionElement = pageElement["revision"];
455 if (revisionElement != null)
457 text = XmlUtils.InnerText(revisionElement["text"], null);
458 XmlElement timeElement = revisionElement["timestamp"];
459 if (timeElement != null)
461 time = new DateTime?(DateTime.Parse(timeElement.InnerText));
466 return new MediaWikiPage(this, pageTitle, text, time);
470 /// 指定された文字列がWikipediaのシステム変数に相当かを判定。
472 /// <param name="text">チェックする文字列。</param>
473 /// <returns><c>true</c> システム変数に相当。</returns>
474 public bool IsMagicWord(string text)
476 string s = text != null ? text : String.Empty;
478 // {{CURRENTYEAR}}や{{ns:1}}みたいなパターンがある
479 foreach (string variable in this.MagicWords)
481 if (s == variable || s.StartsWith(variable + ":"))
492 #region XMLシリアライズ用メソッド
495 /// シリアライズするXMLのスキーマ定義を返す。
497 /// <returns>XML表現を記述するXmlSchema。</returns>
498 public System.Xml.Schema.XmlSchema GetSchema()
504 /// XMLからオブジェクトをデシリアライズする。
506 /// <param name="reader">デシリアライズ元のXmlReader</param>
507 public void ReadXml(XmlReader reader)
509 XmlDocument xml = new XmlDocument();
513 // ※ 以下、基本的に無かったらNGの部分はいちいちチェックしない。例外飛ばす
514 XmlElement siteElement = xml.DocumentElement;
515 this.Location = siteElement.SelectSingleNode("Location").InnerText;
517 using (XmlReader r = XmlReader.Create(
518 new StringReader(siteElement.SelectSingleNode("Language").OuterXml), reader.Settings))
520 this.Language = new XmlSerializer(typeof(Language)).Deserialize(r) as Language;
523 this.NamespacePath = XmlUtils.InnerText(siteElement.SelectSingleNode("NamespacePath"));
524 this.ExportPath = XmlUtils.InnerText(siteElement.SelectSingleNode("ExportPath"));
525 this.Redirect = XmlUtils.InnerText(siteElement.SelectSingleNode("Redirect"));
527 string text = XmlUtils.InnerText(siteElement.SelectSingleNode("TemplateNamespace"));
528 if (!String.IsNullOrEmpty(text))
530 this.TemplateNamespace = int.Parse(text);
533 text = XmlUtils.InnerText(siteElement.SelectSingleNode("CategoryNamespace"));
534 if (!String.IsNullOrEmpty(text))
536 this.CategoryNamespace = int.Parse(text);
539 text = XmlUtils.InnerText(siteElement.SelectSingleNode("FileNamespace"));
540 if (!String.IsNullOrEmpty(text))
542 this.FileNamespace = int.Parse(text);
546 IList<string> variables = new List<string>();
547 foreach (XmlNode variableNode in siteElement.SelectNodes("MagicWords/Variable"))
549 variables.Add(variableNode.InnerText);
552 if (variables.Count > 0)
555 this.MagicWords = variables;
558 // Template:Documentationの設定
559 XmlElement docElement = siteElement.SelectSingleNode("DocumentationTemplate") as XmlElement;
560 if (docElement != null)
562 this.DocumentationTemplate = docElement.InnerText;
563 this.DocumentationTemplateDefaultPage = docElement.GetAttribute("DefaultPage");
568 /// オブジェクトをXMLにシリアライズする。
570 /// <param name="writer">シリアライズ先のXmlWriter</param>
571 public void WriteXml(XmlWriter writer)
573 writer.WriteElementString("Location", this.Location);
574 new XmlSerializer(this.Language.GetType()).Serialize(writer, this.Language);
577 // ※ 設定ファイルに初期値を持つものは、プロパティではなく値から出力
578 writer.WriteElementString("NamespacePath", this.namespacePath);
579 writer.WriteElementString("ExportPath", this.exportPath);
580 writer.WriteElementString("Redirect", this.redirect);
581 writer.WriteElementString(
583 this.templateNamespace.HasValue ? this.templateNamespace.ToString() : String.Empty);
584 writer.WriteElementString(
586 this.templateNamespace.HasValue ? this.categoryNamespace.ToString() : String.Empty);
587 writer.WriteElementString(
589 this.templateNamespace.HasValue ? this.fileNamespace.ToString() : String.Empty);
592 writer.WriteStartElement("MagicWords");
593 if (this.magicWords != null)
595 foreach (string variable in this.magicWords)
597 writer.WriteElementString("Variable", variable);
601 writer.WriteEndElement();
603 // Template:Documentationの設定は一項目で出力
604 if (!String.IsNullOrEmpty(this.DocumentationTemplate))
606 writer.WriteStartElement("DocumentationTemplate");
607 writer.WriteAttributeString("DefaultPage", this.DocumentationTemplateDefaultPage);
608 writer.WriteValue(this.DocumentationTemplate);
609 writer.WriteEndElement();