using System; using System.Configuration; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.SessionState; using System.Diagnostics; using ScrewTurn.Wiki.PluginFramework; namespace ScrewTurn.Wiki { /// /// Performs all the text formatting and parsing operations. /// public static class Formatter { private static readonly Regex NoWikiRegex = new Regex(@"\(.|\n|\r)+?\<\/nowiki\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex NoSingleBr = new Regex(@"\(.|\n|\r)+?\<\/nobr\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex LinkRegex = new Regex(@"(\[\[.+?\]\])|(\[.+?\])", RegexOptions.Compiled); private static readonly Regex RedirectionRegex = new Regex(@"^\ *\>\>\>\ *.+\ *$", RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex H1Regex = new Regex(@"^==.+?==\n?", RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex H2Regex = new Regex(@"^===.+?===\n?", RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex H3Regex = new Regex(@"^====.+?====\n?", RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex H4Regex = new Regex(@"^=====.+?=====\n?", RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex BoldRegex = new Regex(@"'''.+?'''", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex ItalicRegex = new Regex(@"''.+?''", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex BoldItalicRegex = new Regex(@"'''''.+?'''''", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex UnderlinedRegex = new Regex(@"__.+?__", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex StrikedRegex = new Regex(@"(?).+?\-\-)(?!(\>|\>))", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex CodeRegex = new Regex(@"\{\{.+?\}\}", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex PreRegex = new Regex(@"\{\{\{\{.+?\}\}\}\}", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex BoxRegex = new Regex(@"\(\(\(.+?\)\)\)", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex ExtendedUpRegex = new Regex(@"\{up((\:|\().+?)?\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); //private static readonly Regex UpRegex = new Regex(@"\{up\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex SpecialTagRegex = new Regex(@"\{(wikititle|wikiversion|mainurl|rsspage|themepath|clear|br|top|searchbox|pagecount|cloud|orphans|wanted|namespacelist)\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); //private static readonly Regex CloudRegex = new Regex(@"\{cloud\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); //private static readonly Regex NamespaceRegex = new Regex(@"\{namespace\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); //private static readonly Regex NamespaceListRegex = new Regex(@"\{namespacelist\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex Phase3SpecialTagRegex = new Regex(@"\{(username|pagename|loginlogout|namespace|namespacedropdown|incoming|outgoing)\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex RecentChangesRegex = new Regex(@"\{recentchanges(\(\*\))?\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex ListRegex = new Regex(@"(?<=(\n|^))((\*|\#)+(\ )?.+?\n)+((?=\n)|\z)", RegexOptions.Compiled | RegexOptions.Singleline); // Singleline to matche list elements on multiple lines private static readonly Regex TocRegex = new Regex(@"\{toc\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex TransclusionRegex = new Regex(@"\{T(\:|\|).+\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex HRRegex = new Regex(@"(?<=(\n|^))(\ )*----(\ )*\n", RegexOptions.Compiled); //private static readonly Regex SnippetRegex = new Regex(@"\{S(\:|\|)(.+?)(\|(.+?))*}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex SnippetRegex = new Regex(@"\{s\:(.+?)(\|.*?)*\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); private static readonly Regex ClassicSnippetVerifier = new Regex(@"\|\ ?[a-z0-9]+\ ?\=", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex TableRegex = new Regex(@"\{\|(\ [^\n]*)?\n.+?\|\}", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex IndentRegex = new Regex(@"(?<=(\n|^))\:+(\ )?.+?\n", RegexOptions.Compiled); private static readonly Regex EscRegex = new Regex(@"\(.|\n|\r)*?\<\/esc\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); private static readonly Regex SignRegex = new Regex(@"§§\(.+?\)§§", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex FullCodeRegex = new Regex(@"@@.+?@@", RegexOptions.Compiled | RegexOptions.Singleline); //private static readonly Regex UsernameRegex = new Regex(@"\{username\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private static readonly Regex JavascriptRegex = new Regex(@"\.*?\<\/script\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); private static readonly Regex CommentRegex = new Regex(@"(?[\s\n]*))\<\!\-\-.*?\-\-\>(?!([\s\n]*\<\/script\>))", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); /// /// The section editing button placeholder. /// public const string EditSectionPlaceHolder = "%%%%EditSectionPlaceHolder%%%%"; // This string is also used in History.aspx.cs private const string TocTitlePlaceHolder = "%%%%TocTitlePlaceHolder%%%%"; private const string UpReplacement = "GetFile.aspx?File="; private const string ExtendedUpReplacement = "GetFile.aspx?$File="; private const string ExtendedUpReplacementForAttachment = "GetFile.aspx?$Page=@&File="; private const string SingleBrPlaceHolder = "%%%%SingleBrPlaceHolder%%%%"; /// /// Detects the current namespace. /// /// The current page, if any. /// The current namespace (null for the root). private static NamespaceInfo DetectNamespaceInfo(PageInfo currentPage) { if(currentPage == null) { return Tools.DetectCurrentNamespaceInfo(); } else { string ns = NameTools.GetNamespace(currentPage.FullName); return Pages.FindNamespace(ns); } } /// /// Formats WikiMarkup, converting it into XHTML. /// /// The raw WikiMarkup text. /// A value indicating whether the formatting is being done for content indexing. /// The formatting context. /// The current Page (can be null). /// The formatted text. public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current) { string[] tempLinks; return Format(raw, forIndexing, context, current, out tempLinks); } /// /// Formats WikiMarkup, converting it into XHTML. /// /// The raw WikiMarkup text. /// A value indicating whether the formatting is being done for content indexing. /// The formatting context. /// The current Page (can be null). /// The linked pages, both existent and inexistent. /// The formatted text. public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current, out string[] linkedPages) { return Format(raw, forIndexing, context, current, out linkedPages, false); } /// /// Formats WikiMarkup, converting it into XHTML. /// /// The raw WikiMarkup text. /// A value indicating whether the formatting is being done for content indexing. /// The formatting context. /// The current Page (can be null). /// The linked pages, both existent and inexistent. /// A value indicating whether to format in bare-bones mode (for WYSIWYG editor). /// The formatted text. public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current, out string[] linkedPages, bool bareBones) { // Bare Bones: Advanced tags, such as tables, toc, special tags, etc. are not formatted - used for Visual editor display linkedPages = new string[0]; List tempLinkedPages = new List(10); StringBuilder sb = new StringBuilder(raw); Match match; string tmp, a, n, url, title, bigUrl; StringBuilder dummy; // Used for temporary string manipulation inside formatting cycles bool done = false; List noWikiBegin = new List(), noWikiEnd = new List(); int end = 0; List hPos = new List(); sb.Replace("\r", ""); bool addedNewLineAtEnd = false; if(!sb.ToString().EndsWith("\n")) { sb.Append("\n"); // Very important to make Regular Expressions work! addedNewLineAtEnd = true; } // Remove all double- or single-LF in JavaScript tags bool singleLine = Settings.ProcessSingleLineBreaks; match = JavascriptRegex.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); if(singleLine) sb.Insert(match.Index, match.Value.Replace("\n", "")); else sb.Insert(match.Index, match.Value.Replace("\n\n", "\n")); match = JavascriptRegex.Match(sb.ToString(), match.Index + 1); } // Remove empty NoWiki and NoBr tags sb.Replace("", ""); sb.Replace("", ""); ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); // Before Producing HTML match = FullCodeRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); string content = match.Value.Substring(2, match.Length - 4); dummy = new StringBuilder(); dummy.Append("
");
					// IE needs \r\n for line breaks
					dummy.Append(EscapeWikiMarkup(content).Replace("\n", "\r\n"));
					dummy.Append("
"); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = FullCodeRegex.Match(sb.ToString(), end); } if(current != null) { // Check redirection match = RedirectionRegex.Match(sb.ToString()); if(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); string destination = match.Value.Trim().Substring(4).Trim(); while(destination.StartsWith("[") && destination.EndsWith("]")) { destination = destination.Substring(1, destination.Length - 2); } while(sb[match.Index] == '\n' && match.Index < sb.Length - 1) sb.Remove(match.Index, 1); PageInfo dest = Pages.FindPage(destination); if(dest != null) { Redirections.AddRedirection(current, dest); } } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); } } // No more needed (Striked Regex modified) // Temporarily "escape" comments //sb.Replace("", "(^_$)"); ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); // Before Producing HTML if(!bareBones) { match = EscRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, match.Value.Substring(5, match.Length - 11).Replace("<", "<").Replace(">", ">")); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = EscRegex.Match(sb.ToString(), end); } } // Snippets and tables processing was here if(!bareBones) { match = IndentRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, BuildIndent(match.Value) + "\n"); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = IndentRegex.Match(sb.ToString(), end); } } // Process extended UP before standard UP match = ExtendedUpRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { EncodeFilename(sb, match.Index + match.Length); sb.Remove(match.Index, match.Length); string prov = match.Groups[1].Value.StartsWith(":") ? match.Value.Substring(4, match.Value.Length - 5) : match.Value.Substring(3, match.Value.Length - 4); string page = null; // prov - Full.Provider.Type.Name(PageName) // (PageName) is optional, but it can contain brackets, for example (Page(WithBrackets)) if(prov.EndsWith(")") && prov.Contains("(")) { page = prov.Substring(prov.IndexOf("(") + 1); page = page.Substring(0, page.Length - 1); page = Tools.UrlEncode(page); prov = prov.Substring(0, prov.IndexOf("(")); } if(page == null) { // Normal file sb.Insert(match.Index, ExtendedUpReplacement.Replace("$", (prov != "") ? "Provider=" + prov + "&" : "")); } else { // Page attachment sb.Insert(match.Index, ExtendedUpReplacementForAttachment.Replace("$", (prov != "")? "Provider=" + prov + "&" : "").Replace("@", page)); } } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = ExtendedUpRegex.Match(sb.ToString(), end); } /*match = UpRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, UpReplacement); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = UpRegex.Match(sb.ToString(), end); }*/ if(!bareBones) { match = SpecialTagRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); switch(match.Value.Substring(1, match.Value.Length - 2).ToUpperInvariant()) { case "WIKITITLE": sb.Insert(match.Index, Settings.WikiTitle); break; case "WIKIVERSION": sb.Insert(match.Index, Settings.WikiVersion); break; case "MAINURL": sb.Insert(match.Index, Settings.MainUrl); break; case "RSSPAGE": if(current != null) { sb.Insert(match.Index, @""); } break; case "THEMEPATH": sb.Insert(match.Index, Settings.GetThemePath(Tools.DetectCurrentNamespace())); break; case "CLEAR": sb.Insert(match.Index, @"
"); break; case "BR": //if(!AreSingleLineBreaksToBeProcessed()) sb.Insert(match.Index, "
"); sb.Insert(match.Index, "
"); break; case "TOP": sb.Insert(match.Index, @"" + Exchanger.ResourceExchanger.GetResource("Top") + ""); break; case "SEARCHBOX": string textBoxId = "SB" + Guid.NewGuid().ToString("N"); NamespaceInfo ns = DetectNamespaceInfo(current); string nsstring = ns != null ? NameTools.GetFullName(ns.Name, "Search") + ".aspx" : "Search.aspx"; string doSearchFunction = ""; sb.Insert(match.Index, doSearchFunction + @" »"); break; case "CLOUD": string cloud = BuildCloud(DetectNamespaceInfo(current)); sb.Insert(match.Index, cloud); break; case "PAGECOUNT": sb.Insert(match.Index, Pages.GetPages(DetectNamespaceInfo(current)).Count.ToString()); break; case "ORPHANS": if(!forIndexing) sb.Insert(match.Index, BuildOrphanedPagesList(DetectNamespaceInfo(current), context, current)); break; case "WANTED": sb.Insert(match.Index, BuildWantedPagesList(DetectNamespaceInfo(current))); break; case "NAMESPACELIST": sb.Insert(match.Index, BuildNamespaceList()); break; } } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = SpecialTagRegex.Match(sb.ToString(), end); } } match = ListRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); int d = 0; try { string[] lines = match.Value.Split('\n'); // Inline multi-line list elements List tempLines = new List(lines); for(int i = tempLines.Count - 1; i >= 1; i--) { // Skip first line string trimmedLine = tempLines[i].Trim(); if(!trimmedLine.StartsWith("*") && !trimmedLine.StartsWith("#")) { //if(i != tempLines.Count - 1 && tempLines[i].Length > 0) { trimmedLine = "
" + trimmedLine; tempLines[i - 1] += trimmedLine; //} tempLines.RemoveAt(i); } } lines = tempLines.ToArray(); sb.Insert(match.Index, GenerateList(lines, 0, 0, ref d) + "\n"); } catch { sb.Insert(match.Index, @"FORMATTER ERROR (Malformed List)"); } } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = ListRegex.Match(sb.ToString(), end); } match = HRRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, @"

" + "\n"); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = HRRegex.Match(sb.ToString(), end); } // Replace \n with BR was here ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); // Transclusion (intra-Wiki) if(!bareBones) { match = TransclusionRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); PageInfo info = Pages.FindPage(match.Value.Substring(3, match.Value.Length - 4)); if(info != null && info != current) { // Avoid circular transclusion! dummy = new StringBuilder(); dummy.Append(@"
"); dummy.Append(FormattingPipeline.FormatWithPhase1And2(Content.GetPageContent(info, true).Content, forIndexing, FormattingContext.TranscludedPageContent, info)); dummy.Append("
"); sb.Insert(match.Index, dummy.ToString()); } else sb.Insert(match.Index, @"FORMATTER ERROR (Transcluded inexistent page or this same page)"); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = TransclusionRegex.Match(sb.ToString(), end); } } List attachments = new List(); // Links and images match = LinkRegex.Match(sb.ToString()); while(match.Success) { if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { match = LinkRegex.Match(sb.ToString(), end); continue; } // [], [[]] and [[] can occur when empty links are processed if(match.Value.Equals("[]") || match.Value.Equals("[[]]") || match.Value.Equals("[[]")) { end += 2; match = LinkRegex.Match(sb.ToString(), end); continue; // Prevents formatting emtpy links } done = false; if(match.Value.StartsWith("[[")) tmp = match.Value.Substring(2, match.Length - 4).Trim(); else tmp = match.Value.Substring(1, match.Length - 2).Trim(); sb.Remove(match.Index, match.Length); a = ""; n = ""; if(tmp.IndexOf("|") != -1) { // There are some fields string[] fields = tmp.Split('|'); if(fields.Length == 2) { // Link with title a = fields[0]; n = fields[1]; } else { StringBuilder img = new StringBuilder(); // Image if(fields[0].ToLowerInvariant().Equals("imageleft") || fields[0].ToLowerInvariant().Equals("imageright") || fields[0].ToLowerInvariant().Equals("imageauto")) { string c = ""; switch(fields[0].ToLowerInvariant()) { case "imageleft": c = "imageleft"; break; case "imageright": c = "imageright"; break; case "imageauto": c = "imageauto"; break; default: c = "image"; break; } title = fields[1]; url = fields[2]; if(fields.Length == 4) bigUrl = fields[3]; else bigUrl = ""; url = EscapeUrl(url); // bigUrl = EscapeUrl(bigUrl); The url is already escaped by BuildUrl if(c.Equals("imageauto")) { img.Append(@"
"); } else { img.Append(@"
"); } if(bigUrl.Length > 0) { dummy = new StringBuilder(200); dummy.Append(@" 0) dummy.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); else dummy.Append(Exchanger.ResourceExchanger.GetResource("Image")); dummy.Append(@""" />"); img.Append(BuildLink(bigUrl, dummy.ToString(), true, title, forIndexing, bareBones, context, null, tempLinkedPages)); } else { img.Append(@" 0) img.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); else img.Append(Exchanger.ResourceExchanger.GetResource("Image")); img.Append(@""" />"); } if(title.Length > 0 && !title.StartsWith("#")) { img.Append(@"

"); img.Append(title); img.Append("

"); } if(c.Equals("imageauto")) { img.Append("
"); } else { img.Append(""); } sb.Insert(match.Index, img); } else if(fields[0].ToLowerInvariant().Equals("image")) { title = fields[1]; url = fields[2]; if(fields.Length == 4) bigUrl = fields[3]; else bigUrl = ""; url = EscapeUrl(url); // bigUrl = EscapeUrl(bigUrl); The url is already escaped by BuildUrl if(bigUrl.Length > 0) { dummy = new StringBuilder(); dummy.Append(@" 0) dummy.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); else dummy.Append(Exchanger.ResourceExchanger.GetResource("Image")); dummy.Append(@""" />"); img.Append(BuildLink(bigUrl, dummy.ToString(), true, title, forIndexing, bareBones, context, null, tempLinkedPages)); } else { img.Append(@" 0) img.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); else img.Append(Exchanger.ResourceExchanger.GetResource("Image")); img.Append(@""" />"); } sb.Insert(match.Index, img.ToString()); } else { sb.Insert(match.Index, @"FORMATTER ERROR (Malformed Image Tag)"); } done = true; } } else if(tmp.ToLowerInvariant().StartsWith("attachment:")) { // This is an attachment done = true; string f = tmp.Substring("attachment:".Length); if(f.StartsWith("{up}")) f = f.Substring(4); if(f.ToLowerInvariant().StartsWith(UpReplacement.ToLowerInvariant())) f = f.Substring(UpReplacement.Length); attachments.Add(HttpContext.Current.Server.UrlDecode(f)); // Remove all trailing \n, so that attachments have no effect on the output in any case while(sb[match.Index] == '\n' && match.Index < sb.Length - 1) { sb.Remove(match.Index, 1); } } else { a = tmp; n = ""; } if(!done) { sb.Insert(match.Index, BuildLink(a, n, false, "", forIndexing, bareBones, context, current, tempLinkedPages)); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = LinkRegex.Match(sb.ToString(), end); } match = BoldItalicRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder(""); dummy.Append(match.Value.Substring(5, match.Value.Length - 10)); dummy.Append(""); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = BoldItalicRegex.Match(sb.ToString(), end); } match = BoldRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder(""); dummy.Append(match.Value.Substring(3, match.Value.Length - 6)); dummy.Append(""); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = BoldRegex.Match(sb.ToString(), end); } match = ItalicRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder(""); dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); dummy.Append(""); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = ItalicRegex.Match(sb.ToString(), end); } match = UnderlinedRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder(""); dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); dummy.Append(""); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = UnderlinedRegex.Match(sb.ToString(), end); } match = StrikedRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder(""); dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); dummy.Append(""); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = StrikedRegex.Match(sb.ToString(), end); } match = PreRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder("
");
					// IE needs \r\n for line breaks
					dummy.Append(match.Value.Substring(4, match.Value.Length - 8).Replace("\n", "\r\n"));
					dummy.Append("
"); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = PreRegex.Match(sb.ToString(), end); } match = CodeRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder(""); dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); dummy.Append(""); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = CodeRegex.Match(sb.ToString(), end); } string h; // Hx: detection pass (used for the TOC generation and section editing) hPos = DetectHeaders(sb.ToString()); // Hx: formatting pass int count = 0; match = H4Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); h = match.Value.Substring(5, match.Value.Length - 10 - (match.Value.EndsWith("\n") ? 1 : 0)); dummy = new StringBuilder(200); dummy.Append(@"

"); dummy.Append(h); if(!bareBones && !forIndexing) { string id = BuildHAnchor(h, count.ToString()); BuildHeaderAnchor(dummy, id); } dummy.Append("

"); sb.Insert(match.Index, dummy.ToString()); count++; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H4Regex.Match(sb.ToString(), end); } match = H3Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); h = match.Value.Substring(4, match.Value.Length - 8 - (match.Value.EndsWith("\n") ? 1 : 0)); dummy = new StringBuilder(200); if(current != null && !bareBones && !forIndexing) dummy.Append(BuildEditSectionLink(count, current.FullName)); dummy.Append(@"

"); dummy.Append(h); if(!bareBones && !forIndexing) { string id = BuildHAnchor(h, count.ToString()); BuildHeaderAnchor(dummy, id); } dummy.Append("

"); sb.Insert(match.Index, dummy.ToString()); count++; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H3Regex.Match(sb.ToString(), end); } match = H2Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); h = match.Value.Substring(3, match.Value.Length - 6 - (match.Value.EndsWith("\n") ? 1 : 0)); dummy = new StringBuilder(200); if(current != null && !bareBones && !forIndexing) dummy.Append(BuildEditSectionLink(count, current.FullName)); dummy.Append(@"

"); dummy.Append(h); if(!bareBones && !forIndexing) { string id = BuildHAnchor(h, count.ToString()); BuildHeaderAnchor(dummy, id); } dummy.Append("

"); sb.Insert(match.Index, dummy.ToString()); count++; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H2Regex.Match(sb.ToString(), end); } match = H1Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); h = match.Value.Substring(2, match.Value.Length - 4 - (match.Value.EndsWith("\n") ? 1 : 0)); dummy = new StringBuilder(200); if(current != null && !bareBones && !forIndexing) dummy.Append(BuildEditSectionLink(count, current.FullName)); dummy.Append(@"

"); dummy.Append(h); if(!bareBones && !forIndexing) { string id = BuildHAnchor(h, count.ToString()); BuildHeaderAnchor(dummy, id); } dummy.Append("

"); sb.Insert(match.Index, dummy.ToString()); count++; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H1Regex.Match(sb.ToString(), end); } match = BoxRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); dummy = new StringBuilder(@"
"); dummy.Append(match.Value.Substring(3, match.Value.Length - 6)); dummy.Append("
"); sb.Insert(match.Index, dummy.ToString()); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = BoxRegex.Match(sb.ToString(), end); } string tocString = BuildToc(hPos); if(!bareBones && current != null) { match = TocRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); if(!forIndexing) sb.Insert(match.Index, tocString); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = TocRegex.Match(sb.ToString(), end); } } if(!bareBones) { match = SnippetRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { string balanced = null; try { // If the snippet is malformed this can explode balanced = ExpandToBalanceBrackets(sb, match.Index, match.Value); } catch { balanced = match.Value; } sb.Remove(match.Index, balanced.Length); if(balanced.IndexOf("}") == balanced.Length - 1) { // Single-level snippet string[] temp = null; sb.Insert(match.Index, Format(FormatSnippet(balanced, tocString), forIndexing, context, current, out temp, bareBones).Trim('\n')); if(temp != null) tempLinkedPages.AddRange(temp); } else { // Nested snippet int lastOpen = 0; int firstClosedAfterLastOpen = 0; do { lastOpen = balanced.LastIndexOf("{"); firstClosedAfterLastOpen = balanced.IndexOf("}", lastOpen + 1); if(firstClosedAfterLastOpen <= lastOpen) break; // Give up string internalSnippet = balanced.Substring(lastOpen, firstClosedAfterLastOpen - lastOpen + 1); balanced = balanced.Remove(lastOpen, firstClosedAfterLastOpen - lastOpen + 1); string formattedInternalSnippet = FormatSnippet(internalSnippet, tocString); string[] temp; formattedInternalSnippet = Format(formattedInternalSnippet, forIndexing, context, current, out temp, bareBones).Trim('\n'); if(temp != null) tempLinkedPages.AddRange(temp); balanced = balanced.Insert(lastOpen, formattedInternalSnippet); } while(lastOpen != -1); sb.Insert(match.Index, balanced); } } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = SnippetRegex.Match(sb.ToString(), end); } } if(!bareBones) { match = TableRegex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, BuildTable(match.Value)); } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = TableRegex.Match(sb.ToString(), end); } } // Strip out all comments if(!bareBones) { match = CommentRegex.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); match = CommentRegex.Match(sb.ToString(), match.Index + 1); } } // Remove tags if(!bareBones) { sb.Replace("", ""); sb.Replace("", ""); } ProcessLineBreaks(sb, bareBones); if(addedNewLineAtEnd) { if(sb.ToString().EndsWith("
")) sb.Remove(sb.Length - 6, 6); } // Append Attachments if(!bareBones && attachments.Count > 0) { sb.Append(@"
"); for(int i = 0; i < attachments.Count; i++) { sb.Append(@""); sb.Append(attachments[i]); sb.Append(""); if(i != attachments.Count - 1) sb.Append(" - "); } sb.Append("
"); } linkedPages = tempLinkedPages.ToArray(); return sb.ToString(); } /// /// Encodes a filename used in combination with {UP} tags. /// /// The buffer. /// The index where to start working. private static void EncodeFilename(StringBuilder buffer, int startIndex) { // 1. Find end of the filename (first pipe or closed square bracket) // 2. Encode the string string allData = buffer.ToString(); int endIndex = allData.IndexOfAny(new[] { '|', ']' }, startIndex); if(endIndex > startIndex) { int len = endIndex - startIndex; string value = Tools.UrlEncode(allData.Substring(startIndex, len)); buffer.Remove(startIndex, len); buffer.Insert(startIndex, value); } } /// /// Builds the anchor markup for a header. /// /// The string builder. /// The anchor ID. private static void BuildHeaderAnchor(StringBuilder buffer, string id) { if(Settings.EnableSectionAnchors) { buffer.Append(@""); } } /// /// Builds the recent changes list. /// /// A value indicating whether to build a list for all namespace or for just the current one. /// The current namespace. /// The formatting context. /// The current page, or null. /// The recent changes list HTML markup. private static string BuildRecentChanges(NamespaceInfo currentNamespace, bool allNamespaces, FormattingContext context, PageInfo currentPage) { List allChanges = new List(RecentChanges.GetAllChanges()); if(allChanges.Count == 0) return ""; // Sort by descending date/time allChanges.Reverse(); // Filter by namespace if(!allNamespaces) { allChanges.RemoveAll((c) => { NamespaceInfo ns = Pages.FindNamespace(NameTools.GetNamespace(c.Page)); return ns != currentNamespace; }); } return BuildRecentChangesTable(allChanges, context, currentPage); } /// /// Builds a table containing recent changes. /// /// The changes. /// The formatting context. /// The current page, or null. /// The table HTML. public static string BuildRecentChangesTable(IList allChanges, FormattingContext context, PageInfo currentPage) { int maxChanges = Math.Min(Settings.MaxRecentChangesToDisplay, allChanges.Count); StringBuilder sb = new StringBuilder(500); sb.Append(""); for(int i = 0; i < maxChanges; i++) { sb.AppendFormat("", i % 2 == 0 ? "tablerow" : "tablerowalternate"); sb.Append(""); sb.Append(""); } sb.Append("
"); sb.Append(Preferences.AlignWithTimezone(allChanges[i].DateTime).ToString(Settings.DateTimeFormat)); sb.Append(""); sb.Append(PrintRecentChange(allChanges[i], context, currentPage)); sb.Append("
"); return sb.ToString(); } /// /// Prints a recent change. /// /// The change. /// The formatting context. /// The current page, or null. /// The proper text to display. private static string PrintRecentChange(RecentChange change, FormattingContext context, PageInfo currentPage) { switch(change.Change) { case Change.PageUpdated: return Exchanger.ResourceExchanger.GetResource("UserUpdatedPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); case Change.PageRenamed: return Exchanger.ResourceExchanger.GetResource("UserRenamedPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); case Change.PageRolledBack: return Exchanger.ResourceExchanger.GetResource("UserRolledBackPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); case Change.PageDeleted: return Exchanger.ResourceExchanger.GetResource("UserDeletedPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); case Change.MessagePosted: return Exchanger.ResourceExchanger.GetResource("UserPostedMessage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintMessageLink(change, context, currentPage)); case Change.MessageEdited: return Exchanger.ResourceExchanger.GetResource("UserEditedMessage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintMessageLink(change, context, currentPage)); case Change.MessageDeleted: return Exchanger.ResourceExchanger.GetResource("UserDeletedMessage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintMessageLink(change, context, currentPage)); default: throw new NotSupportedException(); } } /// /// Builds a link to a page, properly handling inexistent pages. /// /// The change. /// The formatting context. /// The current page, or null. /// The link HTML markup. private static string PrintPageLink(RecentChange change, FormattingContext context, PageInfo currentPage) { PageInfo page = Pages.FindPage(change.Page); if(page != null) { return string.Format(@"{2}", change.Page, Settings.PageExtension, FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage)); } else { return FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage) + " (" + change.Page + ")"; } } /// /// Builds a link to a page discussion. /// /// The change. /// The formatting context. /// The current page, or null. /// The link HTML markup. private static string PrintMessageLink(RecentChange change, FormattingContext context, PageInfo currentPage) { PageInfo page = Pages.FindPage(change.Page); if(page != null) { return string.Format(@"{3}", change.Page, Settings.PageExtension, Tools.GetMessageIdForAnchor(change.DateTime), FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage) + " (" + FormattingPipeline.PrepareTitle(change.MessageSubject, false, context, currentPage) + ")"); } else { return FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage) + " (" + change.Page + ")"; } } /// /// Builds the orhpaned pages list (valid only in non-indexing mode). /// /// The namespace (null for the root). /// The formatting context. /// The current page, if any. /// The list. private static string BuildOrphanedPagesList(NamespaceInfo nspace, FormattingContext context, PageInfo current) { PageInfo[] orhpans = Pages.GetOrphanedPages(nspace); if(orhpans.Length == 0) return ""; StringBuilder sb = new StringBuilder(500); sb.Append("
    "); foreach(PageInfo page in orhpans) { PageContent content = Content.GetPageContent(page, false); sb.Append("
  • "); sb.AppendFormat(@"{2}", Tools.UrlEncode(page.FullName), Settings.PageExtension, FormattingPipeline.PrepareTitle(content.Title, false, context, current)); sb.Append("
  • "); } sb.Append("
"); return sb.ToString(); } /// /// Builds the wanted pages list. /// /// The namespace (null for the root). /// The list. private static string BuildWantedPagesList(NamespaceInfo nspace) { Dictionary> wanted = Pages.GetWantedPages(nspace != null ? nspace.Name : null); if(wanted.Count == 0) return ""; StringBuilder sb = new StringBuilder(500); sb.Append("
    "); foreach(string page in wanted.Keys) { sb.Append("
  • "); sb.AppendFormat(@"{2}", page, Settings.PageExtension, NameTools.GetLocalName(page)); sb.Append("
  • "); } sb.Append("
"); return sb.ToString(); } /// /// Builds the incoming links list for a page (valid only in Phase3). /// /// The page. /// The formatting context. /// The current page, if any. /// The list. private static string BuildIncomingLinksList(PageInfo page, FormattingContext context, PageInfo current) { if(page == null) return ""; string[] links = Pages.GetPageIncomingLinks(page); if(links.Length == 0) return ""; StringBuilder sb = new StringBuilder(500); sb.AppendFormat("
    "); foreach(string link in links) { PageInfo linkedPage = Pages.FindPage(link); if(linkedPage != null) { PageContent content = Content.GetPageContent(linkedPage, false); sb.Append("
  • "); sb.AppendFormat(@"{2}", Tools.UrlEncode(link), Settings.PageExtension, FormattingPipeline.PrepareTitle(content.Title, false, context, current)); sb.Append("
  • "); } } sb.AppendFormat("
"); return sb.ToString(); } /// /// Builds the outgoing links list for a page (valid only in Phase3). /// /// The page. /// The formatting context. /// The current page, if any. /// The list. private static string BuildOutgoingLinksList(PageInfo page, FormattingContext context, PageInfo current) { if(page == null) return ""; string[] links = Pages.GetPageOutgoingLinks(page); if(links.Length == 0) return ""; StringBuilder sb = new StringBuilder(500); sb.Append("
    "); foreach(string link in links) { PageInfo linkedPage = Pages.FindPage(link); if(linkedPage != null) { PageContent content = Content.GetPageContent(linkedPage, false); sb.Append("
  • "); sb.AppendFormat(@"{2}", Tools.UrlEncode(link), Settings.PageExtension, FormattingPipeline.PrepareTitle(content.Title, false, context, current)); sb.Append("
  • "); } } sb.Append("
"); return sb.ToString(); } /// /// Builds the link to a namespace. /// /// The namespace (null for the root). /// The link. private static string BuildNamespaceLink(string nspace) { return "" + (string.IsNullOrEmpty(nspace) ? "<root>" : nspace) + ""; } /// /// Builds the namespace list. /// /// The namespace list. private static string BuildNamespaceList() { StringBuilder sb = new StringBuilder(100); sb.Append("
    "); sb.Append("
  • "); sb.Append(BuildNamespaceLink(null)); sb.Append("
  • "); foreach(NamespaceInfo ns in Pages.GetNamespaces()) { sb.Append("
  • "); sb.Append(BuildNamespaceLink(ns.Name)); sb.Append("
  • "); } sb.Append("
"); return sb.ToString(); } /// /// Processes line breaks. /// /// The containing the text to process. /// A value indicating whether the formatting is being done in bare-bones mode. private static void ProcessLineBreaks(StringBuilder sb, bool bareBones) { if(bareBones || AreSingleLineBreaksToBeProcessed()) { // Replace new-lines only when not enclosed in tags Match match = NoSingleBr.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, match.Value.Replace("\n", SingleBrPlaceHolder)); match = NoSingleBr.Match(sb.ToString(), match.Index + 1); } sb.Replace("\n", "
"); sb.Replace(SingleBrPlaceHolder, "\n"); } else { // Replace new-lines only when not enclosed in
tags Match match = NoSingleBr.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, match.Value.Replace("\n", SingleBrPlaceHolder)); match = NoSingleBr.Match(sb.ToString(), match.Index + 1); } sb.Replace("\n\n", "

"); sb.Replace(SingleBrPlaceHolder, "\n"); } sb.Replace("
", "
"); // BR Hacks sb.Replace("

", "
"); sb.Replace("

", "
"); sb.Replace("

", "
"); sb.Replace("

", "
"); if(AreSingleLineBreaksToBeProcessed()) { sb.Replace("
", ""); sb.Replace("
", ""); sb.Replace("
", ""); sb.Replace("
", ""); sb.Replace("
", ""); sb.Replace("
", ""); sb.Replace("
", ""); } else { sb.Replace("

", "
"); } sb.Replace("
", ""); sb.Replace("", ""); } /// /// Gets a value indicating whether or not to process single line breaks. /// /// true if SLB are to be processed, false otherwise. private static bool AreSingleLineBreaksToBeProcessed() { return Settings.ProcessSingleLineBreaks; } /// /// Replaces the {TOC} markup in a string. /// /// The input string. /// The TOC replacement. /// The final string. private static string ReplaceToc(string input, string cachedToc) { // HACK: this method is used to trick the formatter so that it works when a {TOC} tag is placed in a trigger // Basically, the Snippet content is formatted in the same context as the main content, but without // The headers list available, thus the need for this special treatment Match match = TocRegex.Match(input); while(match.Success) { input = input.Remove(match.Index, match.Length); input = input.Insert(match.Index, cachedToc); match = TocRegex.Match(input, match.Index + 1); } return input; } /// /// Expands a regex selection to match the number of open curly brackets. /// /// The buffer. /// The match start index. /// The match value. /// The balanced string, or null if the brackets could not be balanced. private static string ExpandToBalanceBrackets(StringBuilder sb, int index, string value) { int tempIndex = -1; int openCount = 0; do { tempIndex = value.IndexOf("{", tempIndex + 1); if(tempIndex >= 0) openCount++; } while(tempIndex != -1 && tempIndex < value.Length - 1); int closeCount = 0; tempIndex = -1; do { tempIndex = value.IndexOf("}", tempIndex + 1); if(tempIndex >= 0) closeCount++; } while(tempIndex != -1 && tempIndex < value.Length - 1); // Already balanced if(openCount == closeCount) return value; tempIndex = index + value.Length - 1; string bigString = sb.ToString(); do { tempIndex = bigString.IndexOf("}", tempIndex + 1); if(tempIndex != -1) { closeCount++; if(closeCount == openCount) { // Balanced return bigString.Substring(index, tempIndex - index + 1); } } } while(tempIndex != -1 && tempIndex < bigString.Length - 1); return null; } /// /// Formats a snippet. /// /// The captured markup. /// The TOC content (trick to allow {TOC} to be inserted in snippets). /// The formatted result. private static string FormatSnippet(string capturedMarkup, string cachedToc) { // If the markup does not contain equal signs, process it using the classic method, assuming there are only positional parameters //if(capturedMarkup.IndexOf("=") == -1) { if(!ClassicSnippetVerifier.IsMatch(capturedMarkup)) { string tempRes = FormatClassicSnippet(capturedMarkup); return ReplaceToc(tempRes, cachedToc); } // If the markup contains "=" but not new lines, simulate the required structure as shown below if(capturedMarkup.IndexOf("\n") == -1) { capturedMarkup = capturedMarkup.Replace("|", "\n|"); } // The format is: // {s:Name | param = value -- OR // | param = value -- OR // | param = value // // which continues on next line, preceded by a blank // } // End bracket can be on a line on its own or on the last content line // 0. Find snippet object string snippetName = capturedMarkup.Substring(3, capturedMarkup.IndexOf("\n") - 3).Trim(); Snippet snippet = Snippets.Find(snippetName); if(snippet == null) return @"FORMATTER ERROR (Snippet Not Found)"; // 1. Strip all useless data at the beginning and end of the text ({s| and }, plus all whitespaces) // Strip opening and closing tags StringBuilder sb = new StringBuilder(capturedMarkup); sb.Remove(0, 3); sb.Remove(sb.Length - 1, 1); // Strip all whitespaces at the end while(char.IsWhiteSpace(sb[sb.Length - 1])) sb.Remove(sb.Length - 1, 1); // 2. Split into lines, preserving empty lines string[] lines = sb.ToString().Split('\n'); // 3. Find all lines starting with a pipe and containing an equal sign -> those are the ones that define params values List parametersLines = new List(lines.Length); for(int i = 0; i < lines.Length; i++) { if(lines[i].Trim().StartsWith("|") && lines[i].Contains("=")) { parametersLines.Add(i); } } // 4. For each parameter line, extract the parameter value, spanning through all subsequent non-parameter lines // Build a name->value dictionary for parameters Dictionary values = new Dictionary(parametersLines.Count); for(int i = 0; i < parametersLines.Count; i++) { // Extract parameter name int equalSignIndex = lines[parametersLines[i]].IndexOf("="); string parameterName = lines[parametersLines[i]].Substring(0, equalSignIndex); parameterName = parameterName.Trim(' ', '|').ToLowerInvariant(); StringBuilder currentValue = new StringBuilder(100); currentValue.Append(lines[parametersLines[i]].Substring(equalSignIndex + 1).TrimStart(' ')); // Span all subsequent lines if(i < parametersLines.Count - 1) { for(int span = parametersLines[i] + 1; span < parametersLines[i + 1]; span++) { currentValue.Append("\n"); currentValue.Append(lines[span]); } } else if(parametersLines[i] < lines.Length - 1) { // All remaining lines belong to the last parameter for(int span = parametersLines[i] + 1; span < lines.Length; span++) { currentValue.Append("\n"); currentValue.Append(lines[span]); } } if(!values.ContainsKey(parameterName)) values.Add(parameterName, currentValue.ToString()); else values[parameterName] = currentValue.ToString(); } // 5. Prepare the snippet output, replacing parameters placeholders with their values // Use a lowercase version of the snippet to identify parameters locations string lowercaseSnippetContent = snippet.Content.ToLowerInvariant(); StringBuilder output = new StringBuilder(snippet.Content); foreach(KeyValuePair pair in values) { int index = lowercaseSnippetContent.IndexOf("?" + pair.Key + "?"); while(index >= 0) { output.Remove(index, pair.Key.Length + 2); output.Insert(index, pair.Value); // Need to update lowercase representation because the parameters values alter the length lowercaseSnippetContent = output.ToString().ToLowerInvariant(); index = lowercaseSnippetContent.IndexOf("?" + pair.Key + "?"); } } // Remove all remaining parameters and return string tempResult = Snippets.ParametersRegex.Replace(output.ToString(), ""); return ReplaceToc(tempResult, cachedToc); } /// /// Format classic number-parameterized snippets. /// /// The captured markup to process. /// The formatted result. private static string FormatClassicSnippet(string capturedMarkup) { int secondPipe = capturedMarkup.Substring(3).IndexOf("|"); string name = ""; if(secondPipe == -1) name = capturedMarkup.Substring(3, capturedMarkup.Length - 4); // No parameters else name = capturedMarkup.Substring(3, secondPipe); Snippet snippet = Snippets.Find(name); if(snippet != null) { string[] parameters = CustomSplit(capturedMarkup.Substring(3 + secondPipe + 1, capturedMarkup.Length - secondPipe - 5)); string fs = PrepareSnippet(parameters, snippet.Content); return fs.Trim('\n'); } else { return @"FORMATTER ERROR (Snippet Not Found)"; } } /// /// Splits a string at pipe characters, taking into account square brackets for links and images. /// /// The input data. /// The resuling splitted strings. private static string[] CustomSplit(string data) { // 1. Find all pipes that are not enclosed in square brackets List indices = new List(10); int index = 0; int openBrackets = 0; // Account for links with two brackets, e.g. [[link]] while(index < data.Length) { if(data[index] == '|') { if(openBrackets == 0) indices.Add(index); } else if(data[index] == '[') openBrackets++; else if(data[index] == ']') openBrackets--; if(openBrackets < 0) openBrackets = 0; index++; } // 2. Split string at reported indices indices.Insert(0, -1); indices.Add(data.Length); List result = new List(indices.Count); for(int i = 0; i < indices.Count - 1; i++) { result.Add(data.Substring(indices[i] + 1, indices[i + 1] - indices[i] - 1)); } return result.ToArray(); } /// /// Prepares the content of a snippet, properly managing parameters. /// /// The snippet parameters. /// The snippet original text. /// The prepared snippet text. private static string PrepareSnippet(string[] parameters, string snippet) { StringBuilder sb = new StringBuilder(snippet); for(int i = 0; i < parameters.Length; i++) { sb.Replace(string.Format("?{0}?", i + 1), parameters[i]); } // Remove all remaining parameters that have no value return Snippets.ParametersRegex.Replace(sb.ToString(), ""); } /// /// Escapes all the characters used by the WikiMarkup. /// /// The Content. /// The escaped Content. private static string EscapeWikiMarkup(string content) { StringBuilder sb = new StringBuilder(content); sb.Replace("&", "&"); // Before all other escapes! sb.Replace("#", "#"); sb.Replace("*", "*"); sb.Replace("<", "<"); sb.Replace(">", ">"); sb.Replace("[", "["); sb.Replace("]", "]"); sb.Replace("{", "{"); sb.Replace("}", "}"); sb.Replace("'''", "'''"); sb.Replace("''", "''"); sb.Replace("=====", "====="); sb.Replace("====", "===="); sb.Replace("===", "==="); sb.Replace("==", "=="); sb.Replace("§§", "§§"); sb.Replace("__", "__"); sb.Replace("--", "--"); sb.Replace("@@", "@@"); sb.Replace(":", ":"); return sb.ToString(); } /// /// Removes all the characters used by the WikiMarkup. /// /// The Content. /// The stripped Content. private static string StripWikiMarkup(string content) { if(string.IsNullOrEmpty(content)) return ""; StringBuilder sb = new StringBuilder(content); sb.Replace("*", ""); sb.Replace("<", ""); sb.Replace(">", ""); sb.Replace("[", ""); sb.Replace("]", ""); sb.Replace("{", ""); sb.Replace("}", ""); sb.Replace("'''", ""); sb.Replace("''", ""); sb.Replace("=====", ""); sb.Replace("====", ""); sb.Replace("===", ""); sb.Replace("==", ""); sb.Replace("§§", ""); sb.Replace("__", ""); sb.Replace("--", ""); sb.Replace("@@", ""); return sb.ToString(); } /// /// Removes all HTML markup from a string. /// /// The string. /// The result. public static string StripHtml(string content) { if(string.IsNullOrEmpty(content)) return ""; StringBuilder sb = new StringBuilder(Regex.Replace(content, "<[^>]*>", " ")); sb.Replace(" ", ""); sb.Replace(" ", " "); return sb.ToString(); } /// /// Builds a Link. /// /// The (raw) HREF. /// The name/title. /// True if the link contains an Image as "visible content". /// The title of the image. /// A value indicating whether the formatting is being done for content indexing. /// A value indicating whether the formatting is being done in bare-bones mode. /// The formatting context. /// The current page, or null. /// The linked pages list (both existent and inexistent). /// The formatted Link. private static string BuildLink(string targetUrl, string title, bool isImage, string imageTitle, bool forIndexing, bool bareBones, FormattingContext context, PageInfo currentPage, List linkedPages) { if(targetUrl == null) targetUrl = ""; if(title == null) title = ""; if(imageTitle == null) imageTitle = ""; bool blank = false; if(targetUrl.StartsWith("^")) { blank = true; targetUrl = targetUrl.Substring(1); } targetUrl = EscapeUrl(targetUrl); string nstripped = StripWikiMarkup(StripHtml(title)); string imageTitleStripped = StripWikiMarkup(StripHtml(imageTitle)); StringBuilder sb = new StringBuilder(150); if(targetUrl.ToLowerInvariant().Equals("anchor") && title.StartsWith("#")) { sb.Append(@" "); } else if(targetUrl.StartsWith("#")) { sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(targetUrl.Substring(1)); sb.Append(@""">"); if(title.Length > 0) sb.Append(title); else sb.Append(targetUrl.Substring(1)); sb.Append(""); } else if(targetUrl.StartsWith("http://") || targetUrl.StartsWith("https://") || targetUrl.StartsWith("ftp://") || targetUrl.StartsWith("file://")) { // The link is complete sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(targetUrl); sb.Append(@""" target=""_blank"">"); if(title.Length > 0) sb.Append(title); else sb.Append(targetUrl); sb.Append(""); } else if(targetUrl.StartsWith(@"\\") || targetUrl.StartsWith("//")) { // The link is a UNC path sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(targetUrl); sb.Append(@""" target=""_blank"">"); if(title.Length > 0) sb.Append(title); else sb.Append(targetUrl); sb.Append(""); } else if(targetUrl.IndexOf("@") != -1 && targetUrl.IndexOf(".") != -1) { // Email sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(Tools.ObfuscateText(targetUrl)); sb.Append(@""">"); if(title.Length > 0) sb.Append(title); else sb.Append(Tools.ObfuscateText(targetUrl)); sb.Append(""); } else if(((targetUrl.IndexOf(".") != -1 && !targetUrl.ToLowerInvariant().EndsWith(".aspx")) || targetUrl.EndsWith("/")) && !targetUrl.StartsWith("++") && Pages.FindPage(targetUrl) == null && !targetUrl.StartsWith("c:") && !targetUrl.StartsWith("C:")) { // Link to an internal file or subdirectory, or link to GetFile.aspx sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(targetUrl); sb.Append(@""">"); if(title.Length > 0) sb.Append(title); else sb.Append(targetUrl); sb.Append(""); } else { if(targetUrl.IndexOf(".aspx") != -1) { // The link points to a "system" page sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(targetUrl); sb.Append(@""">"); if(title.Length > 0) sb.Append(title); else sb.Append(targetUrl); sb.Append(""); } else { if(targetUrl.StartsWith("c:") || targetUrl.StartsWith("C:")) { // Category link //sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(targetUrl.Substring(2)); sb.Append(@""">"); if(title.Length > 0) sb.Append(title); else sb.Append(targetUrl.Substring(2)); sb.Append(""); } else if(targetUrl.Contains(":") || targetUrl.ToLowerInvariant().Contains("%3a") || targetUrl.Contains("&") || targetUrl.Contains("%26")) { sb.Append(@"FORMATTER ERROR ("":"" and ""&"" not supported in Page Names)"); } else { // The link points to a wiki page bool explicitNamespace = false; string tempLink = targetUrl; if(tempLink.StartsWith("++")) { tempLink = tempLink.Substring(2); targetUrl = targetUrl.Substring(2); explicitNamespace = true; } if(targetUrl.IndexOf("#") != -1) { tempLink = targetUrl.Substring(0, targetUrl.IndexOf("#")); targetUrl = Tools.UrlEncode(targetUrl.Substring(0, targetUrl.IndexOf("#"))) + Settings.PageExtension + targetUrl.Substring(targetUrl.IndexOf("#")); } else { targetUrl += Settings.PageExtension; targetUrl = Tools.UrlEncode(targetUrl); } string fullName = ""; if(!explicitNamespace) { fullName = currentPage != null ? NameTools.GetFullName(NameTools.GetNamespace(currentPage.FullName), NameTools.GetLocalName(tempLink)) : tempLink; } else fullName = tempLink; // Add linked page without repetitions //linkedPages.Add(fullName); PageInfo info = Pages.FindPage(fullName); if(info != null) { if(!linkedPages.Contains(info.FullName)) linkedPages.Add(info.FullName); } else { string lowercaseFullName = fullName.ToLowerInvariant(); if(linkedPages.Find(p => { return p.ToLowerInvariant() == lowercaseFullName; }) == null) linkedPages.Add(fullName); } if(info == null) { sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(tempLink);*/ sb.Append(fullName); sb.Append(@""">"); if(title.Length > 0) sb.Append(title); else sb.Append(tempLink); sb.Append(""); } else { sb.Append(@" 0) sb.Append(nstripped); else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); else sb.Append(FormattingPipeline.PrepareTitle(Content.GetPageContent(info, false).Title, context, currentPage));*/ if(forIndexing) { // When saving a page, the SQL Server provider calls IHost.PrepareContentForIndexing // If the content contains a reference to the page being saved, the formatter will call GetPageContent on SQL Server, // resulting in a transaction deadlock (the save transaction waits for the read transaction, while the latter // waits the the locks on the PageContent table being released) // See also Content.GetPageContent if(currentPage != null && currentPage.FullName == info.FullName) { // Do not format title sb.Append(info.FullName); } else { // Try to format title PageContent retrievedContent = Content.GetPageContent(info, false); if(retrievedContent != null && !retrievedContent.IsEmpty()) { sb.Append(FormattingPipeline.PrepareTitle(retrievedContent.Title, forIndexing, context, currentPage)); } else sb.Append(info.FullName); } } else sb.Append(FormattingPipeline.PrepareTitle(Content.GetPageContent(info, false).Title, forIndexing, context, currentPage)); sb.Append(@""">"); if(title.Length > 0) sb.Append(title); else sb.Append(tempLink); sb.Append(""); } } } } return sb.ToString(); } /// /// Detects all the Headers in a block of text (H1, H2, H3, H4). /// /// The text. /// The List of Header objects, in the same order as they are in the text. public static List DetectHeaders(string text) { Match match; string h; int end = 0; List noWikiBegin = new List(), noWikiEnd = new List(); List hPos = new List(); StringBuilder sb = new StringBuilder(text); ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); int count = 0; match = H4Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { h = match.Value.Substring(5, match.Value.Length - 10 - (match.Value.EndsWith("\n") ? 1 : 0)); hPos.Add(new HPosition(match.Index, h, 4, count)); end = match.Index + match.Length; count++; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H4Regex.Match(sb.ToString(), end); } match = H3Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { h = match.Value.Substring(4, match.Value.Length - 8 - (match.Value.EndsWith("\n") ? 1 : 0)); bool found = false; for(int i = 0; i < hPos.Count; i++) { if(match.Index == hPos[i].Index) { found = true; break; } } if(!found) { hPos.Add(new HPosition(match.Index, h, 3, count)); count++; } end = match.Index + match.Length; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H3Regex.Match(sb.ToString(), end); } match = H2Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { h = match.Value.Substring(3, match.Value.Length - 6 - (match.Value.EndsWith("\n") ? 1 : 0)); bool found = false; for(int i = 0; i < hPos.Count; i++) { if(match.Index == hPos[i].Index) { found = true; break; } } if(!found) { hPos.Add(new HPosition(match.Index, h, 2, count)); count++; } end = match.Index + match.Length; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H2Regex.Match(sb.ToString(), end); } match = H1Regex.Match(sb.ToString()); while(match.Success) { if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { h = match.Value.Substring(2, match.Value.Length - 4 - (match.Value.EndsWith("\n") ? 1 : 0)); bool found = false; for(int i = 0; i < hPos.Count; i++) { // A special treatment is needed in this case // because =====xxx===== matches also 2 H1 headers (=='='==) if(match.Index >= hPos[i].Index && match.Index <= hPos[i].Index + hPos[i].Text.Length + 5) { found = true; break; } } if(!found) { hPos.Add(new HPosition(match.Index, h, 1, count)); count++; } end = match.Index + match.Length; } ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); match = H1Regex.Match(sb.ToString(), end); } return hPos; } /// /// Builds the "Edit" links for page sections. /// /// The section ID. /// The page name. /// The link. private static string BuildEditSectionLink(int id, string page) { if(!Settings.EnableSectionEditing) return ""; StringBuilder sb = new StringBuilder(100); sb.Append(@""); sb.Append(EditSectionPlaceHolder); sb.Append(""); return sb.ToString(); } /// /// Generates list HTML markup. /// /// The lines in the list WikiMarkup. /// The current line. /// The current level. /// The current line. /// The correct HTML markup. private static string GenerateList(string[] lines, int line, int level, ref int currLine) { StringBuilder sb = new StringBuilder(200); if(lines[currLine][level] == '*') sb.Append("
    "); else if(lines[currLine][level] == '#') sb.Append("
      "); while(currLine <= lines.Length - 1 && CountBullets(lines[currLine]) >= level + 1) { if(CountBullets(lines[currLine]) == level + 1) { sb.Append("
    1. "); sb.Append(lines[currLine].Substring(CountBullets(lines[currLine])).Trim()); sb.Append("
    2. "); currLine++; } else { sb.Remove(sb.Length - 5, 5); sb.Append(GenerateList(lines, currLine, level + 1, ref currLine)); sb.Append(""); } } if(lines[line][level] == '*') sb.Append("
"); else if(lines[line][level] == '#') sb.Append(""); return sb.ToString(); } /// /// Counts the bullets in a list line. /// /// The line. /// The number of bullets. private static int CountBullets(string line) { int res = 0, count = 0; while(line[count] == '*' || line[count] == '#') { res++; count++; } return res; } /// /// Extracts the bullets from a list line. /// /// The line. /// The bullets. private static string ExtractBullets(string value) { string res = ""; for(int i = 0; i < value.Length; i++) { if(value[i] == '*' || value[i] == '#') res += value[i]; else break; } return res; } /// /// Builds the TOC of a document. /// /// The positions of headers. /// The TOC HTML markup. private static string BuildToc(List hPos) { StringBuilder sb = new StringBuilder(); hPos.Sort(new HPositionComparer()); // Table only used to workaround IE idiosyncrasies - use TocCointainer for styling sb.Append(@"
"); sb.Append(@"
"); sb.Append(@"

"); sb.Append(TocTitlePlaceHolder); sb.Append("

"); sb.Append(@"
"); sb.Append("


"); for(int i = 0; i < hPos.Count; i++) { //Debug.WriteLine(i.ToString() + " " + hPos[i].Index.ToString() + ": " + hPos[i].Level); switch(hPos[i].Level) { case 1: break; case 2: sb.Append("   "); break; case 3: sb.Append("      "); break; case 4: sb.Append("         "); break; } if(hPos[i].Level == 1) sb.Append(""); if(hPos[i].Level == 4) sb.Append(""); sb.Append(@""); sb.Append(StripWikiMarkup(StripHtml(hPos[i].Text))); sb.Append(""); if(hPos[i].Level == 4) sb.Append(""); if(hPos[i].Level == 1) sb.Append(""); sb.Append("
"); } sb.Append("

"); sb.Append("
"); sb.Append("
"); sb.Append("
"); return sb.ToString(); } /// /// Builds a valid anchor name from a string. /// /// The string, usually a header (Hx). /// The anchor ID. public static string BuildHAnchor(string h) { StringBuilder sb = new StringBuilder(StripWikiMarkup(h)); sb.Replace(" ", "_"); sb.Replace(".", ""); sb.Replace(",", ""); sb.Replace(";", ""); sb.Replace("\"", ""); sb.Replace("/", ""); sb.Replace("\\", ""); sb.Replace("'", ""); sb.Replace("(", ""); sb.Replace(")", ""); sb.Replace("[", ""); sb.Replace("]", ""); sb.Replace("{", ""); sb.Replace("}", ""); sb.Replace("<", ""); sb.Replace(">", ""); sb.Replace("#", ""); sb.Replace("\n", ""); sb.Replace("?", ""); sb.Replace("&", ""); sb.Replace("0", "A"); sb.Replace("1", "B"); sb.Replace("2", "C"); sb.Replace("3", "D"); sb.Replace("4", "E"); sb.Replace("5", "F"); sb.Replace("6", "G"); sb.Replace("7", "H"); sb.Replace("8", "I"); sb.Replace("9", "J"); return sb.ToString(); } /// /// Builds a valid and unique anchor name from a string. /// /// The string, usually a header (Hx). /// The unique ID. /// The anchor ID. public static string BuildHAnchor(string h, string uid) { return BuildHAnchor(h) + "_" + uid; } /// /// Escapes ampersands in a URL. /// /// The URL. /// The escaped URL. private static string EscapeUrl(string url) { return url.Replace("&", "&"); } /// /// Builds a HTML table from WikiMarkup. /// /// The WikiMarkup. /// The HTML. private static string BuildTable(string table) { // Proceed line-by-line, ignoring the first and last one string[] lines = table.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); if(lines.Length < 3) { return "FORMATTER ERROR (Malformed Table)"; } StringBuilder sb = new StringBuilder(); sb.Append(" 2) { sb.Append(" "); sb.Append(lines[0].Substring(3)); } sb.Append(">"); int count = 1; if(lines[1].Length >= 3 && lines[1].Trim().StartsWith("|+")) { // Table caption sb.Append(""); sb.Append(lines[1].Substring(3)); sb.Append(""); count++; } if(!lines[count].StartsWith("|-")) sb.Append(""); bool thAdded = false; string item; for(int i = count; i < lines.Length - 1; i++) { if(lines[i].Trim().StartsWith("|-")) { // New line if(i != count) sb.Append(""); sb.Append(" 2) { string style = lines[i].Substring(3); if(style.Length > 0) { sb.Append(" "); sb.Append(style); } } sb.Append(">"); } else if(lines[i].Trim().StartsWith("|")) { // Cell if(lines[i].Length < 3) continue; item = lines[i].Substring(2); if(item.IndexOf(" || ") != -1) { sb.Append(""); sb.Append(item.Replace(" || ", "")); sb.Append(""); } else if(item.IndexOf(" | ") != -1) { sb.Append(""); sb.Append(item.Substring(item.IndexOf(" | ") + 3)); sb.Append(""); } else { sb.Append(""); sb.Append(item); sb.Append(""); } } else if(lines[i].Trim().StartsWith("!")) { // Header if(lines[i].Length < 3) continue; // only if ! is found in the first row of the table, it is an header if(lines[i + 1] == "|-") thAdded = true; item = lines[i].Substring(2); if(item.IndexOf(" !! ") != -1) { sb.Append(""); sb.Append(item.Replace(" !! ", "")); sb.Append(""); } else if(item.IndexOf(" ! ") != -1) { sb.Append(""); sb.Append(item.Substring(item.IndexOf(" ! ") + 3)); sb.Append(""); } else { sb.Append(""); sb.Append(item); sb.Append(""); } } } if(sb.ToString().EndsWith("")) { sb.Remove(sb.Length - 4 - 1, 4); sb.Append(""); } else { sb.Append(""); } sb.Replace("", ""); // Add , tags, if table contains header if(thAdded) { int thIndex = sb.ToString().IndexOf("= 4) sb.Insert(thIndex - 4, ""); sb.Insert(thIndex - 4, ""); // search for the last tag in the first row of the table int thCloseIndex = -1; int thCloseIndex_temp = -1; do { thCloseIndex = thCloseIndex_temp; thCloseIndex_temp = sb.ToString().IndexOf("", thCloseIndex + 1); } while (thCloseIndex_temp != -1 && thCloseIndex_temp < sb.ToString().IndexOf("")); sb.Insert(thCloseIndex + 10, ""); sb.Insert(sb.Length - 8, ""); } return sb.ToString(); } /// /// Builds an indented text block. /// /// The input text. /// The result. private static string BuildIndent(string indent) { int colons = 0; indent = indent.Trim(); while(colons < indent.Length && indent[colons] == ':') colons++; indent = indent.Substring(colons).Trim(); return @"
" + indent + "
"; } /// /// Builds the tag cloud. /// /// The current namespace (null for the root). /// The tag cloud. private static string BuildCloud(NamespaceInfo currentNamespace) { StringBuilder sb = new StringBuilder(); // Total categorized Pages (uncategorized Pages don't count) int tot = Pages.GetPages(currentNamespace).Count - Pages.GetUncategorizedPages(currentNamespace).Length; List categories = Pages.GetCategories(currentNamespace); for(int i = 0; i < categories.Count; i++) { if(categories[i].Pages.Length > 0) { //sb.Append(@""); sb.Append(NameTools.GetLocalName(categories[i].FullName)); sb.Append(""); } if(i != categories.Count - 1) sb.Append(" "); } return sb.ToString(); } /// /// Computes the sixe of a category label in the category cloud. /// /// The occurrence percentage of the category. /// The computes size. private static int ComputeSize(float percentage) { // Interpolates min and max size on a line, so that if: // - percentage = 0 -> size = minSize // - percentage = 100 -> size = maxSize // - intermediate values are calculated float minSize = 8, maxSize = 26; //return (int)((maxSize - minSize) / 100F * (float)percentage + minSize); // Linear interpolation return (int)(maxSize - (maxSize - minSize) * Math.Exp(-percentage / 25)); // Exponential interpolation } /// /// Computes the positions of all NOWIKI tags. /// /// The input text. /// The output list of begin indexes of NOWIKI tags. /// The output list of end indexes of NOWIKI tags. private static void ComputeNoWiki(string text, ref List noWikiBegin, ref List noWikiEnd) { Match match; noWikiBegin.Clear(); noWikiEnd.Clear(); match = NoWikiRegex.Match(text); while(match.Success) { noWikiBegin.Add(match.Index); noWikiEnd.Add(match.Index + match.Length); match = NoWikiRegex.Match(text, match.Index + match.Length); } } /// /// Determines whether a character is enclosed in a NOWIKI tag. /// /// The index of the character. /// The list of begin indexes of NOWIKI tags. /// The list of end indexes of NOWIKI tags. /// The end index of the NOWIKI tag that encloses the specified character, or zero. /// true if the specified character is enclosed in a NOWIKI tag, false otherwise. private static bool IsNoWikied(int index, List noWikiBegin, List noWikiEnd, out int end) { for(int i = 0; i < noWikiBegin.Count; i++) { if(index > noWikiBegin[i] && index < noWikiEnd[i]) { end = noWikiEnd[i]; return true; } } end = 0; return false; } /// /// Performs the internal Phase 3 of the Formatting pipeline. /// /// The raw data. /// The formatting context. /// The current PageInfo, if any. /// The formatted content. public static string FormatPhase3(string raw, FormattingContext context, PageInfo current) { StringBuilder sb = new StringBuilder(raw.Length); StringBuilder dummy; sb.Append(raw); Match match; // Format {CLOUD} tag (it has to be in Phase3 because on page rebind the cache is not cleared) // --> When rebidning a page, the cache is now cleared /*match = CloudRegex.Match(sb.ToString()); string cloudMarkup = BuildCloud(DetectNamespaceInfo(current)); while(match.Success) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, cloudMarkup); match = CloudRegex.Match(sb.ToString(), match.Index + cloudMarkup.Length); }*/ // Format {NAMESPACE} tag // --> Now processed with {INCOMING/OUTGOING} /*string ns = Tools.DetectCurrentNamespace(); if(string.IsNullOrEmpty(ns)) ns = "<root>"; match = NamespaceRegex.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); sb.Insert(match.Index, ns); match = NamespaceRegex.Match(sb.ToString(), match.Index + 11); }*/ // Format other Phase3 special tags match = Phase3SpecialTagRegex.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); switch(match.Value.Substring(1, match.Value.Length - 2).ToUpperInvariant()) { case "NAMESPACE": string ns = Tools.DetectCurrentNamespace(); if(string.IsNullOrEmpty(ns)) ns = "<root>"; sb.Insert(match.Index, ns); break; case "NAMESPACEDROPDOWN": sb.Insert(match.Index, BuildCurrentNamespaceDropDown()); break; case "INCOMING": sb.Insert(match.Index, BuildIncomingLinksList(current, context, current)); break; case "OUTGOING": sb.Insert(match.Index, BuildOutgoingLinksList(current, context, current)); break; case "USERNAME": if(SessionFacade.LoginKey != null) sb.Insert(match.Index, GetProfileLink(SessionFacade.CurrentUsername)); else sb.Insert(match.Index, GetLanguageLink(Exchanger.ResourceExchanger.GetResource("Guest"))); break; case "PAGENAME": if(current != null) sb.Insert(match.Index, current.FullName); break; case "LOGINLOGOUT": if(SessionFacade.LoginKey != null) sb.Insert(match.Index, GetLogoutLink()); else sb.Insert(match.Index, GetLoginLink()); break; } match = Phase3SpecialTagRegex.Match(sb.ToString()); } match = RecentChangesRegex.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); string trimmedTag = match.Value.Trim('{', '}'); // If current page is null, assume root namespace NamespaceInfo currentNamespace = null; if(current != null) currentNamespace = Pages.FindNamespace(NameTools.GetNamespace(current.FullName)); sb.Insert(match.Index, BuildRecentChanges(currentNamespace, trimmedTag.EndsWith("(*)"), context, current)); match = RecentChangesRegex.Match(sb.ToString()); } match = null; dummy = new StringBuilder(""); dummy.Append(Exchanger.ResourceExchanger.GetResource("TableOfContents")); dummy.Append(@" ["); dummy.Append(Exchanger.ResourceExchanger.GetResource("HideShow")); dummy.Append("]"); sb.Replace(TocTitlePlaceHolder, dummy.ToString()); // Display edit links only when formatting page content (and not transcluded page content) if(current != null && context == FormattingContext.PageContent) { bool canEdit = false; bool canEditWithApproval = false; Pages.CanEditPage(current, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames(), out canEdit, out canEditWithApproval); if(canEdit || canEditWithApproval) { sb.Replace(EditSectionPlaceHolder, Exchanger.ResourceExchanger.GetResource("Edit")); } else sb.Replace(EditSectionPlaceHolder, ""); } else sb.Replace(EditSectionPlaceHolder, ""); match = SignRegex.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); string txt = match.Value.Substring(3, match.Length - 6); int idx = txt.LastIndexOf(","); string[] fields = new string[] { txt.Substring(0, idx), txt.Substring(idx + 1) }; dummy = new StringBuilder(); dummy.Append(@""); dummy.Append(fields[0]); dummy.Append(", "); dummy.Append(Preferences.AlignWithTimezone(DateTime.Parse(fields[1])).ToString(Settings.DateTimeFormat)); dummy.Append(""); sb.Insert(match.Index, dummy.ToString()); match = SignRegex.Match(sb.ToString()); } // --> Now processed with other phase3 tags /*match = UsernameRegex.Match(sb.ToString()); while(match.Success) { sb.Remove(match.Index, match.Length); if(SessionFacade.LoginKey != null) sb.Insert(match.Index, GetProfileLink(SessionFacade.CurrentUser.Username)); else sb.Insert(match.Index, Exchanger.ResourceExchanger.GetResource("Guest")); match = UsernameRegex.Match(sb.ToString()); }*/ return sb.ToString(); } /// /// Builds the current namespace drop-down list. /// /// The drop-down list HTML markup. private static string BuildCurrentNamespaceDropDown() { string ns = Tools.DetectCurrentNamespace(); if(ns == null) ns = ""; string currentUser = SessionFacade.GetCurrentUsername(); string[] currentGroups = SessionFacade.GetCurrentGroupNames(); List allNamespaces = Pages.GetNamespaces(); List allowedNamespaces = new List(allNamespaces.Count); if(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ReadPages, currentUser, currentGroups)) { allowedNamespaces.Add(""); } foreach(NamespaceInfo nspace in allNamespaces) { if(AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.ReadPages, currentUser, currentGroups)) { allowedNamespaces.Add(nspace.Name); } } StringBuilder sb = new StringBuilder(500); sb.Append(""); return sb.ToString(); } /// /// Gets the link to the profile page. /// /// The username. /// The link. private static string GetProfileLink(string username) { UserInfo user = Users.FindUser(username); StringBuilder sb = new StringBuilder(200); sb.Append(""); sb.Append(user != null ? Users.GetDisplayName(user) : username); sb.Append(""); return sb.ToString(); } /// /// Gets the link to the language page. /// /// The username. /// The link. private static string GetLanguageLink(string username) { UserInfo user = Users.FindUser(username); StringBuilder sb = new StringBuilder(200); sb.Append(""); sb.Append(user != null ? Users.GetDisplayName(user) : username); sb.Append(""); return sb.ToString(); } /// /// Gets the login link. /// /// The login link. private static string GetLoginLink() { string login = Exchanger.ResourceExchanger.GetResource("Login"); StringBuilder sb = new StringBuilder(200); sb.Append(""); sb.Append(login); sb.Append(""); return sb.ToString(); } /// /// Gets the logout link. /// /// The logout link. private static string GetLogoutLink() { string login = Exchanger.ResourceExchanger.GetResource("Logout"); StringBuilder sb = new StringBuilder(200); sb.Append(""); sb.Append(login); sb.Append(""); return sb.ToString(); } } /// /// Represents a Header. /// public class HPosition { private int index; private string text; private int level; private int id; /// /// Initializes a new instance of the HPosition class. /// /// The Index. /// The Text. /// The Header level. /// The Unique ID of the Header (0-based counter). public HPosition(int index, string text, int level, int id) { this.index = index; this.text = text; this.level = level; this.id = id; } /// /// Gets or sets the Index. /// public int Index { get { return index; } set { index = value; } } /// /// Gets or sets the Text. /// public string Text { get { return text; } set { text = value; } } /// /// Gets or sets the Level. /// public int Level { get { return level; } set { level = value; } } /// /// Gets or sets the ID (0-based counter). /// public int ID { get { return id; } set { id = value; } } } /// /// Compares HPosition objects. /// public class HPositionComparer : IComparer { /// /// Performs the comparison. /// /// The first object. /// The second object. /// The comparison result. public int Compare(HPosition x, HPosition y) { return x.Index.CompareTo(y.Index); } } }