using System; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Text; namespace BloggerLibrary { public class TextParser { private const char HtmlOpenChar = '<'; private const char HtmlCloseChar = '>'; private const char HtmlEndToken = '/'; private const char CustomOpenChar = '['; private const char CustomCloseChar = ']'; private const char CustomEndToken = '/'; private const char quoteChar = '"'; //Holds the tags that the parser will accept private Dictionary tagMap = new Dictionary(); //Holds the tags found in the text, when the closing tags are found the items are pushed off the stack. //Which in well formed forum html will produce an empty stack when parsing has finished private Stack tagStack = new Stack(); //When anything ordinary replacing needs to be done outside a tag. private Dictionary replaceMap = new Dictionary(); //When anything from regularexpressions needs to be replaced outside a tag private Dictionary regExpReplaceMap = new Dictionary(); //Critical map contains critical replaces, that should be done even when inside a noparse tag! private Dictionary criticalMap = new Dictionary(); //Critical map contains critical replaces for regular expressions private Dictionary criticalRegExpMap = new Dictionary(); //Defines tags which contents will not be parsed (ex. quote tags) private Dictionary noTouchMap = new Dictionary(); //Holds values for parameters private Dictionary parameterMap = new Dictionary(); private bool stripHTMLTags = true; public bool StripHTMLTags { get { return stripHTMLTags; } set { stripHTMLTags = value; } } public void AddTag(string tag, string htmlTag) { AddTag(tag, htmlTag, String.Empty); } public void AddTag(string tag, string htmlTag, string parameter) { AddTag(tag, htmlTag, parameter, false); } public void AddTag(string tag, string htmlTag, string parameter, bool noTouch) { if (tagMap.ContainsKey(tag)) { throw new TextParserException("The tag has already been added to the tag map"); } tagMap.Add(tag, htmlTag); if (noTouch) { noTouchMap.Add(tag, true); } if (!String.IsNullOrEmpty(parameter)) { parameterMap.Add(tag, parameter); } } public void AddReplace(string match, string replace) { AddReplace(match, replace, false, false); } public void AddReplace(string match, string replace, bool regularExpression) { AddReplace(match, replace, regularExpression, false); } public void AddReplace(string match, string replace, bool regularExpression, bool critical) { if (!regularExpression) { if (replaceMap.ContainsKey(match)) { throw new TextParserException("The match string has already been added to the replace map"); } replaceMap.Add(match, replace); if (critical) { criticalMap.Add(match, true); } } else { if (replaceMap.ContainsKey(match)) { throw new TextParserException("The match string has already been added to the replace map"); } regExpReplaceMap.Add(match, replace); if (critical) { criticalRegExpMap.Add(match, true); } } } private string text; public TextParser(string text){ this.text = text; } public TextParser() { } public string TextToParse { get { return text; } set { text = value; } } private string GetUserParameter(string tagName, string userArg){ if (parameterMap.ContainsKey(tagName)) { string param = parameterMap[tagName]; if (param.Contains("$")) { return " " + param.Replace("$", userArg); } else { return " " + param; } } else { return String.Empty; } } public string Parse() { StringBuilder sb = new StringBuilder(); bool inTag = false; bool inBeginTag = false; bool inEndTag = false; bool noParse = false; bool inUserParam = false; bool inQuote = false; int textSize = text.Length; string currentTag = String.Empty; string userParam = String.Empty; StringBuilder currentToken = new StringBuilder(); foreach (char c in text) { if(inTag) { if (c == CustomCloseChar) { currentToken.Append(CustomOpenChar); currentToken.Append(CustomCloseChar); } else if (c == CustomEndToken) { inTag = false; inEndTag = true; } else if(!noParse) { currentTag += c; inTag = false; inBeginTag = true; } else { inTag = false; currentToken.Append(CustomOpenChar); currentToken.Append(c); } } else if (inBeginTag) { if (c == CustomCloseChar && !inQuote) { inBeginTag = false; if (tagMap.ContainsKey(currentTag)) { string tag = tagMap[currentTag]; tagStack.Push(currentTag); PerformReplace(noParse, ref currentToken); sb.Append(currentToken); currentToken = new StringBuilder(); sb.Append(HtmlOpenChar); sb.Append(tag); sb.Append(GetUserParameter(currentTag, userParam)); sb.Append(HtmlCloseChar); if (noTouchMap.ContainsKey(currentTag)) { noParse = true; } } else { currentToken.Append(CustomOpenChar); currentToken.Append(currentTag); if(userParam != String.Empty) currentToken.Append('=' + userParam); currentToken.Append(CustomCloseChar); } userParam = String.Empty; currentTag = String.Empty; inUserParam = false; } else if(c == CustomCloseChar && inQuote) { currentToken.Append(CustomOpenChar); currentToken.Append(currentTag); currentToken.Append('='); currentToken.Append(quoteChar); currentToken.Append(userParam); currentToken.Append(CustomCloseChar); inBeginTag = false; inQuote = false; inUserParam = false; currentTag = String.Empty; } else if (c == quoteChar) { if(!inQuote){ if(inUserParam){ inQuote = true; } else { currentToken.Append(CustomOpenChar); currentToken.Append(currentTag); currentToken.Append(quoteChar); inBeginTag = false; currentTag = String.Empty; } } else { inQuote = false; } } else if (c == '=') { if (!inUserParam) { inUserParam = true; } else { if (inQuote) { userParam += c; } else { currentToken.Append(CustomOpenChar); currentToken.Append(currentTag); currentToken.Append('='); if (inQuote) currentToken.Append('"'); currentToken.Append(userParam); currentToken.Append('='); inBeginTag = false; inQuote = false; inUserParam = false; currentTag = String.Empty; userParam = String.Empty; } } } else { if (inUserParam) { userParam += c; } else { currentTag += c; } } } else if (inEndTag) { if (c != CustomCloseChar) { currentTag += c; } else { inEndTag = false; if (!noParse || noTouchMap.ContainsKey(currentTag)) { if (tagMap.ContainsKey(currentTag)) { string tag = tagMap[currentTag]; bool outputHtmlEndTag = true; if (!EnsureTagStack(currentTag, ref currentToken, sb, noParse)) //If this expression returns true then we need to manually output the opening tag { outputHtmlEndTag = false; } //Output close tag if (outputHtmlEndTag) { //No need to output current token, EnsureTagStack does that, but reset the current token currentToken = new StringBuilder(); sb.Append(HtmlOpenChar); sb.Append(HtmlEndToken); sb.Append(tag); sb.Append(HtmlCloseChar); } else { currentToken.Append(CustomOpenChar); currentToken.Append(CustomEndToken); currentToken.Append(currentTag); currentToken.Append(CustomCloseChar); } if (noTouchMap.ContainsKey(currentTag)) { noParse = false; } } else { currentToken.Append(CustomOpenChar); currentToken.Append(CustomEndToken); currentToken.Append(currentTag); currentToken.Append(CustomCloseChar); } } else { currentToken.Append(CustomOpenChar); currentToken.Append(CustomEndToken); currentToken.Append(currentTag); currentToken.Append(CustomCloseChar); } currentTag = String.Empty; } } else { if (c == CustomOpenChar) { inTag = true; } else { currentToken.Append(c); } } } PerformReplace(noParse, ref currentToken); sb.Append(currentToken); while (tagStack.Count > 0) { string tag = tagMap[tagStack.Pop()]; sb.Append(HtmlOpenChar + HtmlEndToken + tag + HtmlCloseChar); } return sb.ToString(); } private void PerformReplace(bool noParse, ref StringBuilder currentToken) { if (!noParse) { foreach (KeyValuePair entry in replaceMap) { currentToken.Replace(entry.Key, entry.Value); } foreach (KeyValuePair entry in regExpReplaceMap) { Regex reg = new Regex(entry.Key); currentToken = new StringBuilder(reg.Replace(currentToken.ToString(), entry.Value)); } } foreach (KeyValuePair entry in criticalMap) { if (entry.Value) { string replace = replaceMap[entry.Key]; currentToken.Replace(entry.Key, replace); } } foreach (KeyValuePair entry in criticalRegExpMap) { if (entry.Value) { string replace = regExpReplaceMap[entry.Key]; Regex reg = new Regex(entry.Key); currentToken = new StringBuilder(reg.Replace(currentToken.ToString(), replace)); } } } private bool EnsureTagStack(string tag, ref StringBuilder token, StringBuilder output, bool noParse) { if (!tagStack.Contains(tag)) { return false; } /*if (StripHTMLTags) { token.Replace("<", "<"); token.Replace(">", ">"); }*/ PerformReplace(noParse, ref token); output.Append(token.ToString()); string tagStackTag = tagStack.Pop(); while (tagStackTag != tag) { string htmlTag = tagMap[tagStackTag]; output.Append(HtmlOpenChar.ToString() + HtmlEndToken.ToString() + htmlTag + HtmlCloseChar); tagStackTag = tagStack.Pop(); } return true; } } public class TextParserException : Exception { public TextParserException(string message) : base(message) {} } }