using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.RegularExpressions; using System.Web; using Microsoft.AspNetCore.Http; using ZelWiki.Engine.Function; using ZelWiki.Engine.Function.Exceptions; using ZelWiki.Engine.Library; using ZelWiki.Engine.Library.Interfaces; using ZelWiki.Library; using ZelWiki.Library.Interfaces; using static ZelWiki.Engine.Library.Constants; using Constants = ZelWiki.Engine.Library.Constants; namespace ZelWiki.Engine { public class ZelEngineState : IZelEngineState { public IZelEngine Engine { get; private set; } private string _queryTokenHash = "c03a1c9e-da83-479b-87e8-21d7906bd866"; private int _matchesStoredPerIteration = 0; private readonly string _tocName = "TOC_" + new Random().Next(0, 1000000).ToString(); private readonly Dictionary _handlerState = new(); #region 公共属性. /// /// 通过调用设置自定义页面标题 @@Title("...") /// public string? PageTitle { get; set; } public int ErrorCount { get; private set; } public int MatchCount { get; private set; } public int TransformIterations { get; private set; } public TimeSpan ProcessingTime { get; private set; } public Dictionary Variables { get; } = new(); public Dictionary Snippets { get; } = new(); public List OutgoingLinks { get; private set; } = new(); public List ProcessingInstructions { get; private set; } = new(); public string HtmlResult { get; private set; } = string.Empty; public List Tags { get; set; } = new(); public Dictionary Matches { get; private set; } = new(); public List TableOfContents { get; } = new(); public List Headers { get; } = new(); #endregion #region 入参. public IPage Page { get; } public int? Revision { get; } public IQueryCollection QueryString { get; } public ISessionState? Session { get; } public HashSet OmitMatches { get; private set; } = new(); public int NestDepth { get; private set; } //用于递归 #endregion /// /// 存储只需要在单个wiki处理会话中生存的处理程序的值. /// public void SetStateValue(string key, T value) { if (value == null) { return; } _handlerState[key] = value; } /// /// 获取只需要在单个处理会话中生存的处理程序的值. /// public T GetStateValue(string key, T defaultValue) { if (_handlerState.TryGetValue(key, out var value)) { return (T)value; } SetStateValue(key, defaultValue); return defaultValue; } /// /// 尝试获取只需要在单个处理会话中生存的处理程序的值.. /// public bool TryGetStateValue(string key, [MaybeNullWhen(false)] out T? outValue) { if (_handlerState.TryGetValue(key, out var value)) { outValue = (T)value; return true; } outValue = default; return false; } /// /// 创建一个实例 /// /// /// /// /// /// /// internal ZelEngineState(IZelEngine engine, ISessionState? session, IPage page, int? revision = null, Constants.WikiMatchType[]? omitMatches = null, int nestDepth = 0) { QueryString = session?.QueryString ?? new QueryCollection(); Page = page; Revision = revision; Matches = new Dictionary(); Session = session; NestDepth = nestDepth; if (omitMatches != null) { OmitMatches.UnionWith(omitMatches); } Engine = engine; } /// /// /// /// /// /// public IZelEngineState TransformChild(IPage page, int? revision = null) { return new ZelEngineState(Engine, Session, page, revision, OmitMatches.ToArray(), NestDepth + 1) .Transform(); } internal IZelEngineState Transform() { var startTime = DateTime.UtcNow; try { var pageContent = new WikiString(Page.Body); pageContent.Replace("\r\n", "\n"); TransformLiterals(pageContent); while (TransformAll(pageContent) > 0) { } TransformPostProcessingFunctions(pageContent); TransformWhitespace(pageContent); if (PageTitle != null) { if (Matches.TryGetValue(PageTitle, out var pageTitle)) { PageTitle = pageTitle.Content; } } int length; do { length = pageContent.Length; foreach (var v in Matches) { if (OmitMatches.Contains(v.Value.MatchType)) pageContent.Replace(v.Key, string.Empty); else pageContent.Replace(v.Key, v.Value.Content); } } while (length != pageContent.Length); pageContent.Replace(SoftBreak, "\r\n"); pageContent.Replace(HardBreak, "
"); foreach (var header in Headers) { pageContent.Insert(0, header); } HtmlResult = pageContent.ToString(); } catch (Exception ex) { StoreCriticalError(ex); } ProcessingTime = DateTime.UtcNow - startTime; Engine.CompletionHandler.Complete(this); return this; } private int TransformAll(WikiString pageContent) { TransformIterations++; _matchesStoredPerIteration = 0; TransformComments(pageContent); TransformHeadings(pageContent); TransformScopeFunctions(pageContent); TransformVariables(pageContent); TransformLinks(pageContent); TransformMarkup(pageContent); TransformEmoji(pageContent); TransformStandardFunctions(pageContent, true); TransformStandardFunctions(pageContent, false); TransformProcessingInstructionFunctions(pageContent); int length; do { length = pageContent.Length; foreach (var v in Matches) { if (v.Value.AllowNestedDecode) { if (OmitMatches.Contains(v.Value.MatchType)) pageContent.Replace(v.Key, string.Empty); else pageContent.Replace(v.Key, v.Value.Content); } } } while (length != pageContent.Length); return _matchesStoredPerIteration; } /// /// 转换单行和多行的基本标记,如粗体、斜体、下划线等. /// /// private void TransformMarkup(WikiString pageContent) { var symbols = WikiUtility.GetApplicableSymbols(pageContent.Value); foreach (var symbol in symbols) { var sequence = new string(symbol, 2); var escapedSequence = Regex.Escape(sequence); var rgx = new Regex(@$"{escapedSequence}(.*?){escapedSequence}", RegexOptions.IgnoreCase); var orderedMatches = WikiUtility.OrderMatchesByLengthDescending(rgx.Matches(pageContent.ToString())); foreach (var match in orderedMatches) { string body = match.Value.Substring(sequence.Length, match.Value.Length - sequence.Length * 2); var result = Engine.MarkupHandler.Handle(this, symbol, body); StoreHandlerResult(result, WikiMatchType.Markup, pageContent, match.Value, result.Content); } } var sizeUpOrderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformHeaderMarkup().Matches(pageContent.ToString())); foreach (var match in sizeUpOrderedMatches) { var headingMarkers = 0; foreach (char c in match.Value) { if (c != '^') { break; } headingMarkers++; } if (headingMarkers >= 2 && headingMarkers <= 6) { string value = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim(); int fontSize = 1 + headingMarkers; if (fontSize < 1) fontSize = 1; var markup = "" + value + "\r\n"; StoreMatch(WikiMatchType.Markup, pageContent, match.Value, markup); } } } /// /// 转换内联和多行文字块 /// /// private void TransformLiterals(WikiString pageContent) { var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformLiterals().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { var value = match.Value.Substring(2, match.Value.Length - 4); value = HttpUtility.HtmlEncode(value); StoreMatch(WikiMatchType.Literal, pageContent, match.Value, value.Replace("\r", "").Trim().Replace("\n", "
\r\n"), false); } } /// /// /// /// private void TransformScopeFunctions(WikiString pageContent) { var content = pageContent.ToString(); var rawBlock = string.Empty; var startPos = content.Length - 1; while (true) { startPos = content.LastIndexOf("{{", startPos); if (startPos < 0) { break; } int endPos = content.IndexOf("}}", startPos); if (endPos < 0 || endPos < startPos) { var exception = new StringBuilder(); exception.AppendLine( $"定位后发生解析错误 {startPos}:
无法找到结束标记.

"); if (rawBlock?.Length > 0) { exception.AppendLine( $"最后一个成功解析的块是:
{rawBlock}"); } exception.AppendLine( $"问题发生在:
{pageContent.ToString().Substring(startPos)}

"); exception.AppendLine( $"解析器正在处理的内容是:
{pageContent}

"); throw new Exception(exception.ToString()); } rawBlock = content.Substring(startPos, endPos - startPos + 2); var transformBlock = new WikiString(rawBlock); TransformScopeFunctions(transformBlock, true); TransformScopeFunctions(transformBlock, false); content = content.Replace(rawBlock, transformBlock.ToString()); } pageContent.Clear(); pageContent.Append(content); } /// /// /// /// /// private void TransformScopeFunctions(WikiString pageContent, bool isFirstChance) { // {{([\\S\\s]*)}} var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformBlock().Matches(pageContent.ToString())); var functionHandler = Engine.ScopeFunctionHandler; foreach (var match in orderedMatches) { int paramEndIndex = -1; FunctionCall function; string mockFunctionCall = "##" + match.Value.Trim([' ', '\t', '{', '}']); try { function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, mockFunctionCall, out paramEndIndex); var firstChanceFunctions = new [] { "code" }; if (isFirstChance && firstChanceFunctions.Contains(function.Name.ToLower()) == false) { continue; } } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); continue; } string scopeBody = mockFunctionCall.Substring(paramEndIndex).Trim(); try { var result = functionHandler.Handle(this, function, scopeBody); StoreHandlerResult(result, WikiMatchType.ScopeFunction, pageContent, match.Value, scopeBody); } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); } } } /// /// /// /// private void TransformHeadings(WikiString pageContent) { var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformSectionHeadings().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { var headingMarkers = 0; foreach (var c in match.Value) { if (c != '=') { break; } headingMarkers++; } if (headingMarkers >= 2) { var link = _tocName + "_" + TableOfContents.Count.ToString(); var text = match.Value.Substring(headingMarkers, match.Value.Length - headingMarkers).Trim() .Trim(['=']).Trim(); var result = Engine.HeadingHandler.Handle(this, headingMarkers, link, text); if (!result.Instructions.Contains(HandlerResultInstruction.Skip)) { TableOfContents.Add(new TableOfContentsTag(headingMarkers - 1, match.Index, link, text)); } StoreHandlerResult(result, WikiMatchType.Heading, pageContent, match.Value, result.Content); } } } private void TransformComments(WikiString pageContent) { var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformComments().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { var result = Engine.CommentHandler.Handle(this, match.Value); StoreHandlerResult(result, WikiMatchType.Comment, pageContent, match.Value, result.Content); } } private void TransformEmoji(WikiString pageContent) { var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformEmoji().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { var key = match.Value.Trim().ToLower().Trim('%'); var scale = 100; var parts = key.Split(','); if (parts.Length > 1) { key = parts[0]; scale = int.Parse(parts[1]); } var result = Engine.EmojiHandler.Handle(this, $"%%{key}%%", scale); StoreHandlerResult(result, WikiMatchType.Emoji, pageContent, match.Value, result.Content); } } /// /// /// /// private void TransformVariables(WikiString pageContent) { var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformVariables().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { var key = match.Value.Trim(['{', '}', ' ', '\t', '$']); if (key.Contains("=")) { var sections = key.Split('='); key = sections[0].Trim(); var value = sections[1].Trim(); if (!Variables.TryAdd(key, value)) { Variables[key] = value; } var identifier = StoreMatch(WikiMatchType.Variable, pageContent, match.Value, ""); pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline. } else { if (Variables.TryGetValue(key, out string? value)) { var identifier = StoreMatch(WikiMatchType.Variable, pageContent, match.Value, value); pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline. } else { throw new Exception( $"未定义的变量 {key} . 在再用Get()应该先使用##Set()."); } } } } /// /// /// /// private void TransformLinks(WikiString pageContent) { //Parse external explicit links. eg. [[http://test.net]]. var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformExplicitHTTPLinks().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { string link = match.Value.Substring(2, match.Value.Length - 4).Trim(); var args = FunctionParser.ParseRawArgumentsAddParenthesis(link); if (args.Count > 1) { link = args[0]; string? text = args[1]; string imageTag = "image:"; string? image = null; if (text.StartsWith(imageTag, StringComparison.CurrentCultureIgnoreCase)) { image = text.Substring(imageTag.Length).Trim(); text = null; } var result = Engine.ExternalLinkHandler.Handle(this, link, text, image); StoreHandlerResult(result, Constants.WikiMatchType.Link, pageContent, match.Value, string.Empty); } else { var result = Engine.ExternalLinkHandler.Handle(this, link, link, null); StoreHandlerResult(result, Constants.WikiMatchType.Link, pageContent, match.Value, string.Empty); } } orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformExplicitHTTPsLinks().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { string link = match.Value.Substring(2, match.Value.Length - 4).Trim(); var args = FunctionParser.ParseRawArgumentsAddParenthesis(link); if (args.Count > 1) { link = args[0]; string? text = args[1]; string imageTag = "image:"; string? image = null; if (text.StartsWith(imageTag, StringComparison.CurrentCultureIgnoreCase)) { image = text.Substring(imageTag.Length).Trim(); text = null; } var result = Engine.ExternalLinkHandler.Handle(this, link, text, image); StoreHandlerResult(result, Constants.WikiMatchType.Link, pageContent, match.Value, string.Empty); } else { var result = Engine.ExternalLinkHandler.Handle(this, link, link, null); StoreHandlerResult(result, Constants.WikiMatchType.Link, pageContent, match.Value, string.Empty); } } orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformInternalDynamicLinks().Matches(pageContent.ToString())); foreach (var match in orderedMatches) { string keyword = match.Value.Substring(2, match.Value.Length - 4); var args = FunctionParser.ParseRawArgumentsAddParenthesis(keyword); string pageName; string text; string? image = null; int imageScale = 100; if (args.Count == 1) { text = WikiUtility.GetPageNamePart(args[0]); pageName = args[0]; } else if (args.Count >= 2) { pageName = args[0]; string imageTag = "image:"; if (args[1].StartsWith(imageTag, StringComparison.CurrentCultureIgnoreCase)) { image = args[1].Substring(imageTag.Length).Trim(); text = WikiUtility.GetPageNamePart(args[0]); } else { text = args[1]; } if (args.Count >= 3) { if (int.TryParse(args[2], out imageScale) == false) { imageScale = 100; } } } else { StoreError(pageContent, match.Value, "The external link contains no page name."); continue; } var pageNavigation = new NamespaceNavigation(pageName); if (string.IsNullOrEmpty(pageNavigation.Namespace) || pageName.Trim().StartsWith("::")) pageNavigation.Namespace = Page.Namespace; var result = Engine.InternalLinkHandler.Handle(this, pageNavigation, pageName.Trim(':'), text, image, imageScale); if (!result.Instructions.Contains(HandlerResultInstruction.Skip)) { OutgoingLinks.Add(new PageReference(pageName, pageNavigation.Canonical)); } StoreHandlerResult(result, WikiMatchType.Link, pageContent, match.Value, string.Empty); } } /// /// /// /// private void TransformProcessingInstructionFunctions(WikiString pageContent) { // (\\@\\@[\\w-]+\\(\\))|(\\@\\@[\\w-]+\\(.*?\\))|(\\@\\@[\\w-]+)
var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformProcessingInstructions().Matches(pageContent.ToString())); var functionHandler = Engine.ProcessingInstructionFunctionHandler; foreach (var match in orderedMatches) { FunctionCall function; try { function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex); } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); continue; } try { var result = functionHandler.Handle(this, function, string.Empty); StoreHandlerResult(result, Constants.WikiMatchType.Instruction, pageContent, match.Value, string.Empty); } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); } } } /// /// /// /// /// private void TransformStandardFunctions(WikiString pageContent, bool isFirstChance) { //Remove the last "(\#\#[\w-]+)" if you start to have matching problems: var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformFunctions().Matches(pageContent.ToString())); var functionHandler = Engine.StandardFunctionHandler; foreach (var match in orderedMatches) { FunctionCall function; try { function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex); } catch (WikiFunctionPrototypeNotDefinedException ex) { var postProcessPrototypes = Engine.PostProcessingFunctionHandler.Prototypes; var parsed = FunctionParser.ParseFunctionCall(postProcessPrototypes, match.Value); if (parsed != default) { if (postProcessPrototypes.Exists(parsed.Prefix, parsed.Name)) { continue; //This IS a function, but it is meant to be parsed at the end of processing. } } StoreError(pageContent, match.Value, ex.Message); continue; } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); continue; } var firstChanceFunctions = new string[] { "include", "inject" }; //Process these the first time through. if (isFirstChance && firstChanceFunctions.Contains(function.Name.ToLower()) == false) { continue; } try { var result = functionHandler.Handle(this, function, string.Empty); StoreHandlerResult(result, Constants.WikiMatchType.StandardFunction, pageContent, match.Value, string.Empty); } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); } } } /// /// /// /// private void TransformPostProcessingFunctions(WikiString pageContent) { //Remove the last "(\#\#[\w-]+)" if you start to have matching problems: var orderedMatches = WikiUtility.OrderMatchesByLengthDescending( PrecompiledRegex.TransformPostProcess().Matches(pageContent.ToString())); var functionHandler = Engine.PostProcessingFunctionHandler; foreach (var match in orderedMatches) { FunctionCall function; try { function = FunctionParser.ParseAndGetFunctionCall(functionHandler.Prototypes, match.Value, out int matchEndIndex); } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); continue; } try { var result = functionHandler.Handle(this, function, string.Empty); StoreHandlerResult(result, Constants.WikiMatchType.StandardFunction, pageContent, match.Value, string.Empty); } catch (Exception ex) { StoreError(pageContent, match.Value, ex.Message); } } } private static void TransformWhitespace(WikiString pageContent) { string identifier = $""; pageContent.Replace("\r\n", "\n"); pageContent.Replace("\n", identifier); pageContent.Replace($"{identifier}{identifier}", identifier); pageContent.Replace(identifier, "
"); } #region Utility. private void StoreHandlerResult(HandlerResult result, WikiMatchType matchType, WikiString pageContent, string matchValue, string scopeBody) { if (result.Instructions.Contains(HandlerResultInstruction.Skip)) { return; } bool allowNestedDecode = !result.Instructions.Contains(HandlerResultInstruction.DisallowNestedProcessing); string identifier; if (result.Instructions.Contains(HandlerResultInstruction.OnlyReplaceFirstMatch)) { identifier = StoreFirstMatch(matchType, pageContent, matchValue, result.Content, allowNestedDecode); } else { identifier = StoreMatch(matchType, pageContent, matchValue, result.Content, allowNestedDecode); } foreach (var instruction in result.Instructions) { switch (instruction) { case HandlerResultInstruction.TruncateTrailingLine: pageContent.Replace($"{identifier}\n", $"{identifier}"); //Kill trailing newline. break; } } } private void StoreCriticalError(Exception ex) { Engine.ExceptionHandler.Log(this, ex, $"Page: {Page.Navigation}, Error: {ex.Message}"); ErrorCount++; HtmlResult = WikiUtility.WarningCard("Wiki Parser Exception", ex.Message); } private string StoreError(WikiString pageContent, string match, string value) { Engine.ExceptionHandler.Log(this, null, $"Page: {Page.Navigation}, Error: {value}"); ErrorCount++; _matchesStoredPerIteration++; string identifier = $""; var matchSet = new WikiMatchSet() { Content = $"{{{value}}}", AllowNestedDecode = false, MatchType = WikiMatchType.Error }; Matches.Add(identifier, matchSet); pageContent.Replace(match, identifier); return identifier; } private string StoreMatch(WikiMatchType matchType, WikiString pageContent, string match, string value, bool allowNestedDecode = true) { MatchCount++; _matchesStoredPerIteration++; string identifier = $""; var matchSet = new WikiMatchSet() { MatchType = matchType, Content = value, AllowNestedDecode = allowNestedDecode }; Matches.Add(identifier, matchSet); pageContent.Replace(match, identifier); return identifier; } private string StoreFirstMatch(WikiMatchType matchType, WikiString pageContent, string match, string value, bool allowNestedDecode = true) { MatchCount++; _matchesStoredPerIteration++; string identifier = $""; var matchSet = new WikiMatchSet() { MatchType = matchType, Content = value, AllowNestedDecode = allowNestedDecode }; Matches.Add(identifier, matchSet); var pageContentCopy = NTDLS.Helpers.Text.ReplaceFirstOccurrence(pageContent.ToString(), match, identifier); pageContent.Clear(); pageContent.Append(pageContentCopy); return identifier; } /// /// /// /// public string GetNextQueryToken() { _queryTokenHash = Security.Helpers.Sha256( Security.Helpers.EncryptString(Security.Helpers.MachineKey, _queryTokenHash)); return $"H{Security.Helpers.Crc32(_queryTokenHash)}"; } #endregion } }