1: /// <summary>
2: /// This is a generic class that is used to parse all CustomTags
3: /// It will search for all CustomTagsin the output of an ASPX
4: /// page and, if there is a Template defined in the TagParsers
5: /// it renders that template (passing the parameters in the QueryString)
6: /// it makes MAX_PASSES passes over the HTML to account for
7: /// CustomTags that output other CustomTags
8: /// </summary>
9: class CustomTagParser : Stream
10: {
11: const int MAX_PASSES = 20;
12: private Stream _OutputStream;
13: private MemoryStream _BufferStream;
14: public CustomTagParser(Stream outputStream)
15: {
16: _OutputStream = outputStream;
17: _BufferStream = new MemoryStream();
18: }
19: public override bool CanRead
20: {
21: get { return false; }
22: }
23:
24: public override bool CanSeek
25: {
26: get { return false; }
27: }
28:
29: public override bool CanWrite
30: {
31: get { return true; }
32: }
33:
34: public override void Flush()
35: {
36: string html;
37: _BufferStream.Position = 0;
38: using (StreamReader reader = new StreamReader(_BufferStream, ASCIIEncoding.UTF8))
39: {
40: html = reader.ReadToEnd();
41: }
42: html = RenderCustomTags(html);
43: byte[] utf8Bytes = ASCIIEncoding.UTF8.GetBytes(html);
44: _OutputStream.Write(utf8Bytes, 0, utf8Bytes.Length);
45: _OutputStream.Flush();
46: }
47:
48: private static Dictionary<string, string> GetTagParameters(string tag)
49: {
50: Dictionary<string, string> paramDictionary = new Dictionary<string, string>();
51: // Get param/value pairs
52: Regex parameterRegex = new Regex("([a-z0-9]+)=\"([\\w\\s#|]*)\"", RegexOptions.IgnoreCase);
53: foreach (Match paramMatch in parameterRegex.Matches(tag))
54: {
55: string paramName = paramMatch.Groups[1].Value;
56: string paramValue = paramMatch.Groups[2].Value;
57: paramDictionary.Add(paramName.ToLower(), paramValue);
58: }
59: // Get params with no value pair (e.g. "notable" in <zifftag notable>)
60: Regex novalueRegex = new Regex("\\s\\b([a-z0-9]+)\\b(?!=)", RegexOptions.IgnoreCase);
61: foreach (Match paramMatch in novalueRegex.Matches(tag))
62: {
63: string paramName = paramMatch.Groups[1].Value;
64: paramDictionary.Add(paramName.ToLower(), null);
65: }
66: return paramDictionary;
67: }
68:
69: private static string GetPageAndQueryString(string templateLocation, string innerText, Dictionary<string, string> parameters)
70: {
71: StringBuilder pageAndQueryString = new StringBuilder();
72: pageAndQueryString.Append(templateLocation);
73: pageAndQueryString.Append("?innerText=");
74: pageAndQueryString.Append(HttpUtility.UrlEncode(innerText));
75: foreach (var param in parameters)
76: {
77: pageAndQueryString.Append("&");
78: pageAndQueryString.Append(HttpUtility.UrlEncode(param.Key));
79: pageAndQueryString.Append("=");
80: pageAndQueryString.Append(HttpUtility.UrlEncode(param.Value));
81: }
82: return pageAndQueryString.ToString();
83: }
84: private string RenderCustomTags(string html)
85: {
86: StringWriter writer = null;
87: Regex customTagRegex = new Regex("<(CUSTOM[A-Z0-9]*)([^>]*)>((.*?)</\\1>)?", RegexOptions.IgnoreCase);
88: for (int i = 0; i <= MAX_PASSES; i++)
89: {
90: int currentOffset = 0;
91: writer = new StringWriter();
92: MatchCollection customTagMatches = customTagRegex.Matches(html);
93: if (customTagMatches.Count == 0)
94: break; // no need to keep going
95: foreach (Match match in customTagMatches)
96: {
97: // Get tag info
98: string fullTag = match.Value;
99: string tagName = match.Groups[1].Value;
100: string innerText = string.Empty;
101: string parameterString = match.Groups[2].Value;
102: if (match.Groups.Count == 5 /* has closing tag */)
103: innerText = match.Groups[4].Value;
104:
105: // Get template location
106: string templateLocation;
107: if (!TagParsers.TryGetValue(tagName, out templateLocation))
108: continue; // No template defined for this tag
109:
110: // Write text before this tag
111: writer.Write(html.Substring(currentOffset, match.Index - currentOffset));
112:
113: Dictionary<string, string> parameters = GetTagParameters(parameterString);
114:
115: string pageAndQueryString = GetPageAndQueryString(templateLocation, innerText, parameters);
116: try
117: {
118: HttpContext.Current.Server.Execute(pageAndQueryString.ToString(), writer);
119: }
120: catch (Exception ex)
121: {
122: // Some logging
123: }
124: finally
125: {
126: // Update current offset before next pass
127: currentOffset = match.Index + match.Length;
128: }
129: }
130: // Write remainder of html to writer
131: writer.Write(html.Substring(currentOffset));
132: html = writer.ToString();
133: }
134: return html;
135: }
136: public override long Length
137: {
138: get { return _BufferStream.Length; }
139: }
140:
141: public override long Position
142: {
143: get
144: {
145: throw new NotImplementedException();
146: }
147: set
148: {
149: throw new NotImplementedException();
150: }
151: }
152:
153: public override int Read(byte[] buffer, int offset, int count)
154: {
155: throw new NotImplementedException();
156: }
157:
158: public override long Seek(long offset, SeekOrigin origin)
159: {
160: throw new NotImplementedException();
161: }
162:
163: public override void SetLength(long value)
164: {
165: throw new NotImplementedException();
166: }
167:
168: public override void Write(byte[] buffer, int offset, int count)
169: {
170: _BufferStream.Write(buffer, offset, count);
171: }
172: }