Send to your Kindle private static Dictionary<string, PageElement> elementSet = new Dictionary<string, PageElement>(); /// <summary> /// WikiText -> Elements /// </summary> /// <param name="wikitext">< > を中間データとして使用するため、PageElements系以外から呼び出すときはwikitextにHttpUtility.HtmlEncode()の出力を渡すこと。</param> /// <returns></returns> public static List<PageElement> Tokenize(PageElement parent, string wikitext, HashSet<Type> targetClasses) { //FIXME:wikitextはエスケープ済みでなければならない仕様 // Regex.Replaceの仕様上引数を渡せないので、クロージャ内で使える変数を用意 Type elementClass = null; PageElement _elementInstance = null; Match _match = null; bool matched; Func<Match, string> createElement = delegate(Match m) { matched = true; string ret; var classes = new HashSet<Type>(targetClasses); classes.Remove(elementClass); if (elementClass.GetMethod("Merge") != null) { // 連結可能Element Debug.Assert(elementClass.Name != null); if (_elementInstance != null && _elementInstance.GetType().Name == elementClass.Name && _match.Index + _match.Length == m.Index) { // 連結、既存Elementに連結 elementClass.GetMethod("Merge").Invoke(null, new object[] { parent, m, classes, _elementInstance }); //_elem = _elem; ret = ""; } else { var elementInstance = (PageElement)Assembly.GetAssembly(elementClass).CreateInstance(elementClass.FullName, false, BindingFlags.CreateInstance, null, new object[] { parent, m, classes }, null, null); elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance); _elementInstance = elementInstance; ret = "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">"; } } else { var elementInstance = (PageElement)Assembly.GetAssembly(elementClass).CreateInstance(elementClass.FullName, false, BindingFlags.CreateInstance, null, new object[] { parent, m, classes }, null, null); elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance); _elementInstance = null; ret = "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">"; } _match = m; return ret; }; Func<Match, string> createPlaintextElement = delegate(Match m) { matched = true; // Plains系オブジェクト生成 //var elementInstance = PageElements.Plain.Construct(parent, m.Groups[0].Value, targetPlainClasses); var elementInstance = PageElements.Plain.Construct(parent, m.Groups[0].Value, targetClasses); if (elementInstance != null) { elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance); return "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">"; } else { return ""; } }; var elements = new List<PageElement>(); var plugins = new HashSet<Type>(targetClasses); do { // 置き換えが起きるたびに状況が変わるので、置き換えが起きなくなるまで置き換えを試す matched = false; // targetClasses Wikitextを消費しない記法で無限再帰するのを防止 foreach (var plugin in plugins) { if (plugin.IsSubclassOf(typeof(PageElements.Notation))) { string pattern = (string)plugin.GetProperty("Pattern").GetGetMethod().Invoke(null, null); if (pattern != null) { elementClass = plugin; //// ignoreClassesでも対象Wikitextが短くなっているなら無限再帰を防げる。 //if (targetClasses.Contains(plugin)) // wikitext = Regex.Replace(wikitext, @"(?<=.)" + pattern + @"|" + pattern + @"(?=.)", new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); //else // wikitext = Regex.Replace(wikitext, pattern, new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); // matchedはdelegate内で更新 wikitext = Regex.Replace(wikitext, pattern, new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); } } } } while (matched); Debug.WriteLine("1 ====================\n" + wikitext + "\n====================\n"); { //TODO:Variables系 } Debug.WriteLine("2 ====================\n" + wikitext + "\n====================\n"); { // Plaintext系 この時点で > ... < 間にあるテキスト全てが対象。 //FIXME:名前付きグループを使えば3つの正規表現をまとめられる。 wikitext = Regex.Replace(wikitext, @"^([^<]+)", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); wikitext = Regex.Replace(wikitext, @"(?<=\>)((?:[^<>]|\n)+?)(?=\<)", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); wikitext = Regex.Replace(wikitext, @"([^>]+)$", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); } Debug.Assert(Regex.Replace(wikitext, @"\<[^\>]+?\>", "", RegexOptions.Singleline & RegexOptions.IgnoreCase) == ""); { // Elements構築 foreach (Match m in Regex.Matches(wikitext, @"\<([^\>]+?)\>", RegexOptions.Singleline & RegexOptions.IgnoreCase)) { elements.Add(elementSet[m.Groups[1].Value]); } } return elements; } private static Dictionary<string, PageElement> elementSet = new Dictionary<string, PageElement>(); /// <summary> /// WikiText -> Elements /// </summary> /// <param name="wikitext">< > を中間データとして使用するため、PageElements系以外から呼び出すときはwikitextにHttpUtility.HtmlEncode()の出力を渡すこと。</param> /// <returns></returns> public static List<PageElement> Tokenize(PageElement parent, string wikitext, HashSet<Type> targetClasses) { //FIXME:wikitextはエスケープ済みでなければならない仕様 // Regex.Replaceの仕様上引数を渡せないので、クロージャ内で使える変数を用意 Type elementClass = null; PageElement _elementInstance = null; Match _match = null; bool matched; Func<Match, string> createElement = delegate(Match m) { matched = true; string ret; var classes = new HashSet<Type>(targetClasses); classes.Remove(elementClass); if (elementClass.GetMethod("Merge") != null) { // 連結可能Element Debug.Assert(elementClass.Name != null); if (_elementInstance != null && _elementInstance.GetType().Name == elementClass.Name && _match.Index + _match.Length == m.Index) { // 連結、既存Elementに連結 elementClass.GetMethod("Merge").Invoke(null, new object[] { parent, m, classes, _elementInstance }); //_elem = _elem; ret = ""; } else { var elementInstance = (PageElement)Assembly.GetAssembly(elementClass).CreateInstance(elementClass.FullName, false, BindingFlags.CreateInstance, null, new object[] { parent, m, classes }, null, null); elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance); _elementInstance = elementInstance; ret = "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">"; } } else { var elementInstance = (PageElement)Assembly.GetAssembly(elementClass).CreateInstance(elementClass.FullName, false, BindingFlags.CreateInstance, null, new object[] { parent, m, classes }, null, null); elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance); _elementInstance = null; ret = "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">"; } _match = m; return ret; }; Func<Match, string> createPlaintextElement = delegate(Match m) { matched = true; // Plains系オブジェクト生成 //var elementInstance = PageElements.Plain.Construct(parent, m.Groups[0].Value, targetPlainClasses); var elementInstance = PageElements.Plain.Construct(parent, m.Groups[0].Value, targetClasses); if (elementInstance != null) { elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance); return "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">"; } else { return ""; } }; var elements = new List<PageElement>(); var plugins = new HashSet<Type>(targetClasses); do { // 置き換えが起きるたびに状況が変わるので、置き換えが起きなくなるまで置き換えを試す matched = false; // targetClasses Wikitextを消費しない記法で無限再帰するのを防止 foreach (var plugin in plugins) { if (plugin.IsSubclassOf(typeof(PageElements.Notation))) { string pattern = (string)plugin.GetProperty("Pattern").GetGetMethod().Invoke(null, null); if (pattern != null) { elementClass = plugin; //// ignoreClassesでも対象Wikitextが短くなっているなら無限再帰を防げる。 //if (targetClasses.Contains(plugin)) // wikitext = Regex.Replace(wikitext, @"(?<=.)" + pattern + @"|" + pattern + @"(?=.)", new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); //else // wikitext = Regex.Replace(wikitext, pattern, new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); // matchedはdelegate内で更新 wikitext = Regex.Replace(wikitext, pattern, new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); } } } } while (matched); Debug.WriteLine("1 ====================\n" + wikitext + "\n====================\n"); { //TODO:Variables系 } Debug.WriteLine("2 ====================\n" + wikitext + "\n====================\n"); { // Plaintext系 この時点で > ... < 間にあるテキスト全てが対象。 //FIXME:名前付きグループを使えば3つの正規表現をまとめられる。 wikitext = Regex.Replace(wikitext, @"^([^<]+)", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); wikitext = Regex.Replace(wikitext, @"(?<=\>)((?:[^<>]|\n)+?)(?=\<)", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); wikitext = Regex.Replace(wikitext, @"([^>]+)$", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase); } Debug.Assert(Regex.Replace(wikitext, @"\<[^\>]+?\>", "", RegexOptions.Singleline & RegexOptions.IgnoreCase) == ""); { // Elements構築 foreach (Match m in Regex.Matches(wikitext, @"\<([^\>]+?)\>", RegexOptions.Singleline & RegexOptions.IgnoreCase)) { elements.Add(elementSet[m.Groups[1].Value]); } } return elements; }