Click here to Skip to main content
15,895,084 members
Articles / Web Development / ASP.NET

OpenCollective -- The Requirements Management Wiki

Rate me:
Please Sign up or sign in to vote.
4.41/5 (16 votes)
9 Nov 20044 min read 262.5K   1.8K   111  
An article on building a project oriented wiki for software development requirements management
/*
OpenCollective -  http://www.netbrick.net/ -- Version 0.73
Copyright (c) 2004
by Tyler Jensen ( tylerj@netbrick.net ) of NetBrick Inc. ( http://www.netbrick.net )

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
of the Software, and to permit persons to whom the Software is furnished to do 
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 
OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using System.Web;
using OpenCollective.Data;

namespace OpenCollective.Engine
{
	/// <summary>
	/// The wiki parser is the presentation engine that converts plain text entries to presentable HTML.
	/// </summary>
	public class Parser
	{
		// no public constructor, methods only
		public Parser() {}

		// Regex to extract topics from a line 
		// or the whole text like topic_text or topic_ 
		public static string LinkRxForm = @"(\^\[\[|\+\[\[|\[\[)(.)*?(\]\])";

		//Now use the string to create the Regex
		public static Regex LinkRx = new Regex(LinkRxForm, RegexOptions.Compiled);

		public static Regex LinkRxAll = new Regex(LinkRxForm, RegexOptions.Compiled|RegexOptions.Singleline);

		public string ConvertWikiTextToHTML(string appPath, string wikiName, string topic, string wikitext)
		{
			StringBuilder result = new StringBuilder(7);
			ArrayList Segments = this.SegmentWikiText(wikitext);
			for(int i = 0; i < Segments.Count; i++)
			{
				WikiTextBlock block = (WikiTextBlock)Segments[i];
				switch(block.BlockType)
				{
					case WikiTextBlockType.List:
						result.Append(FormatListWikiText(appPath, wikiName, topic, block.WikiText));
						break;
					case WikiTextBlockType.Code:
						result.Append(FormatCodeWikiText(appPath, wikiName, topic, block.WikiText));
						break;
					case WikiTextBlockType.Table:
						result.Append(FormatTableWikiText(appPath, wikiName, topic, block.WikiText));
						break;
					default:
						result.Append(FormatPlainWikiText(appPath, wikiName, topic, block.WikiText));
						break;
				}
			}
			return result.ToString();
		}

		public string ConvertWikiTextToHTMLNoLinks(string appPath, string wikiName, string topic, string wikitext)
		{
			bool hideLinks = true;
			StringBuilder result = new StringBuilder(7);
			ArrayList Segments = this.SegmentWikiText(wikitext);
			for(int i = 0; i < Segments.Count; i++)
			{
				WikiTextBlock block = (WikiTextBlock)Segments[i];
				switch(block.BlockType)
				{
					case WikiTextBlockType.List:
						result.Append(FormatListWikiText(appPath, wikiName, topic, block.WikiText, hideLinks));
						break;
					case WikiTextBlockType.Code:
						result.Append(FormatCodeWikiText(appPath, wikiName, topic, block.WikiText));
						break;
					case WikiTextBlockType.Table:
						result.Append(FormatTableWikiText(appPath, wikiName, topic, block.WikiText, hideLinks));
						break;
					default:
						result.Append(FormatPlainWikiText(appPath, wikiName, topic, block.WikiText, hideLinks));
						break;
				}
			}
			return result.ToString();
		}

		private string FormatListWikiText(string appPath, string wikiName, string topic, string wikitext)
		{
			bool hideLinks = false;
			return FormatListWikiText(appPath, wikiName, topic, wikitext, hideLinks);
		}
		private string FormatListWikiText(string appPath, string wikiName, string topic, string wikitext, bool hideLinks)
		{
			wikitext = wikitext.Replace("\r", "");
			string [] Lines = wikitext.Split (new char[] {'\n'});
			StringBuilder result = new StringBuilder(7);
			Stack listStack = new Stack(3);  //a stack for ol and ul lists
			bool isFirstLine = true;
			foreach(string wikiline in Lines)
			{
				if(wikiline != "")  //only parse lines with text or return character - this eliminates any trailing empty lines
				{
					string theLine = wikiline; //wikiline.Replace("\r", "");  //drop the return character
					//this.HTMLEncodeSimple(ref theLine);  //convert all   &  <  >   characters  //TODO - consider allowing HTML
					this.FormatStandardWikiLine(appPath, wikiName, ref theLine, topic, hideLinks);

					//handle lists  Stack listStack, bool inList, (indent level can be taken from listStack.Count
					string listLine = "";  
							
					//check for the list line format  @"(^\s[\s]+)(1|\*)(\s)"
					Match m = Regex.Match(theLine, @"(^\s[\s]+)(\#|\*)(\s)", RegexOptions.Compiled);
					if (m.Length > 0)
						listLine = m.ToString();

					int indentLevel = listStack.Count;
					int indentSpaces = listLine.Length - 2;
					bool isBullet = true;
					if(listLine != "")
						isBullet = listLine.Substring(listLine.Length - 2, 1) == "*";

					double levelsToChange = 0.0;
					double rem = 0.0;

					if(isFirstLine)
					{
						rem = indentSpaces % 2;
						if (rem > 0.0)
							levelsToChange = (indentSpaces + 1) / 2;
						else
							levelsToChange = indentSpaces / 2;

						theLine = "<li>" + theLine.Remove(0, listLine.Length) + "</li>\n";
					
						for (int x = 0; x < levelsToChange; x++) //note: we need at least one header
						{
							if (isBullet)
							{
								theLine = "<ul>" + theLine;
								listStack.Push("</ul>");
							}
							else
							{
								switch (x)
								{
									case 1:
										theLine = "<ol type=\"A\">" + theLine;
										break;
									case 2:
										theLine = "<ol type=\"a\">" + theLine;
										break;
									default:
										theLine = "<ol>" + theLine;
										break;
								}
								listStack.Push("</ol>");
							}
						}
						theLine += "\n";
						isFirstLine = false;  //toggle back now that the first line is done
					}
					else
					{
						//this is a subsequent list line - get it's indent level and if different that current indentLevel, adjust
						if ((indentLevel * 2) == indentSpaces)
						{
							//this line is on the same indent level, so just add <li> and </li> tags - removing the list indicator of course
							theLine = "<li>" + theLine.Remove(0, listLine.Length) + "</li>\n";
						}
						else
						{
							//indent level changed so we either have to add a <ul> or <ol> and push the close tags
							//or we have to pop close tags to close the number of levels the line dropped and then add <li> and </li> tags
							if ((indentLevel * 2) > indentSpaces)
							{
								//going back in, so pop some close tags off after applying the <li> tag - ? is how many to pop off
								rem = ((indentLevel * 2) - indentSpaces) % 2;
								if (rem > 0.0)
									levelsToChange = ((indentLevel * 2) - indentSpaces) / 2;
								else
									levelsToChange = ((indentLevel * 2) - indentSpaces + 1) / 2;

								theLine = "<li>" + theLine.Remove(0, listLine.Length) + "</li>\n";
								for (int x = 0; x < levelsToChange; x++)
								{
									try
									{
										theLine = listStack.Pop().ToString() + theLine;
									}
									catch
									{
										//do nothing - means we popped 'em all off
									}
								}
								theLine += "\n";
							}
							else
							{
								//indenting even more, so add the <ul> or <ol> tag, push the close tag and add the <li> tag
								rem = (indentSpaces - (indentLevel * 2)) % 2;
								if (rem > 0.0)
									levelsToChange = (indentSpaces - (indentLevel * 2) + 1) / 2;
								else
									levelsToChange = (indentSpaces - (indentLevel * 2)) / 2;

								theLine = "<li>" + theLine.Remove(0, listLine.Length) + "</li>\n";
								for (int x = 0; x < levelsToChange; x++)
								{
									if (isBullet)
									{
										theLine = "<ul>" + theLine;
										listStack.Push("</ul>");
									}
									else
									{
										switch (indentLevel + x)
										{
											case 1:
												theLine = "<ol type=\"A\">" + theLine;
												break;
											case 2:
												theLine = "<ol type=\"a\">" + theLine;
												break;
											default:
												theLine = "<ol>" + theLine;
												break;
										}
										listStack.Push("</ol>");
									}
								}
								theLine += "\n";
							}
						}
					}
					result.Append(theLine);
				}
			}

			//append remaining </##> closing tages
			while(listStack.Count > 0)
			{
				result.Append(listStack.Pop().ToString());
			}
			return result.ToString();
		}

		private string FormatTableWikiText(string appPath, string wikiName, string topic, string wikitext)
		{
			bool hideLinks = false;
			return FormatTableWikiText(appPath, wikiName, topic, wikitext, hideLinks);
		}
		private string FormatTableWikiText(string appPath, string wikiName, string topic, string wikitext, bool hideLinks)
		{

/*
++++(W360,H100%,B1)
@@ 
Here is a the first table data row. With column header style.		FormatStandardWikiLine
@@ 
Here is the second column with header style.								FormatStandardWikiLine
~~~~
||
Here is the first column data													FormatStandardWikiLine
||(W60,H90) 
Here is the second column data with width=60 and height=90			FormatStandardWikiLine
~~~~
##
Here is a the first table data row. With column footer style.		FormatStandardWikiLine
##
Here is the second column with footer style.								FormatStandardWikiLine
++++


++++(W360,H100%,B1)\n@@ \nHere is a the first table data row. With column header style.\n@@ \nHere is the second column with header style.\n~~~~\n||\nHere is the first column data\n||(W60,H90) \nHere is the second column data with width=60 and height=90\n~~~~\n##\nHere is a the first table data row. With column footer style.\n##\nHere is the second column with footer style. \n++++\n

*/
			string head = Regex.Replace(wikitext.Substring(0, wikitext.IndexOf("\n")), @"(.*?)(\()(.+?)(\))", "$3", RegexOptions.Compiled);
			string[] tboptions = head.Split(',');

			string tbwidth = "";
			string tbheight = "";
			//string tbborder = "";
			string tbalign = "";
			for(int i = 0; i < tboptions.Length; i++)
			{
				if(tboptions[i].ToUpper().StartsWith("W"))
					tbwidth = " width=\"" + tboptions[i].Substring(1, tboptions[i].Length - 1) + "\"";
				else if(tboptions[i].ToUpper().StartsWith("H"))
					tbheight = " height=\"" + tboptions[i].Substring(1, tboptions[i].Length - 1) + "\"";
				else if(tboptions[i].ToUpper().StartsWith("C"))
					tbalign = " align=\"" + tboptions[i].Substring(0, tboptions[i].Length) + "\"";
				else if(tboptions[i].ToUpper().StartsWith("L"))
					tbalign = " align=\"" + tboptions[i].Substring(0, tboptions[i].Length) + "\"";
				else if(tboptions[i].ToUpper().StartsWith("R"))
					tbalign = " align=\"" + tboptions[i].Substring(0, tboptions[i].Length) + "\"";

				//else if(tboptions[i].ToUpper().StartsWith("B"))
				//	tbborder = " border=\"" + tboptions[i].Substring(1, tboptions[i].Length - 1) + "\"";

			}

			wikitext = wikitext.Remove(0, wikitext.IndexOf("\n") + 1);
			wikitext = wikitext.Remove(wikitext.Length - 7, 7); //get rid of ++++\n
			//string[] rows = wikitext.Split(new char[] {'~','~','~','~','\n'});
			MatchCollection rows = Regex.Matches(wikitext, @"(@@|\|\||##)(.+?)(?=\n~~+.*?\n|$)", RegexOptions.Compiled|RegexOptions.Singleline); //(?:(\n~~~~\n|$))", RegexOptions.Compiled);

			if(rows.Count == 0)
				return "<div><br><b>@@@@@@@@@ TABLE FORMATTING ERROR - REMOVE TRAILING SPACES FROM ROW SEPARATORS ====</b><br></div>";
			else
			{
				// @@ \nHere is a the first table data row. With column header style.\n@@ \nHere is the second column with header style.\n
				// ||\nHere is the first column data\n||(W60,H90) \nHere is the second column data with width=60 and height=90\n
				// ##\nHere is a the first table data row. With column footer style.\n##\nHere is the second column with footer style.
				StringBuilder result = new StringBuilder(7);
				result.Append("<table border=\"0\" class=\"ctable\" " + tbalign + tbwidth + tbheight + " cellspacing=\"0\" cellpadding=\"0\">\n");
				
				foreach(Match row in rows)
				{
					string rowtext = row.ToString();
					result.Append("<tr>");
					MatchCollection columns = Regex.Matches(rowtext, @"(@@|\|\||##)(.*?)(?=$|@@|\|\||##)", RegexOptions.Compiled|RegexOptions.Singleline);

					foreach(Match col in columns)
					{
						string coltext = col.ToString();
						if(coltext.EndsWith("\n")) coltext = coltext.Substring(0, coltext.Length - 1);
						if(coltext.StartsWith("@@") || coltext.StartsWith("||") || coltext.StartsWith("##"))
						{
							string[] colItems = coltext.Split('\n');
							string rhead = colItems[0];

							string tdclass = "cellhead";
							if(rhead.StartsWith("||"))
								tdclass = "celldata";
							else if(rhead.StartsWith("##"))
								tdclass = "cellfoot";

							string rheadOptions = Regex.Replace(rhead, @"(.*?)(\()(.+?)(\))", "$3", RegexOptions.Compiled);
							string[] rowOptions = rheadOptions.Split(',');
							string tdwidth = "";
							string tdheight = "";
							string tdalign = "";

							for(int n = 0; n < rowOptions.Length; n++)
							{
								if(rowOptions[n].ToUpper().StartsWith("W"))
									tdwidth = " width=\"" + rowOptions[n].Substring(1, rowOptions[n].Length - 1) + "\"";
								else if(rowOptions[n].ToUpper().StartsWith("H"))
									tdheight = " height=\"" + rowOptions[n].Substring(1, rowOptions[n].Length - 1) + "\"";
								else if(rowOptions[n].ToUpper().StartsWith("L"))
									tdalign = " align=\"" + rowOptions[n].Substring(0, rowOptions[n].Length) + "\"";
								else if(rowOptions[n].ToUpper().StartsWith("C"))
									tdalign = " align=\"" + rowOptions[n].Substring(0, rowOptions[n].Length) + "\"";
								else if(rowOptions[n].ToUpper().StartsWith("R"))
									tdalign = " align=\"" + rowOptions[n].Substring(0, rowOptions[n].Length) + "\"";
							}
							result.Append("<td valign=\"top\" class=\"" + tdclass + "\"" + tdwidth + tdheight + tdalign + ">");
							for(int y = 1; y < colItems.Length; y++)
							{
								string line = colItems[y];
								this.FormatStandardWikiLine(appPath, wikiName, ref line, topic, hideLinks);
								result.Append(line + "\n");
							}
							result.Append("</td>\n");
						}
						else
							return "<div><br><b>@@@@@@@@@ TABLE FORMATTING ERROR - EACH COLUMN MUST HAVE A STYLE: @@, ||, or ##</b><br></div>";

					}
					//close up row
					result.Append("</tr>\n");
				}
				result.Append("</table>\n");
				return result.ToString();
			}
		}

		private static string FormatCodeWikiText(string appPath, string wikiName, string topic, string wikitext)
		{
			//TODO - color code it for HTML and C# and Javascript
			//Parser.HTMLEncodeSimple(ref wikitext);
			return "\n<pre>" + wikitext + "</pre>\n";  //TODO - use style/class courier/courier new
		}

		private string FormatPlainWikiText(string appPath, string wikiName, string topic, string wikitext)
		{
			bool hideLinks = false;
			return FormatPlainWikiText(appPath, wikiName, topic, wikitext, hideLinks);
		}																												  
		private string FormatPlainWikiText(string appPath, string wikiName, string topic, string wikitext, bool hideLinks)
		{
			wikitext = wikitext.Replace("\r", "");
			string [] Lines = wikitext.Split (new char[] {'\n'});
			StringBuilder result = new StringBuilder(7);
			foreach(string wikiline in Lines)
			{
				if(wikiline != "")  //only parse lines with text or return character - this eliminates any trailing empty lines
				{
					string theLine = wikiline; //.Replace("\r", "");  //drop the return character
					//HTMLEncodeSimple (ref theLine);  //convert all   &  <  >   characters  //TODO - consider allowing HTML
					if (theLine.Trim() == "")
						theLine = theLine = "<div>&nbsp;</div>\n";  //add div as a blank line //theLine.Trim(); // 
					else
					{
						this.FormatStandardWikiLine(appPath, wikiName, ref theLine, topic, hideLinks);
						//last act - if it does not end with blockquote, surround with div tags
						if (theLine.EndsWith("</blockquote>"))
							theLine += "\n";
						else
							theLine = "<div>" + theLine + "</div>\n";

					}
					result.Append(theLine);
				}
			}			
			return result.ToString();
		}

		public void FormatStandardWikiLine(string appPath, string wikiName, ref string theLine, string topic, bool hideLinks)
		{
			//format line - if starts with ; - then apply no formatting
			if(theLine.StartsWith(";"))
				theLine = theLine.Substring(1, theLine.Length - 1); //just remove the comment char
			else
			{
				if(!hideLinks)
					FormatWikiLinks(appPath, wikiName, topic, ref theLine);
				FormatHorizontalRule(ref theLine);
				FormatBoldItalic(ref theLine);
				FormatBoldItalicUnderlineWord(ref theLine);
				FormatHeading(ref theLine);  //now includes numbering
				FormatIndent(ref theLine);
			}

		}

		#region Preset Format Directives
		
		//instance members for preset format directives
		private bool fdDoNumbers = true;

		private void ProcessPresetFormatDirectives(string preLine)
		{
			//preLine = "{{nonumbers}}	  or {{mydirective}{nonumbers}}
			if(preLine.IndexOf("{nonumbers}") > 0)
			{
				this.fdDoNumbers = false;
			}
		}
		
		#endregion


		public ArrayList SegmentWikiText(string wikitext)
		{
			wikitext = wikitext.Replace("\r", "");
			ArrayList segList = new ArrayList();
			StringBuilder blocktext = new StringBuilder();
			WikiTextBlockType blockType = WikiTextBlockType.Plain;  //start with the assumption of plain text
			string[] Lines = wikitext.Split(new char[] {'\n'});

			//Process Preset Format Directives
			if(Lines.Length > 0)
			{
				for( int i = 0; i < Lines.Length; i++)
				{
					string rline = Lines[i].Trim();
					if(Regex.IsMatch(rline, @"^\{\{(.*?)\}\}$"))
					{
						ProcessPresetFormatDirectives(rline.ToLower());
						Lines[i] = ""; //remove directives
					}
				}
			}


			//number header lines if more than 3 - loop to count, then loop to add numbers if greater than 3
			if(this.fdDoNumbers)
			{
				int hdCount = 0;
				for( int i = 0; i < Lines.Length; i++)
				{
					if(Regex.IsMatch(Lines[i], "^\\={2}(.*?)\\={2}$"))
						hdCount++;
				}
				if(hdCount > Config.HeaderThreshold)
				{
					//do count
					int h1 = 0;
					int h2 = 0;
					int h3 = 0;
					int curLevel = 1;
					for( int i = 0; i < Lines.Length; i++)
					{
						if(Regex.IsMatch(Lines[i], "^\\={4}(.*?)\\={4}$"))
						{
							h3++;
							curLevel = 3;
							Lines[i] = AddLineNumber(Lines[i], h1, h2, h3);
						}
						else if(Regex.IsMatch(Lines[i], "^\\={3}(.*?)\\={3}$"))
						{
							if(curLevel == 3) h3 = 0;
							h2++;
							curLevel = 2;
							Lines[i] = AddLineNumber(Lines[i], h1, h2, h3);
						}
						else if(Regex.IsMatch(Lines[i], "^\\={2}(.*?)\\={2}$"))
						{
							if(curLevel == 2 || curLevel == 3)
							{
								h2 = 0;
								h3 = 0;
							}
							h1++;
							curLevel = 1;
							Lines[i] = AddLineNumber(Lines[i], h1, h2, h3);
						}
					}
				}
			}

			/*
++++(W360,H100%,B1)
@@ 
Here is a the first table data row. With column header style.
@@ 
Here is the second column with header style.
====
##       
Here is the first column data
## (W60,H90) 
Here is the second column data with width=60 and height=90
====
## 
Here is the first column data
## 
Here is the second column data 
====
## 
Here is the first column data
## 
Here is the second column data 
====
## 
Here is a the first table data row. With column footer style.
## 
Here is the second column with footer style. 
++++

			*/
			// parse each line of text individually
			foreach(string wline in Lines)
			{
				string wikiline = wline; //.Replace("\r", "");
				if(wikiline.StartsWith("::code::"))
				{
					if (blockType == WikiTextBlockType.Plain || blockType == WikiTextBlockType.List || blockType == WikiTextBlockType.Table)
					{
						//begins a new list type block, so we must end the previous block if it has anything in it
						if(blocktext.Length > 0)
						{
							WikiTextBlock block = new WikiTextBlock(blocktext.ToString(), blockType);
							segList.Add(block);
							blocktext = new StringBuilder();
						}
						blockType = WikiTextBlockType.Code;  // toggle flag for code mode - starts the preformatted block
					}
					else
					{
						//this is the end of the code block, so add it to the return value
						if(blocktext.Length > 0)
						{
							WikiTextBlock block = new WikiTextBlock(blocktext.ToString(), blockType);
							segList.Add(block);
							blocktext = new StringBuilder(3);  //reset to new string builder object
						}
						blockType = WikiTextBlockType.Plain;  //toggle back to plain
					}
				}
				else if(wikiline.StartsWith("++++")) //table block
				{
					if (blockType == WikiTextBlockType.Plain || blockType == WikiTextBlockType.List || blockType == WikiTextBlockType.Code)
					{
						//begins a new list type block, so we must end the previous block if it has anything in it
						if(blocktext.Length > 0)
						{
							WikiTextBlock block = new WikiTextBlock(blocktext.ToString(), blockType);
							segList.Add(block);
							blocktext = new StringBuilder();
						}
						blockType = WikiTextBlockType.Table;  // toggle flag for table - starts the table block
						
						//include first line in block
						wikiline += "\n";
						blocktext.Append(wikiline); //add current line to existing block
					}
					else
					{
						//this is the end of the code block, so add it to the return value
						if(blocktext.Length > 0)
						{
							//include last line in block
							wikiline += "\n";
							blocktext.Append(wikiline); //add current line to existing block

							WikiTextBlock block = new WikiTextBlock(blocktext.ToString(), blockType);
							segList.Add(block);
							blocktext = new StringBuilder(3);  //reset to new string builder object
						}
						blockType = WikiTextBlockType.Plain;  //toggle back to plain
					}
				}
				else
				{
					//not a ::code:: toggle line, so check for block switch and append the line to the current block string builder

					//match list line format: 
					//line begins with two or more spaces and then a * or # and then a space
					Match m = Regex.Match(wikiline, @"(^\s[\s]+)(\#|\*)(\s)", RegexOptions.Compiled);

					bool isListLine = m.Length > 0;  //handle lists or plain block

					if(blockType == WikiTextBlockType.List)
					{
						if(!isListLine)
						{
							if(blocktext.Length > 0)
							{
								WikiTextBlock block = new WikiTextBlock(blocktext.ToString(), blockType);
								segList.Add(block);
								blocktext = new StringBuilder(3);  //reset to new string builder object
							}
							blockType = WikiTextBlockType.Plain;;  //now we have just ended our list
						}
					}
					else
					{
						if(isListLine)
						{
							//this is the first list line, so close the current block and switch to list mode
							if(blocktext.Length > 0)
							{
								WikiTextBlock block = new WikiTextBlock(blocktext.ToString(), blockType);
								segList.Add(block);
								blocktext = new StringBuilder(3);  //reset to new string builder object
							}
							blockType = WikiTextBlockType.List;  //this is the first line in a list - start list and add to listQ and indentLevel, etc.
						}
					}
					wikiline += "\n";
					blocktext.Append(wikiline); //add current line to existing block
				}		
			}
			if(blocktext.Length > 0) //handle last block
			{
				WikiTextBlock block = new WikiTextBlock(blocktext.ToString(), blockType);
				segList.Add(block);
			}
			return segList;
		}

		private static string AddLineNumber(string line, int h1, int h2, int h3)
		{
			//0.0.0 = 0
			//1.0.0 = 1
			//1.1.0 = 1.1
			//0.2.2 = 0.2.2

			string num = h1.ToString();
			if(h2 > 0 || (h2 == 0 && h3 > 0)) num += "." + h2.ToString();
			if(h3 > 0) num += "." + h3.ToString();
			num += " &nbsp;&nbsp; "; //add trailing space

			if(line.StartsWith("===="))
				line = line.Insert(4, num);
			else if(line.StartsWith("==="))
				line = line.Insert(3, num);
			else if(line.StartsWith("=="))
				line = line.Insert(2, num);

			return line;
		}

		private static void FormatHorizontalRule(ref string line)
		{
			line = Regex.Replace(line, "^-{4,}", "<hr>", RegexOptions.Compiled);
		}

		private static void FormatBoldItalic(ref string line)
		{
			// '''text''' = bold,  ''text'' = italic
			line = Regex.Replace(line, "'{3}(.*?)'{3}", "<strong>$1</strong>", RegexOptions.Compiled);
			line = Regex.Replace(line, "'{2}(.*?)'{2}", "<em>$1</em>", RegexOptions.Compiled);
		}

		private static void FormatBoldItalicUnderlineWord(ref string line)
		{
			// bold = *word*
			line = Regex.Replace(line, "(^|\\s)\\*(\\S*)\\*(\\s|$|\\.|,|:|;|\\?|\\!)", "$1<strong>$2</strong>$3", RegexOptions.Compiled);
			// italic = /word/
			line = Regex.Replace(line, "(^|\\s)/(\\S*)/(\\s|$|\\.|,|:|;|\\?|\\!)", "$1<em>$2</em>$3", RegexOptions.Compiled);
			// underline = _word_
			line = Regex.Replace(line, "(^|\\s)_(\\S*)_(\\s|$|\\.|,|:|;|\\?|\\!)", "$1<u>$2</u>$3", RegexOptions.Compiled);
		}

		private static void FormatHeading(ref string line)
		{
			// Format <H1>, <H2> amd <H3> headings  - word or phrase surrounded by ==, ===, or ====
			// Heading 3
			line = Regex.Replace(line, "^\\={4}(.*?)\\={4}$", "<div class=\"hd3\">$1</div>", RegexOptions.Compiled);
			// Heading 2
			line = Regex.Replace(line, "^\\={3}(.*?)\\={3}$", "<div class=\"hd2\">$1</div>", RegexOptions.Compiled);
			// Heading 1
			line = Regex.Replace(line, "^\\={2}(.*?)\\={2}$", "<div class=\"hd1\">$1</div>", RegexOptions.Compiled);
		}

		private static void FormatIndent(ref string theLine)
		{
			//surround with div class=ind1, ind2, or ind3
			if (theLine.StartsWith(": "))
				theLine = "<div class=\"ind1\">" + theLine.Substring(2, theLine.Length - 2) + "</div>";
			else if (theLine.StartsWith(":: "))
				theLine = "<div class=\"ind2\">" + theLine.Substring(3, theLine.Length - 3) + "</div>";
			else if (theLine.StartsWith("::: "))
				theLine = "<div class=\"ind3\">" + theLine.Substring(4, theLine.Length - 4) + "</div>";
		}

		public string ConvertWikiLinksToHTML(string appPath, string wikiName, string wikiTopic, string wikiText)
		{
			FormatWikiLinks(appPath, wikiName, wikiTopic, ref wikiText);
			return wikiText;
		}

		private static void FormatWikiLinks(string appPath, string wikiName, string topic, ref string line)
		{
			// format new links style with images, files, topics, http, ftp, mail

			MatchCollection wikiTopics = Parser.LinkRx.Matches(line);
			foreach (Match m in wikiTopics)
			{
				string mtxt = m.ToString();
				WikiTopicLink link = new WikiTopicLink(wikiName, mtxt);

				string preImg = "";
				if(link.IsParent)
					preImg = "<img src=\"" + appPath + "/images/parent.gif\" border=\"0\" alt=\"This is a parent link.\" align=\"absmiddle\">";
				else if(link.IsChild)
					preImg = "<img src=\"" + appPath + "/images/child.gif\" border=\"0\" alt=\"This is a child link.\" align=\"absmiddle\">";

				string repLink = "";
				switch(link.Protocol)
				{
					case "file":
						repLink = "<a href=\"" + appPath + "/getfile.aspx?w=" + HttpUtility.HtmlEncode(link.WikiName) 
							+ "&f=" + HttpUtility.HtmlEncode(link.Topic) + "\" class=\"bodylink\">" + HttpUtility.HtmlEncode(link.LinkText) + "</a>";
						break;
					case "image":
						repLink = BuildImageHtml(appPath, link);
						break;
					case "imagelink":
						repLink = BuildImageLink(preImg, appPath, link);
						break;
					case "http":
					case "https":
					case "ftp":
					case "mailto":
						repLink = "<a href=\"" + HttpUtility.HtmlEncode(link.Topic) + "\" target=\"_blank\" class=\"bodylink\">" 
							+ HttpUtility.HtmlEncode(link.LinkText) + "</a>";
						break;
					case "":
					case "talk":
					default:
						if(link.Topic.ToLower() == topic.ToLower() && link.WikiName.ToLower() == wikiName.ToLower())
							repLink = "<b>" + link.LinkText + "</b>";
						else
						{
							WikiKey key = new WikiKey(link.WikiName, link.Topic);
							string lkclass = "nolink";
							if(TopicManager.TopicIndex[key] != null)
								lkclass = "bodylink";
							string talkVal = "";
							if(link.Protocol == "talk") talkVal = "?talk";
							repLink = "<a href=\"" + appPath + "/wiki/" + link.WikiName + "/" + link.Topic + ".wiki" + talkVal 
								+ "\" class=\"" + lkclass + "\">" + preImg + link.LinkText + "</a>";
						}
						break;
				}
				line = line.Replace(mtxt, repLink);
			}
		}

		private static string BuildImageHtml(string appPath, WikiTopicLink link)
		{
			//all options come into account here
			//link.File
			//link.FileWiki
			//link.Align
			//link.Caption
			//link.Frame  ----
			//link.LinkText
			//link.IsChild
			//link.IsParent
			//link.Thumb -----
			//link.Width
			//link.WikiName

			//<div style="float:right;"><img src="images/logo.gif" border="0"></div>
			string divclass = "imgmid";
			if(link.Align == "right") divclass = "imgright";
			if(link.Align == "left")  divclass = "imgleft";
			if(link.Frame) divclass += "fr"; //imgmidfr, imgrightfr, imgleftfr

			string divtag = "<div class=\"" + divclass + "\">";

			string width = "";
			if(link.Thumb) width = " width=\"100px\"";
			if(link.Width.Length > 0) width = " width=\"" + link.Width + "\"";


			string repLink = "<img src=\"" + appPath + "/getimage.aspx?w=" + HttpUtility.HtmlEncode(link.WikiName) 
				+ "&f=" + HttpUtility.HtmlEncode(link.Topic) + "\" border=\"0\"" + width + ">";

			if(link.Thumb)
				repLink = "<a href=\"" + appPath + "/getimage.aspx?w=" + HttpUtility.HtmlEncode(link.WikiName) 
					+ "&f=" + HttpUtility.HtmlEncode(link.File) + "\" target=\"_blank\" border=\"0\">" + repLink + "</a>";

			if(link.Frame && link.Caption.Length > 0)
				repLink += "<br><div class=\"caption\">" + link.Caption + "</div>";

			repLink = divtag + repLink + "</div>";

			return repLink;
		}

		private static string BuildImageLink(string preImg, string appPath, WikiTopicLink link)
		{
			//all options come into account here
			string divclass = "imgmid";
			if(link.Align == "right") divclass = "imgright";
			if(link.Align == "left")  divclass = "imgleft";
			if(link.Frame) divclass += "fr"; //imgmidfr, imgrightfr, imgleftfr

			string divtag = "<div class=\"" + divclass + "\">";

			string width = "";
			if(link.Thumb) width = " width=\"100px\"";
			if(link.Width.Length > 0) width = " width=\"" + link.Width + "\"";

			string repLink = "<img src=\"" + appPath + "/getimage.aspx?w=" + HttpUtility.HtmlEncode(link.FileWiki) 
				+ "&f=" + HttpUtility.HtmlEncode(link.File) + "\" border=\"0\"" + width + ">";

			WikiKey key = new WikiKey(link.WikiName, link.Topic);
			string lkclass = "nolink";
			if(TopicManager.TopicIndex[key] != null)
				lkclass = "bodylink";

			//make link to topic - check for outside link - target=_blank
			if( link.IsOutsideLink )
				repLink = "<a href=\"" + link.Topic + "\" class=\"bodylink\" target=\"_blank\">" + repLink;
			else
				repLink = "<a href=\"" + appPath + "/wiki/" + link.WikiName + "/" + link.Topic + ".wiki\" class=\"" + lkclass + "\">" + repLink;

			if(link.Frame)
				repLink += "<br><div class=\"caption\">" + preImg + link.LinkText + "</div>";

			repLink += "</a>"; //finish link to include caption if included

			repLink = divtag + repLink + "</div>";

			return repLink;
		}

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Since 2001 I've been writing .NET applications in C# and architecting n-tier applications in the enterprise. Before that I worked as a tech writer for nine years. Don't bother doing the math. I'm old. Ever since I laid eyes on my first Commodore PET, I've been a technologist. I've worked in the software world for fifteen years. I started as a technical writer and learned to code from the best engineers as I worked with them in creating technical documentation. It was then that I learned that writing code was more fun and frankly easier than writing about code. I've been doing both ever since. You can visit my blog at http://www.tsjensen.com/blog.

Comments and Discussions