1 // ================================================================================================
3 // MediaWikiのウェブサイト(システム)をあらわすモデルクラスソース</summary>
5 // <copyright file="MediaWiki.cs" company="honeplusのメモ帳">
6 // Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
9 // ================================================================================================
11 namespace Honememo.Wptscs.Websites
14 using System.Collections.Generic;
18 using System.Xml.Serialization;
19 using Honememo.Models;
20 using Honememo.Utilities;
21 using Honememo.Wptscs.Models;
22 using Honememo.Wptscs.Properties;
23 using Honememo.Wptscs.Utilities;
26 /// MediaWikiのウェブサイト(システム)をあらわすモデルクラスです。
28 public class MediaWiki : Website, IXmlSerializable
33 /// 名前空間情報取得用にアクセスするAPI。
35 private string metaApi;
40 private string exportPath;
45 private string redirect;
50 private int? templateNamespace;
55 private int? categoryNamespace;
60 private int? fileNamespace;
63 /// MediaWiki書式のシステム定義変数。
65 private ISet<string> magicWords;
68 /// MediaWikiの名前空間の情報。
70 private IDictionary<int, IgnoreCaseSet> namespaces;
73 /// MediaWikiのウィキ間リンクのプレフィックス情報。
75 private IgnoreCaseSet interwikiPrefixs;
78 /// MediaWikiのウィキ間リンクのプレフィックス情報(APIから取得した値と設定値の集合)。
80 private IgnoreCaseSet interwikiPrefixCaches;
83 /// <see cref="InitializeByMetaApi"/>同期用ロックオブジェクト。
85 private object lockLoadMetaApi = new object();
88 /// Template:Documentation(言語間リンク等を別ページに記述するためのテンプレート)に相当するページ名。
90 private IList<string> documentationTemplates = new List<string>();
97 /// 指定された言語, サーバーのMediaWikiを表すインスタンスを作成。
99 /// <param name="language">ウェブサイトの言語。</param>
100 /// <param name="location">ウェブサイトの場所。</param>
101 /// <exception cref="ArgumentNullException"><paramref name="language"/>または<paramref name="location"/>が<c>null</c>の場合。</exception>
102 /// <exception cref="ArgumentException"><paramref name="location"/>が空の文字列の場合。</exception>
103 public MediaWiki(Language language, string location)
104 : base(language, location)
109 /// 指定された言語のWikipediaを表すインスタンスを作成。
111 /// <param name="language">ウェブサイトの言語。</param>
112 /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
113 public MediaWiki(Language language)
115 // 親で初期化していないのは、languageのnullチェックの前にnull参照でエラーになってしまうから
116 this.Language = language;
117 this.Location = string.Format(Settings.Default.WikipediaLocation, language.Code);
121 /// 空のインスタンスを作成(シリアライズ or 拡張用)。
123 protected MediaWiki()
129 #region 設定ファイルに初期値を持つプロパティ
132 /// MediaWikiメタ情報取得用にアクセスするAPI。
134 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
135 public string MetaApi
139 if (string.IsNullOrEmpty(this.metaApi))
141 return Settings.Default.MediaWikiMetaApi;
149 this.metaApi = value;
154 /// 記事のXMLデータが存在するパス。
156 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
157 public string ExportPath
161 if (string.IsNullOrEmpty(this.exportPath))
163 return Settings.Default.MediaWikiExportPath;
166 return this.exportPath;
171 this.exportPath = value;
178 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
179 public string Redirect
183 if (string.IsNullOrEmpty(this.redirect))
185 return Settings.Default.MediaWikiRedirect;
188 return this.redirect;
193 this.redirect = value;
198 /// テンプレートの名前空間を示す番号。
200 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
201 public int TemplateNamespace
205 return this.templateNamespace ?? Settings.Default.MediaWikiTemplateNamespace;
210 this.templateNamespace = value;
217 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
218 public int CategoryNamespace
222 return this.categoryNamespace ?? Settings.Default.MediaWikiCategoryNamespace;
227 this.categoryNamespace = value;
234 /// <remarks>値が指定されていない場合、デフォルト値を返す。</remarks>
235 public int FileNamespace
239 return this.fileNamespace ?? Settings.Default.MediaWikiFileNamespace;
244 this.fileNamespace = value;
249 /// MediaWiki書式のシステム定義変数。
252 /// 値が指定されていない場合、デフォルト値を返す。
255 public ISet<string> MagicWords
259 if (this.magicWords == null)
261 // ※ 初期値は http://www.mediawiki.org/wiki/Help:Magic_words 等を参考に設定。
262 // APIからも取得できるが、2012年2月現在 #expr でなければ認識されないものが
263 // exprで返ってきたりとアプリで使うには情報が足りないため人力で対応。
264 return new HashSet<string>(Settings.Default.MediaWikiMagicWords.Cast<string>());
267 return this.magicWords;
272 this.magicWords = value;
278 #region サーバーから値を取得するプロパティ
281 /// MediaWikiの名前空間の情報。
284 /// サーバーから情報を取得。大文字小文字を区別しない。
286 public IDictionary<int, IgnoreCaseSet> Namespaces
290 // 値が設定されていない場合、サーバーから取得して初期化する
291 // ※ コンストラクタ等で初期化していないのは、通信の準備が整うまで行えないため
292 // ※ 余計なロック・通信をしないよう、ロックの前後に値のチェックを行う
293 if (this.namespaces != null)
295 return this.namespaces;
298 lock (this.lockLoadMetaApi)
300 if (this.namespaces != null)
302 return this.namespaces;
305 this.InitializeByMetaApi();
308 return this.namespaces;
313 this.namespaces = value;
318 /// MediaWikiのウィキ間リンクのプレフィックス情報。
321 /// 値が設定されていない場合デフォルト値とサーバーから、
322 /// 設定されている場合その内容とサーバーから取得した情報を使用する。
325 public IgnoreCaseSet InterwikiPrefixs
329 // 値が準備されていない場合、サーバーと設定ファイルから取得して初期化する
330 // ※ コンストラクタ等で初期化していないのは、通信の準備が整うまで行えないため
331 // ※ 余計なロック・通信をしないよう、ロックの前後に値のチェックを行う
332 if (this.interwikiPrefixCaches != null)
334 return this.interwikiPrefixCaches;
337 lock (this.lockLoadMetaApi)
339 if (this.interwikiPrefixCaches != null)
341 return this.interwikiPrefixCaches;
344 this.InitializeByMetaApi();
347 return this.interwikiPrefixCaches;
353 this.interwikiPrefixs = value;
354 this.interwikiPrefixCaches = null;
363 /// Template:Documentation(言語間リンク等を別ページに記述するためのテンプレート)に相当するページ名。
365 /// <remarks>空の場合、その言語版にはこれに相当する機能は無いものとして扱う。</remarks>
366 public IList<string> DocumentationTemplates
370 return this.documentationTemplates;
378 // ※ 例外にしてもよいが、このクラスにはnullで初期化としている
379 // プロパティが多数存在するので、動きをあわせる
380 value = new List<string>();
383 this.documentationTemplates = value;
388 /// Template:Documentationで指定が無い場合に参照するページ名。
391 /// ほとんどの言語では[[/Doc]]の模様。
392 /// 空の場合、明示的な指定が無い場合は参照不能として扱う。
394 public string DocumentationTemplateDefaultPage
401 /// Template:仮リンク(他言語へのリンク)で書式化するためのフォーマット。
403 /// <remarks>空の場合、その言語版にはこれに相当する機能は無いor使用しないものとして扱う。</remarks>
404 public string LinkInterwikiFormat
411 /// Template:Langで書式化するためのフォーマット。
413 /// <remarks>空の場合、その言語版にはこれに相当する機能は無いor使用しないものとして扱う。</remarks>
414 public string LangFormat
421 /// 言語名の記事が存在するようなサイトか?
424 /// そうした記事が存在する場合<c>true</c>。
425 /// Wikipediaには[[日本語]]といった記事が存在するが、Wikitravelであればそうした記事は存在し得ない。
426 /// 言語名をリンクにするかといった判断に用いる。
428 public bool HasLanguagePage
441 /// <param name="title">ページタイトル。</param>
442 /// <returns>取得したページ。</returns>
443 /// <exception cref="FileNotFoundException">ページが存在しない場合。</exception>
444 /// <exception cref="EndPeriodException">末尾がピリオドのページの場合(既知の不具合への対応)。</exception>
445 /// <remarks>ページの取得に失敗した場合(通信エラーなど)は、その状況に応じた例外を投げる。</remarks>
446 public override Page GetPage(string title)
448 // fileスキームの場合、記事名からファイルに使えない文字をエスケープ
449 // ※ 仕組み的な処理はWebsite側に置きたいが、向こうではタイトルだけを抽出できないので
450 string escapeTitle = title;
451 if (new Uri(this.Location).IsFile)
453 escapeTitle = FormUtils.ReplaceInvalidFileNameChars(title);
457 Uri uri = new Uri(new Uri(this.Location), StringUtils.FormatDollarVariable(this.ExportPath, escapeTitle));
458 if (uri.OriginalString.EndsWith("."))
460 // 末尾がピリオドのページが取得できない既知の不具合への暫定対応
461 // 対処方法が不明なため、せめて例外を投げて検知する
462 throw new EndPeriodException(title + " is not suppoted");
465 // ページのXMLデータをMediaWikiサーバーから取得
466 XmlDocument xml = new XmlDocument();
469 using (Stream reader = this.WebProxy.GetStream(uri))
474 catch (System.Net.WebException e)
476 // 404エラーによるページ取得失敗は詰め替えて返す
477 if (this.IsNotFound(e))
479 throw new FileNotFoundException("page not found", e);
485 // ルートエレメントまで取得し、フォーマットをチェック
486 XmlElement rootElement = xml["mediawiki"];
487 if (rootElement == null)
489 // XMLは取得できたが空 or フォーマットが想定外
490 throw new InvalidDataException("parse failed : mediawiki element is not found");
494 XmlElement pageElement = rootElement["page"];
495 if (pageElement == null)
498 throw new FileNotFoundException("page not found");
502 // ※ 一応、各項目が無くても動作するようにする
503 string pageTitle = XmlUtils.InnerText(pageElement["title"], title);
505 DateTime? time = null;
506 XmlElement revisionElement = pageElement["revision"];
507 if (revisionElement != null)
509 text = XmlUtils.InnerText(revisionElement["text"], null);
510 XmlElement timeElement = revisionElement["timestamp"];
511 if (timeElement != null)
513 time = new DateTime?(DateTime.Parse(timeElement.InnerText));
518 return new MediaWikiPage(this, pageTitle, text, time, uri);
522 /// 指定された文字列がMediaWikiのシステム変数に相当かを判定。
524 /// <param name="text">チェックする文字列。</param>
525 /// <returns>システム変数に相当する場合<c>true</c>。</returns>
526 /// <remarks>大文字小文字は区別する。</remarks>
527 public bool IsMagicWord(string text)
529 // {{CURRENTYEAR}}や{{ns:1}}みたいなパターンがある
530 string s = StringUtils.DefaultString(text);
531 foreach (string variable in this.MagicWords)
533 if (s == variable || s.StartsWith(variable + ":"))
543 /// 指定されたリンク文字列がMediaWikiのウィキ間リンクかを判定。
545 /// <param name="link">チェックするリンク文字列。</param>
546 /// <returns>ウィキ間リンクに該当する場合<c>true</c>。</returns>
547 /// <remarks>大文字小文字は区別しない。</remarks>
548 public bool IsInterwiki(string link)
550 // ※ ウィキ間リンクには入れ子もあるが、ここでは意識する必要はない
551 string s = StringUtils.DefaultString(link);
553 // 名前空間と被る場合はそちらが優先、ウィキ間リンクと判定しない
554 if (this.IsNamespace(link))
559 // 文字列がいずれかのウィキ間リンクのプレフィックスで始まるか
560 int index = s.IndexOf(':');
566 return this.InterwikiPrefixs.Contains(s.Remove(index));
570 /// 指定されたリンク文字列がMediaWikiのいずれかの名前空間に属すかを判定。
572 /// <param name="link">チェックするリンク文字列。</param>
573 /// <returns>いずれかの名前空間に該当する場合<c>true</c>。</returns>
574 /// <remarks>大文字小文字は区別しない。</remarks>
575 public bool IsNamespace(string link)
577 // 文字列がいずれかの名前空間のプレフィックスで始まるか
578 string s = StringUtils.DefaultString(link);
579 int index = s.IndexOf(':');
585 string prefix = s.Remove(index);
586 foreach (IgnoreCaseSet prefixes in this.Namespaces.Values)
588 if (prefixes.Contains(prefix))
598 /// <see cref="LinkInterwikiFormat"/> を渡された記事名, 言語, 他言語版記事名, 表示名で書式化した文字列を返す。
600 /// <param name="title">記事名。</param>
601 /// <param name="lang">言語。</param>
602 /// <param name="langTitle">他言語版記事名。</param>
603 /// <param name="label">表示名。</param>
604 /// <returns>書式化した文字列。<see cref="LinkInterwikiFormat"/>が未設定の場合<c>null</c>。</returns>
605 public string FormatLinkInterwiki(string title, string lang, string langTitle, string label)
607 if (string.IsNullOrEmpty(this.LinkInterwikiFormat))
612 return StringUtils.FormatDollarVariable(this.LinkInterwikiFormat, title, lang, langTitle, label);
616 /// <see cref="LangFormat"/> を渡された言語, 文字列で書式化した文字列を返す。
618 /// <param name="lang">言語。</param>
619 /// <param name="text">文字列。</param>
620 /// <returns>書式化した文字列。<see cref="LangFormat"/>が未設定の場合<c>null</c>。</returns>
622 /// この<paramref name="lang"/>と<see cref="Language"/>のコードは、厳密には一致しないケースがあるが
623 /// (例、simple→en)、2012年2月現在の実装ではそこまで正確さは要求していない。
625 public string FormatLang(string lang, string text)
627 if (string.IsNullOrEmpty(this.LangFormat))
632 return StringUtils.FormatDollarVariable(this.LangFormat, lang, text);
637 #region XMLシリアライズ用メソッド
640 /// シリアライズするXMLのスキーマ定義を返す。
642 /// <returns>XML表現を記述する<see cref="System.Xml.Schema.XmlSchema"/>。</returns>
643 public System.Xml.Schema.XmlSchema GetSchema()
649 /// XMLからオブジェクトをデシリアライズする。
651 /// <param name="reader">デシリアライズ元の<see cref="XmlReader"/></param>
652 public void ReadXml(XmlReader reader)
654 XmlDocument xml = new XmlDocument();
658 // ※ 以下、基本的に無かったらNGの部分はいちいちチェックしない。例外飛ばす
659 XmlElement siteElement = xml.DocumentElement;
660 this.Location = siteElement.SelectSingleNode("Location").InnerText;
662 using (XmlReader r = XmlReader.Create(
663 new StringReader(siteElement.SelectSingleNode("Language").OuterXml), reader.Settings))
665 this.Language = new XmlSerializer(typeof(Language)).Deserialize(r) as Language;
668 this.MetaApi = XmlUtils.InnerText(siteElement.SelectSingleNode("MetaApi"));
669 this.ExportPath = XmlUtils.InnerText(siteElement.SelectSingleNode("ExportPath"));
670 this.Redirect = XmlUtils.InnerText(siteElement.SelectSingleNode("Redirect"));
673 if (int.TryParse(XmlUtils.InnerText(siteElement.SelectSingleNode("TemplateNamespace")), out namespaceId))
675 this.TemplateNamespace = namespaceId;
678 if (int.TryParse(XmlUtils.InnerText(siteElement.SelectSingleNode("CategoryNamespace")), out namespaceId))
680 this.CategoryNamespace = namespaceId;
683 if (int.TryParse(XmlUtils.InnerText(siteElement.SelectSingleNode("FileNamespace")), out namespaceId))
685 this.FileNamespace = namespaceId;
689 ISet<string> variables = new HashSet<string>();
690 foreach (XmlNode variableNode in siteElement.SelectNodes("MagicWords/Variable"))
692 variables.Add(variableNode.InnerText);
695 if (variables.Count > 0)
698 this.MagicWords = variables;
702 IgnoreCaseSet prefixs = new IgnoreCaseSet();
703 foreach (XmlNode prefixNode in siteElement.SelectNodes("InterwikiPrefixs/Prefix"))
705 prefixs.Add(prefixNode.InnerText);
708 if (prefixs.Count > 0)
711 this.InterwikiPrefixs = prefixs;
714 // Template:Documentationの設定
715 this.DocumentationTemplates = new List<string>();
716 foreach (XmlNode docNode in siteElement.SelectNodes("DocumentationTemplates/DocumentationTemplate"))
718 this.DocumentationTemplates.Add(docNode.InnerText);
719 XmlElement docElement = docNode as XmlElement;
720 if (docElement != null)
722 // ※ XML上DefaultPageはテンプレートごとに異なる値を持てるが、
723 // そうした例を見かけたことがないため、代表で一つの値のみ使用
724 // (複数値が持てるのも、リダイレクトが存在するためその対策として)
725 this.DocumentationTemplateDefaultPage = docElement.GetAttribute("DefaultPage");
729 this.LinkInterwikiFormat = XmlUtils.InnerText(siteElement.SelectSingleNode("LinkInterwikiFormat"));
730 this.LangFormat = XmlUtils.InnerText(siteElement.SelectSingleNode("LangFormat"));
731 bool hasLanguagePage;
732 if (bool.TryParse(XmlUtils.InnerText(siteElement.SelectSingleNode("HasLanguagePage")), out hasLanguagePage))
734 this.HasLanguagePage = hasLanguagePage;
739 /// オブジェクトをXMLにシリアライズする。
741 /// <param name="writer">シリアライズ先の<see cref="XmlWriter"/></param>
742 public void WriteXml(XmlWriter writer)
744 writer.WriteElementString("Location", this.Location);
745 new XmlSerializer(this.Language.GetType()).Serialize(writer, this.Language);
748 // ※ 設定ファイルに初期値を持つものは、プロパティではなく値から出力
749 writer.WriteElementString("MetaApi", this.metaApi);
750 writer.WriteElementString("ExportPath", this.exportPath);
751 writer.WriteElementString("Redirect", this.redirect);
752 writer.WriteElementString(
754 this.templateNamespace.HasValue ? this.templateNamespace.ToString() : string.Empty);
755 writer.WriteElementString(
757 this.templateNamespace.HasValue ? this.categoryNamespace.ToString() : string.Empty);
758 writer.WriteElementString(
760 this.templateNamespace.HasValue ? this.fileNamespace.ToString() : string.Empty);
763 writer.WriteStartElement("MagicWords");
764 if (this.magicWords != null)
766 foreach (string variable in this.magicWords)
768 writer.WriteElementString("Variable", variable);
773 writer.WriteEndElement();
774 writer.WriteStartElement("InterwikiPrefixs");
775 if (this.interwikiPrefixs != null)
777 foreach (string prefix in this.interwikiPrefixs)
779 writer.WriteElementString("Prefix", prefix);
783 // Template:Documentationの設定
784 writer.WriteEndElement();
785 writer.WriteStartElement("DocumentationTemplates");
786 foreach (string doc in this.DocumentationTemplates)
788 writer.WriteStartElement("DocumentationTemplate");
789 writer.WriteAttributeString("DefaultPage", this.DocumentationTemplateDefaultPage);
790 writer.WriteValue(doc);
791 writer.WriteEndElement();
794 writer.WriteEndElement();
795 writer.WriteElementString("LinkInterwikiFormat", this.LinkInterwikiFormat);
796 writer.WriteElementString("LangFormat", this.LangFormat);
797 writer.WriteElementString("HasLanguagePage", this.HasLanguagePage.ToString());
805 /// <see cref="MetaApi"/>を使用してサーバーからメタ情報を取得する。
807 /// <exception cref="System.Net.WebException">通信エラー等が発生した場合。</exception>
808 /// <exception cref="InvalidDataException">APIから取得した情報が想定外のフォーマットの場合。</exception>
809 private void InitializeByMetaApi()
811 // APIのXMLデータをMediaWikiサーバーから取得
812 XmlDocument xml = new XmlDocument();
813 using (Stream reader = this.WebProxy.GetStream(new Uri(new Uri(this.Location), this.MetaApi)))
818 // ルートエレメントまで取得し、フォーマットをチェック
819 XmlElement rootElement = xml["api"];
820 if (rootElement == null)
822 // XMLは取得できたが空 or フォーマットが想定外
823 throw new InvalidDataException("parse failed : api element is not found");
827 XmlElement queryElement = rootElement["query"];
828 if (queryElement == null)
831 throw new InvalidDataException("parse failed : query element is not found");
834 // クエリー内のネームスペース・ネームスペースエイリアス、ウィキ間リンクを読み込み
835 this.namespaces = this.LoadNamespacesElement(queryElement);
836 this.interwikiPrefixCaches = this.LoadInterwikimapElement(queryElement);
838 // ウィキ間リンクは読み込んだ後に設定ファイルorプロパティの分をマージ
839 // ※ 設定ファイルの初期値は下記より作成。
840 // http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/maintenance/interwiki.sql?view=markup
841 // APIに加えて設定ファイルも持っているのは、2012年2月現在APIから返ってこない
842 // 項目(wikipediaとかcommonsとか)が存在するため。
843 this.interwikiPrefixCaches.UnionWith(
844 this.interwikiPrefixs == null
845 ? Settings.Default.MediaWikiInterwikiPrefixs.Cast<string>()
846 : this.interwikiPrefixs);
850 /// <see cref="MetaApi"/>から取得したXMLのうち、ネームスペースに関する部分を読み込む。
852 /// <param name="queryElement">APIから取得したXML要素のうち、api→query部分のエレメント。</param>
853 /// <returns>読み込んだネームスペース情報。</returns>
854 /// <exception cref="InvalidDataException">namespacesエレメントが存在しない場合。</exception>
855 private IDictionary<int, IgnoreCaseSet> LoadNamespacesElement(XmlElement queryElement)
857 // ネームスペースブロックを取得、ネームスペースブロックまでは必須
858 XmlElement namespacesElement = queryElement["namespaces"];
859 if (namespacesElement == null)
862 throw new InvalidDataException("parse failed : namespaces element is not found");
866 IDictionary<int, IgnoreCaseSet> namespaces = new Dictionary<int, IgnoreCaseSet>();
867 foreach (XmlNode node in namespacesElement.ChildNodes)
869 XmlElement namespaceElement = node as XmlElement;
870 if (namespaceElement != null)
874 int id = decimal.ToInt16(decimal.Parse(namespaceElement.GetAttribute("id")));
875 IgnoreCaseSet values = new IgnoreCaseSet();
876 values.Add(namespaceElement.InnerText);
877 namespaces[id] = values;
880 string canonical = namespaceElement.GetAttribute("canonical");
881 if (!string.IsNullOrEmpty(canonical))
883 values.Add(canonical);
888 // キャッチしているのは、万が一想定外の書式が返された場合に、完璧に動かなくなるのを防ぐため
889 System.Diagnostics.Debug.WriteLine("MediaWiki.LoadNamespacesElement > 例外発生 : " + e);
894 // ネームスペースエイリアスブロックを取得、無い場合も想定
895 XmlElement aliasesElement = queryElement["namespacealiases"];
896 if (aliasesElement != null)
899 foreach (XmlNode node in aliasesElement.ChildNodes)
901 XmlElement namespaceElement = node as XmlElement;
902 if (namespaceElement != null)
906 int id = decimal.ToInt16(decimal.Parse(namespaceElement.GetAttribute("id")));
907 ISet<string> values = new HashSet<string>();
908 if (namespaces.ContainsKey(id))
910 values = namespaces[id];
913 values.Add(namespaceElement.InnerText);
917 // キャッチしているのは、万が一想定外の書式が返された場合に、完璧に動かなくなるのを防ぐため
918 System.Diagnostics.Debug.WriteLine("MediaWiki.LoadNamespacesElement > 例外発生 : " + e);
928 /// <see cref="MetaApi"/>から取得したXMLのうち、ウィキ間リンクに関する部分を読み込む。
930 /// <param name="queryElement">APIから取得したXML要素のうち、api→query部分のエレメント。</param>
931 /// <returns>読み込んだウィキ間リンク情報。</returns>
932 /// <exception cref="InvalidDataException">interwikimapエレメントが存在しない場合。</exception>
933 private IgnoreCaseSet LoadInterwikimapElement(XmlElement queryElement)
935 // ウィキ間リンクブロックを取得、ウィキ間リンクブロックまでは必須
936 XmlElement interwikimapElement = queryElement["interwikimap"];
937 if (interwikimapElement == null)
940 throw new InvalidDataException("parse failed : interwikimap element is not found");
944 IgnoreCaseSet interwikiPrefixs = new IgnoreCaseSet();
945 foreach (XmlNode node in interwikimapElement.ChildNodes)
947 XmlElement interwikiElement = node as XmlElement;
948 if (interwikiElement != null)
950 string prefix = interwikiElement.GetAttribute("prefix");
951 if (!string.IsNullOrWhiteSpace(prefix))
953 interwikiPrefixs.Add(prefix);
958 return interwikiPrefixs;