screwturn-4/Core/Formatter.cs
Dario Solera 1147017d92 Fixed and closed #414: "Link to this section" is now translated.
Fixed and closed #413: "~~~~" tag now correctly renders the display name.
Fixed #408: reverse formatter now works even when no new lines are present at the end of the content.
2009-11-10 07:59:28 +00:00

2655 lines
104 KiB
C#
Raw Blame History

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 {
/// <summary>
/// Performs all the text formatting and parsing operations.
/// </summary>
public static class Formatter {
private static readonly Regex NoWikiRegex = new Regex(@"\<nowiki\>(.|\n|\r)+?\<\/nowiki\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly Regex NoSingleBr = new Regex(@"\<nobr\>(.|\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(@"(?<!(\<\!|\&lt;))(\-\-(?!\>).+?\-\-)(?!(\>|\&gt;))", 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(@"\<esc\>(.|\n|\r)*?\<\/esc\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline);
private static readonly Regex SignRegex = new Regex(@"<22><>\(.+?\)<29><>", 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.*?\>.*?\<\/script\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant);
private static readonly Regex CommentRegex = new Regex(@"(?<!(\<script.*?\>[\s\n]*))\<\!\-\-.*?\-\-\>(?!([\s\n]*\<\/script\>))", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant);
/// <summary>
/// The section editing button placeholder.
/// </summary>
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%%%%";
private const string SectionLinkTextPlaceHolder = "%%%%SectionLinkTextPlaceHolder%%%%";
/// <summary>
/// Detects the current namespace.
/// </summary>
/// <param name="currentPage">The current page, if any.</param>
/// <returns>The current namespace (<c>null</c> for the root).</returns>
private static NamespaceInfo DetectNamespaceInfo(PageInfo currentPage) {
if(currentPage == null) {
return Tools.DetectCurrentNamespaceInfo();
}
else {
string ns = NameTools.GetNamespace(currentPage.FullName);
return Pages.FindNamespace(ns);
}
}
/// <summary>
/// Formats WikiMarkup, converting it into XHTML.
/// </summary>
/// <param name="raw">The raw WikiMarkup text.</param>
/// <param name="forIndexing">A value indicating whether the formatting is being done for content indexing.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current Page (can be null).</param>
/// <returns>The formatted text.</returns>
public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current) {
string[] tempLinks;
return Format(raw, forIndexing, context, current, out tempLinks);
}
/// <summary>
/// Formats WikiMarkup, converting it into XHTML.
/// </summary>
/// <param name="raw">The raw WikiMarkup text.</param>
/// <param name="forIndexing">A value indicating whether the formatting is being done for content indexing.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current Page (can be null).</param>
/// <param name="linkedPages">The linked pages, both existent and inexistent.</param>
/// <returns>The formatted text.</returns>
public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current, out string[] linkedPages) {
return Format(raw, forIndexing, context, current, out linkedPages, false);
}
/// <summary>
/// Formats WikiMarkup, converting it into XHTML.
/// </summary>
/// <param name="raw">The raw WikiMarkup text.</param>
/// <param name="forIndexing">A value indicating whether the formatting is being done for content indexing.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current Page (can be null).</param>
/// <param name="linkedPages">The linked pages, both existent and inexistent.</param>
/// <param name="bareBones">A value indicating whether to format in bare-bones mode (for WYSIWYG editor).</param>
/// <returns>The formatted text.</returns>
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<string> tempLinkedPages = new List<string>(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<int> noWikiBegin = new List<int>(), noWikiEnd = new List<int>();
int end = 0;
List<HPosition> hPos = new List<HPosition>();
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("<nowiki></nowiki>", "");
sb.Replace("<nobr></nobr>", "");
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("<pre><nobr>");
// IE needs \r\n for line breaks
dummy.Append(EscapeWikiMarkup(content).Replace("\n", "\r\n"));
dummy.Append("</nobr></pre>");
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("<!--", "($_^)");
//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("<", "&lt;").Replace(">", "&gt;"));
}
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)) {
// Encode filename only if it's used inside a link,
// i.e. check if {UP} is used just after a '['
// This works because links are processed afterwards
string sbString = sb.ToString();
if(match.Index > 0 && (sbString[match.Index - 1] == '[' || sbString[match.Index - 1] == '^')) {
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, @"<a href=""" +
UrlTools.BuildUrl("RSS.aspx?Page=", Tools.UrlEncode(current.FullName)) +
@""" title=""" + Exchanger.ResourceExchanger.GetResource("RssForThisPage") + @"""><img src=""" +
Settings.GetThemePath(Tools.DetectCurrentNamespace()) + @"Images/RSS.png"" alt=""RSS"" /></a>");
}
break;
case "THEMEPATH":
sb.Insert(match.Index, Settings.GetThemePath(Tools.DetectCurrentNamespace()));
break;
case "CLEAR":
sb.Insert(match.Index, @"<div style=""clear: both;""></div>");
break;
case "BR":
//if(!AreSingleLineBreaksToBeProcessed()) sb.Insert(match.Index, "<br />");
sb.Insert(match.Index, "<br />");
break;
case "TOP":
sb.Insert(match.Index, @"<a href=""#PageTop"">" + Exchanger.ResourceExchanger.GetResource("Top") + "</a>");
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 = "<nowiki><nobr><script type=\"text/javascript\"><!--\r\n" + @"function _DoSearch_" + textBoxId + "() { document.location = '" + nsstring + @"?AllNamespaces=1&FilesAndAttachments=1&Query=' + encodeURI(document.getElementById('" + textBoxId + "').value); }" + "\r\n// -->\r\n</script>";
sb.Insert(match.Index, doSearchFunction +
@"<input class=""txtsearchbox"" type=""text"" id=""" + textBoxId + @""" onkeydown=""javascript:var keycode; if(window.event) keycode = event.keyCode; else keycode = event.which; if(keycode == 10 || keycode == 13) { _DoSearch_" + textBoxId + @"(); return false; }"" /> <big><a href=""#"" onclick=""javascript:_DoSearch_" + textBoxId + @"(); return false;"">&raquo;</a></big></nowiki></nobr>");
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<string> tempLines = new List<string>(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 = "<br />" + 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, @"<b style=""color: #FF0000;"">FORMATTER ERROR (Malformed List)</b>");
}
}
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, @"<h1 class=""separator""> </h1>" + "\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(@"<div class=""transcludedpage"">");
dummy.Append(FormattingPipeline.FormatWithPhase1And2(Content.GetPageContent(info, true).Content,
forIndexing, FormattingContext.TranscludedPageContent, info));
dummy.Append("</div>");
sb.Insert(match.Index, dummy.ToString());
}
else sb.Insert(match.Index, @"<b style=""color: #FF0000;"">FORMATTER ERROR (Transcluded inexistent page or this same page)</b>");
}
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = TransclusionRegex.Match(sb.ToString(), end);
}
}
List<string> attachments = new List<string>();
// 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(@"<table class=""imageauto"" cellpadding=""0"" cellspacing=""0""><tr><td>");
}
else {
img.Append(@"<div class=""");
img.Append(c);
img.Append(@""">");
}
if(bigUrl.Length > 0) {
dummy = new StringBuilder(200);
dummy.Append(@"<img class=""image"" src=""");
dummy.Append(url);
dummy.Append(@""" alt=""");
if(title.Length > 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(@"<img class=""image"" src=""");
img.Append(url);
img.Append(@""" alt=""");
if(title.Length > 0) img.Append(StripWikiMarkup(StripHtml(title.TrimStart('#'))));
else img.Append(Exchanger.ResourceExchanger.GetResource("Image"));
img.Append(@""" />");
}
if(title.Length > 0 && !title.StartsWith("#")) {
img.Append(@"<p class=""imagedescription"">");
img.Append(title);
img.Append("</p>");
}
if(c.Equals("imageauto")) {
img.Append("</td></tr></table>");
}
else {
img.Append("</div>");
}
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(@"<img src=""");
dummy.Append(url);
dummy.Append(@""" alt=""");
if(title.Length > 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(@"<img src=""");
img.Append(url);
img.Append(@""" alt=""");
if(title.Length > 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, @"<b style=""color: #FF0000;"">FORMATTER ERROR (Malformed Image Tag)</b>");
}
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("<b><i>");
dummy.Append(match.Value.Substring(5, match.Value.Length - 10));
dummy.Append("</i></b>");
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("<b>");
dummy.Append(match.Value.Substring(3, match.Value.Length - 6));
dummy.Append("</b>");
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("<i>");
dummy.Append(match.Value.Substring(2, match.Value.Length - 4));
dummy.Append("</i>");
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("<u>");
dummy.Append(match.Value.Substring(2, match.Value.Length - 4));
dummy.Append("</u>");
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("<strike>");
dummy.Append(match.Value.Substring(2, match.Value.Length - 4));
dummy.Append("</strike>");
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("<pre>");
// IE needs \r\n for line breaks
dummy.Append(match.Value.Substring(4, match.Value.Length - 8).Replace("\n", "\r\n"));
dummy.Append("</pre>");
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("<code>");
dummy.Append(match.Value.Substring(2, match.Value.Length - 4));
dummy.Append("</code>");
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(@"<h4 class=""separator"">");
dummy.Append(h);
if(!bareBones && !forIndexing) {
string id = BuildHAnchor(h, count.ToString());
BuildHeaderAnchor(dummy, id);
}
dummy.Append("</h4>");
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(@"<h3 class=""separator"">");
dummy.Append(h);
if(!bareBones && !forIndexing) {
string id = BuildHAnchor(h, count.ToString());
BuildHeaderAnchor(dummy, id);
}
dummy.Append("</h3>");
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(@"<h2 class=""separator"">");
dummy.Append(h);
if(!bareBones && !forIndexing) {
string id = BuildHAnchor(h, count.ToString());
BuildHeaderAnchor(dummy, id);
}
dummy.Append("</h2>");
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(@"<h1 class=""separator"">");
dummy.Append(h);
if(!bareBones && !forIndexing) {
string id = BuildHAnchor(h, count.ToString());
BuildHeaderAnchor(dummy, id);
}
dummy.Append("</h1>");
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(@"<div class=""box"">");
dummy.Append(match.Value.Substring(3, match.Value.Length - 6));
dummy.Append("</div>");
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 <nowiki> tags
if(!bareBones) {
sb.Replace("<nowiki>", "");
sb.Replace("</nowiki>", "");
}
ProcessLineBreaks(sb, bareBones);
if(addedNewLineAtEnd) {
if(sb.ToString().EndsWith("<br />")) sb.Remove(sb.Length - 6, 6);
}
// Append Attachments
if(!bareBones && attachments.Count > 0) {
sb.Append(@"<div id=""AttachmentsDiv"">");
for(int i = 0; i < attachments.Count; i++) {
sb.Append(@"<a href=""");
sb.Append(UpReplacement);
sb.Append(Tools.UrlEncode(attachments[i]));
sb.Append(@""" class=""attachment"">");
sb.Append(attachments[i]);
sb.Append("</a>");
if(i != attachments.Count - 1) sb.Append(" - ");
}
sb.Append("</div>");
}
linkedPages = tempLinkedPages.ToArray();
return sb.ToString();
}
/// <summary>
/// Encodes a filename used in combination with {UP} tags.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="startIndex">The index where to start working.</param>
private static void EncodeFilename(StringBuilder buffer, int startIndex) {
// 1. Find end of the filename (first pipe or closed square bracket)
// 2. Decode the string, so that it does not break if it was already encoded
// 3. Encode the string
string allData = buffer.ToString();
int endIndex = allData.IndexOfAny(new[] { '|', ']' }, startIndex);
if(endIndex > startIndex) {
int len = endIndex - startIndex;
// {, : and } are used in snippets which are useful in links
string input = Tools.UrlDecode(allData.Substring(startIndex, len));
string value = Tools.UrlEncode(input).Replace("%7b", "{").Replace("%7B", "{").Replace("%7d", "}").Replace("%7D", "}").Replace("%3a", ":").Replace("%3A", ":");
buffer.Remove(startIndex, len);
buffer.Insert(startIndex, value);
}
}
/// <summary>
/// Builds the anchor markup for a header.
/// </summary>
/// <param name="buffer">The string builder.</param>
/// <param name="id">The anchor ID.</param>
private static void BuildHeaderAnchor(StringBuilder buffer, string id) {
buffer.Append(@"<a class=""headeranchor"" id=""");
buffer.Append(id);
buffer.Append(@""" href=""#");
buffer.Append(id);
buffer.Append(@""" title=""");
buffer.Append(SectionLinkTextPlaceHolder);
if(Settings.EnableSectionAnchors) buffer.Append(@""">&#0182;</a>");
else buffer.Append(@"""></a>");
}
/// <summary>
/// Builds the recent changes list.
/// </summary>
/// <param name="allNamespaces">A value indicating whether to build a list for all namespace or for just the current one.</param>
/// <param name="currentNamespace">The current namespace.</param>
/// <param name="context">The formatting context.</param>
/// <param name="currentPage">The current page, or <c>null</c>.</param>
/// <returns>The recent changes list HTML markup.</returns>
private static string BuildRecentChanges(NamespaceInfo currentNamespace, bool allNamespaces, FormattingContext context, PageInfo currentPage) {
List<RecentChange> allChanges = new List<RecentChange>(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);
}
/// <summary>
/// Builds a table containing recent changes.
/// </summary>
/// <param name="allChanges">The changes.</param>
/// <param name="context">The formatting context.</param>
/// <param name="currentPage">The current page, or <c>null</c>.</param>
/// <returns>The table HTML.</returns>
public static string BuildRecentChangesTable(IList<RecentChange> allChanges, FormattingContext context, PageInfo currentPage) {
int maxChanges = Math.Min(Settings.MaxRecentChangesToDisplay, allChanges.Count);
StringBuilder sb = new StringBuilder(500);
sb.Append("<table cellpadding=\"0\" cellspacing=\"0\" class=\"generictable recentchanges\"><tbody>");
for(int i = 0; i < maxChanges; i++) {
sb.AppendFormat("<tr class=\"{0}\">", i % 2 == 0 ? "tablerow" : "tablerowalternate");
sb.Append("<td>");
sb.Append(Preferences.AlignWithTimezone(allChanges[i].DateTime).ToString(Settings.DateTimeFormat));
sb.Append("</td><td>");
sb.Append(PrintRecentChange(allChanges[i], context, currentPage));
sb.Append("</td>");
sb.Append("</tr>");
}
sb.Append("</tbody></table>");
return sb.ToString();
}
/// <summary>
/// Prints a recent change.
/// </summary>
/// <param name="change">The change.</param>
/// <param name="context">The formatting context.</param>
/// <param name="currentPage">The current page, or <c>null</c>.</param>
/// <returns>The proper text to display.</returns>
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();
}
}
/// <summary>
/// Builds a link to a page, properly handling inexistent pages.
/// </summary>
/// <param name="change">The change.</param>
/// <param name="context">The formatting context.</param>
/// <param name="currentPage">The current page, or <c>null</c>.</param>
/// <returns>The link HTML markup.</returns>
private static string PrintPageLink(RecentChange change, FormattingContext context, PageInfo currentPage) {
PageInfo page = Pages.FindPage(change.Page);
if(page != null) {
return string.Format(@"<a href=""{0}{1}"" class=""pagelink"">{2}</a>",
change.Page, Settings.PageExtension, FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage));
}
else {
return FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage) + " (" + change.Page + ")";
}
}
/// <summary>
/// Builds a link to a page discussion.
/// </summary>
/// <param name="change">The change.</param>
/// <param name="context">The formatting context.</param>
/// <param name="currentPage">The current page, or <c>null</c>.</param>
/// <returns>The link HTML markup.</returns>
private static string PrintMessageLink(RecentChange change, FormattingContext context, PageInfo currentPage) {
PageInfo page = Pages.FindPage(change.Page);
if(page != null) {
return string.Format(@"<a href=""{0}{1}?Discuss=1#{2}"" class=""pagelink"">{3}</a>",
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 + ")";
}
}
/// <summary>
/// Builds the orhpaned pages list (valid only in non-indexing mode).
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current page, if any.</param>
/// <returns>The list.</returns>
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("<ul>");
foreach(PageInfo page in orhpans) {
PageContent content = Content.GetPageContent(page, false);
sb.Append("<li>");
sb.AppendFormat(@"<a href=""{0}{1}"" title=""{2}"" class=""pagelink"">{2}</a>", Tools.UrlEncode(page.FullName), Settings.PageExtension,
FormattingPipeline.PrepareTitle(content.Title, false, context, current));
sb.Append("</li>");
}
sb.Append("</ul>");
return sb.ToString();
}
/// <summary>
/// Builds the wanted pages list.
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The list.</returns>
private static string BuildWantedPagesList(NamespaceInfo nspace) {
Dictionary<string, List<string>> wanted = Pages.GetWantedPages(nspace != null ? nspace.Name : null);
if(wanted.Count == 0) return "";
StringBuilder sb = new StringBuilder(500);
sb.Append("<ul>");
foreach(string page in wanted.Keys) {
sb.Append("<li>");
sb.AppendFormat(@"<a href=""{0}{1}"" title=""{0}"" class=""unknownlink"">{2}</a>", page, Settings.PageExtension, NameTools.GetLocalName(page));
sb.Append("</li>");
}
sb.Append("</ul>");
return sb.ToString();
}
/// <summary>
/// Builds the incoming links list for a page (valid only in Phase3).
/// </summary>
/// <param name="page">The page.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current page, if any.</param>
/// <returns>The list.</returns>
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("<ul>");
foreach(string link in links) {
PageInfo linkedPage = Pages.FindPage(link);
if(linkedPage != null) {
PageContent content = Content.GetPageContent(linkedPage, false);
sb.Append("<li>");
sb.AppendFormat(@"<a href=""{0}{1}"" title=""{2}"" class=""pagelink"">{2}</a>", Tools.UrlEncode(link), Settings.PageExtension,
FormattingPipeline.PrepareTitle(content.Title, false, context, current));
sb.Append("</li>");
}
}
sb.AppendFormat("</ul>");
return sb.ToString();
}
/// <summary>
/// Builds the outgoing links list for a page (valid only in Phase3).
/// </summary>
/// <param name="page">The page.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current page, if any.</param>
/// <returns>The list.</returns>
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("<ul>");
foreach(string link in links) {
PageInfo linkedPage = Pages.FindPage(link);
if(linkedPage != null) {
PageContent content = Content.GetPageContent(linkedPage, false);
sb.Append("<li>");
sb.AppendFormat(@"<a href=""{0}{1}"" title=""{2}"" class=""pagelink"">{2}</a>", Tools.UrlEncode(link), Settings.PageExtension,
FormattingPipeline.PrepareTitle(content.Title, false, context, current));
sb.Append("</li>");
}
}
sb.Append("</ul>");
return sb.ToString();
}
/// <summary>
/// Builds the link to a namespace.
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The link.</returns>
private static string BuildNamespaceLink(string nspace) {
return "<a href=\"" + (string.IsNullOrEmpty(nspace) ? "" : Tools.UrlEncode(nspace) + ".") +
"Default.aspx\" class=\"pagelink\" title=\"" + (string.IsNullOrEmpty(nspace) ? "" : Tools.UrlEncode(nspace)) + "\">" +
(string.IsNullOrEmpty(nspace) ? "&lt;root&gt;" : nspace) + "</a>";
}
/// <summary>
/// Builds the namespace list.
/// </summary>
/// <returns>The namespace list.</returns>
private static string BuildNamespaceList() {
StringBuilder sb = new StringBuilder(100);
sb.Append("<ul>");
sb.Append("<li>");
sb.Append(BuildNamespaceLink(null));
sb.Append("</li>");
foreach(NamespaceInfo ns in Pages.GetNamespaces()) {
sb.Append("<li>");
sb.Append(BuildNamespaceLink(ns.Name));
sb.Append("</li>");
}
sb.Append("</ul>");
return sb.ToString();
}
/// <summary>
/// Processes line breaks.
/// </summary>
/// <param name="sb">The <see cref="T:StringBuilder" /> containing the text to process.</param>
/// <param name="bareBones">A value indicating whether the formatting is being done in bare-bones mode.</param>
private static void ProcessLineBreaks(StringBuilder sb, bool bareBones) {
if(bareBones || AreSingleLineBreaksToBeProcessed()) {
// Replace new-lines only when not enclosed in <nobr> 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", "<br />");
sb.Replace(SingleBrPlaceHolder, "\n");
}
else {
// Replace new-lines only when not enclosed in <nobr> 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", "<br /><br />");
sb.Replace(SingleBrPlaceHolder, "\n");
}
sb.Replace("<br>", "<br />");
// BR Hacks
sb.Replace("</ul><br /><br />", "</ul><br />");
sb.Replace("</ol><br /><br />", "</ol><br />");
sb.Replace("</table><br /><br />", "</table><br />");
sb.Replace("</pre><br /><br />", "</pre><br />");
if(AreSingleLineBreaksToBeProcessed()) {
sb.Replace("</h1><br />", "</h1>");
sb.Replace("</h2><br />", "</h2>");
sb.Replace("</h3><br />", "</h3>");
sb.Replace("</h4><br />", "</h4>");
sb.Replace("</h5><br />", "</h5>");
sb.Replace("</h6><br />", "</h6>");
sb.Replace("</div><br />", "</div>");
}
else {
sb.Replace("</div><br /><br />", "</div><br />");
}
sb.Replace("<nobr>", "");
sb.Replace("</nobr>", "");
}
/// <summary>
/// Gets a value indicating whether or not to process single line breaks.
/// </summary>
/// <returns><c>true</c> if SLB are to be processed, <c>false</c> otherwise.</returns>
private static bool AreSingleLineBreaksToBeProcessed() {
return Settings.ProcessSingleLineBreaks;
}
/// <summary>
/// Replaces the {TOC} markup in a string.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="cachedToc">The TOC replacement.</param>
/// <returns>The final string.</returns>
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;
}
/// <summary>
/// Expands a regex selection to match the number of open curly brackets.
/// </summary>
/// <param name="sb">The buffer.</param>
/// <param name="index">The match start index.</param>
/// <param name="value">The match value.</param>
/// <returns>The balanced string, or <c>null</c> if the brackets could not be balanced.</returns>
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;
}
/// <summary>
/// Formats a snippet.
/// </summary>
/// <param name="capturedMarkup">The captured markup.</param>
/// <param name="cachedToc">The TOC content (trick to allow {TOC} to be inserted in snippets).</param>
/// <returns>The formatted result.</returns>
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 @"<b style=""color: #FF0000;"">FORMATTER ERROR (Snippet Not Found)</b>";
// 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<int> parametersLines = new List<int>(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<string, string> values = new Dictionary<string, string>(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<string, string> 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);
}
/// <summary>
/// Format classic number-parameterized snippets.
/// </summary>
/// <param name="capturedMarkup">The captured markup to process.</param>
/// <returns>The formatted result.</returns>
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 @"<b style=""color: #FF0000;"">FORMATTER ERROR (Snippet Not Found)</b>";
}
}
/// <summary>
/// Splits a string at pipe characters, taking into account square brackets for links and images.
/// </summary>
/// <param name="data">The input data.</param>
/// <returns>The resuling splitted strings.</returns>
private static string[] CustomSplit(string data) {
// 1. Find all pipes that are not enclosed in square brackets
List<int> indices = new List<int>(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<string> result = new List<string>(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();
}
/// <summary>
/// Prepares the content of a snippet, properly managing parameters.
/// </summary>
/// <param name="parameters">The snippet parameters.</param>
/// <param name="snippet">The snippet original text.</param>
/// <returns>The prepared snippet text.</returns>
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(), "");
}
/// <summary>
/// Escapes all the characters used by the WikiMarkup.
/// </summary>
/// <param name="content">The Content.</param>
/// <returns>The escaped Content.</returns>
private static string EscapeWikiMarkup(string content) {
StringBuilder sb = new StringBuilder(content);
sb.Replace("&", "&amp;"); // Before all other escapes!
sb.Replace("#", "&#35;");
sb.Replace("*", "&#42;");
sb.Replace("<", "&lt;");
sb.Replace(">", "&gt;");
sb.Replace("[", "&#91;");
sb.Replace("]", "&#93;");
sb.Replace("{", "&#123;");
sb.Replace("}", "&#125;");
sb.Replace("'''", "&#39;&#39;&#39;");
sb.Replace("''", "&#39;&#39;");
sb.Replace("=====", "&#61;&#61;&#61;&#61;&#61;");
sb.Replace("====", "&#61;&#61;&#61;&#61;");
sb.Replace("===", "&#61;&#61;&#61;");
sb.Replace("==", "&#61;&#61;");
sb.Replace("<22><>", "&#167;&#167;");
sb.Replace("__", "&#95;&#95;");
sb.Replace("--", "&#45;&#45;");
sb.Replace("@@", "&#64;&#64;");
sb.Replace(":", "&#58;");
return sb.ToString();
}
/// <summary>
/// Removes all the characters used by the WikiMarkup.
/// </summary>
/// <param name="content">The Content.</param>
/// <returns>The stripped Content.</returns>
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("<22><>", "");
sb.Replace("__", "");
sb.Replace("--", "");
sb.Replace("@@", "");
return sb.ToString();
}
/// <summary>
/// Removes all HTML markup from a string.
/// </summary>
/// <param name="content">The string.</param>
/// <returns>The result.</returns>
public static string StripHtml(string content) {
if(string.IsNullOrEmpty(content)) return "";
StringBuilder sb = new StringBuilder(Regex.Replace(content, "<[^>]*>", " "));
sb.Replace("&nbsp;", "");
sb.Replace(" ", " ");
return sb.ToString();
}
/// <summary>
/// Builds a Link.
/// </summary>
/// <param name="targetUrl">The (raw) HREF.</param>
/// <param name="title">The name/title.</param>
/// <param name="isImage">True if the link contains an Image as "visible content".</param>
/// <param name="imageTitle">The title of the image.</param>
/// <param name="forIndexing">A value indicating whether the formatting is being done for content indexing.</param>
/// <param name="bareBones">A value indicating whether the formatting is being done in bare-bones mode.</param>
/// <param name="context">The formatting context.</param>
/// <param name="currentPage">The current page, or <c>null</c>.</param>
/// <param name="linkedPages">The linked pages list (both existent and inexistent).</param>
/// <returns>The formatted Link.</returns>
private static string BuildLink(string targetUrl, string title, bool isImage, string imageTitle,
bool forIndexing, bool bareBones, FormattingContext context, PageInfo currentPage, List<string> 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(@"<a id=""");
sb.Append(title.Substring(1));
sb.Append(@"""> </a>");
}
else if(targetUrl.StartsWith("#")) {
sb.Append(@"<a");
if(!isImage) sb.Append(@" class=""internallink""");
if(blank) sb.Append(@" target=""_blank""");
sb.Append(@" href=""");
//sb.Append(a);
UrlTools.BuildUrl(sb, targetUrl);
sb.Append(@""" title=""");
if(!isImage && title.Length > 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("</a>");
}
else if(targetUrl.StartsWith("http://") || targetUrl.StartsWith("https://") || targetUrl.StartsWith("ftp://") || targetUrl.StartsWith("file://")) {
// The link is complete
sb.Append(@"<a");
if(!isImage) sb.Append(@" class=""externallink""");
sb.Append(@" href=""");
sb.Append(targetUrl);
sb.Append(@""" title=""");
if(!isImage && title.Length > 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("</a>");
}
else if(targetUrl.StartsWith(@"\\") || targetUrl.StartsWith("//")) {
// The link is a UNC path
sb.Append(@"<a");
if(!isImage) sb.Append(@" class=""externallink""");
sb.Append(@" href=""file://///");
sb.Append(targetUrl.Substring(2));
sb.Append(@""" title=""");
if(!isImage && title.Length > 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("</a>");
}
else if(targetUrl.IndexOf("@") != -1 && targetUrl.IndexOf(".") != -1) {
// Email
sb.Append(@"<a");
if(!isImage) sb.Append(@" class=""emaillink""");
if(blank) sb.Append(@" target=""_blank""");
sb.Append(@" href=""mailto:");
sb.Append(Tools.ObfuscateText(targetUrl.Replace("&amp;", "%26"))); // Trick to let ampersands work in email addresses
sb.Append(@""" title=""");
if(!isImage && title.Length > 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("</a>");
}
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(@"<a");
if(!isImage) sb.Append(@" class=""internallink""");
if(blank) sb.Append(@" target=""_blank""");
sb.Append(@" href=""");
//sb.Append(a);
if(targetUrl.ToLowerInvariant().StartsWith("getfile.aspx")) sb.Append(targetUrl);
else UrlTools.BuildUrl(sb, targetUrl);
sb.Append(@""" title=""");
if(!isImage && title.Length > 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("</a>");
}
else {
if(targetUrl.IndexOf(".aspx") != -1) {
// The link points to a "system" page
sb.Append(@"<a");
if(!isImage) sb.Append(@" class=""systemlink""");
if(blank) sb.Append(@" target=""_blank""");
sb.Append(@" href=""");
//sb.Append(a);
UrlTools.BuildUrl(sb, targetUrl);
sb.Append(@""" title=""");
if(!isImage && title.Length > 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("</a>");
}
else {
if(targetUrl.StartsWith("c:") || targetUrl.StartsWith("C:")) {
// Category link
//sb.Append(@"<a href=""AllPages.aspx?Cat=");
//sb.Append(Tools.UrlEncode(a.Substring(2)));
sb.Append(@"<a href=""");
UrlTools.BuildUrl(sb, "AllPages.aspx?Cat=", Tools.UrlEncode(targetUrl.Substring(2)));
sb.Append(@""" class=""systemlink"" title=""");
if(!isImage && title.Length > 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("</a>");
}
else if(targetUrl.Contains(":") || targetUrl.ToLowerInvariant().Contains("%3a") || targetUrl.Contains("&") || targetUrl.Contains("%26")) {
sb.Append(@"<b style=""color: #FF0000;"">FORMATTER ERROR ("":"" and ""&"" not supported in Page Names)</b>");
}
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(@"<a");
if(!isImage) sb.Append(@" class=""unknownlink""");
if(blank) sb.Append(@" target=""_blank""");
sb.Append(@" href=""");
//sb.Append(a);
UrlTools.BuildUrl(sb, explicitNamespace ? "++" : "", targetUrl);
sb.Append(@""" title=""");
/*if(!isImage && title.Length > 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("</a>");
}
else {
sb.Append(@"<a");
if(!isImage) sb.Append(@" class=""pagelink""");
if(blank) sb.Append(@" target=""_blank""");
sb.Append(@" href=""");
//sb.Append(a);
UrlTools.BuildUrl(sb, explicitNamespace ? "++" : "", targetUrl);
sb.Append(@""" title=""");
/*if(!isImage && title.Length > 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("</a>");
}
}
}
}
return sb.ToString();
}
/// <summary>
/// Detects all the Headers in a block of text (H1, H2, H3, H4).
/// </summary>
/// <param name="text">The text.</param>
/// <returns>The List of Header objects, in the same order as they are in the text.</returns>
public static List<HPosition> DetectHeaders(string text) {
Match match;
string h;
int end = 0;
List<int> noWikiBegin = new List<int>(), noWikiEnd = new List<int>();
List<HPosition> hPos = new List<HPosition>();
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;
}
/// <summary>
/// Builds the "Edit" links for page sections.
/// </summary>
/// <param name="id">The section ID.</param>
/// <param name="page">The page name.</param>
/// <returns>The link.</returns>
private static string BuildEditSectionLink(int id, string page) {
if(!Settings.EnableSectionEditing) return "";
StringBuilder sb = new StringBuilder(100);
sb.Append(@"<a href=""");
UrlTools.BuildUrl(sb, "Edit.aspx?Page=", Tools.UrlEncode(page), "&amp;Section=", id.ToString());
sb.Append(@""" class=""editsectionlink"">");
sb.Append(EditSectionPlaceHolder);
sb.Append("</a>");
return sb.ToString();
}
/// <summary>
/// Generates list HTML markup.
/// </summary>
/// <param name="lines">The lines in the list WikiMarkup.</param>
/// <param name="line">The current line.</param>
/// <param name="level">The current level.</param>
/// <param name="currLine">The current line.</param>
/// <returns>The correct HTML markup.</returns>
private static string GenerateList(string[] lines, int line, int level, ref int currLine) {
StringBuilder sb = new StringBuilder(200);
if(lines[currLine][level] == '*') sb.Append("<ul>");
else if(lines[currLine][level] == '#') sb.Append("<ol>");
while(currLine <= lines.Length - 1 && CountBullets(lines[currLine]) >= level + 1) {
if(CountBullets(lines[currLine]) == level + 1) {
sb.Append("<li>");
sb.Append(lines[currLine].Substring(CountBullets(lines[currLine])).Trim());
sb.Append("</li>");
currLine++;
}
else {
sb.Remove(sb.Length - 5, 5);
sb.Append(GenerateList(lines, currLine, level + 1, ref currLine));
sb.Append("</li>");
}
}
if(lines[line][level] == '*') sb.Append("</ul>");
else if(lines[line][level] == '#') sb.Append("</ol>");
return sb.ToString();
}
/// <summary>
/// Counts the bullets in a list line.
/// </summary>
/// <param name="line">The line.</param>
/// <returns>The number of bullets.</returns>
private static int CountBullets(string line) {
int res = 0, count = 0;
while(line[count] == '*' || line[count] == '#') {
res++;
count++;
}
return res;
}
/// <summary>
/// Extracts the bullets from a list line.
/// </summary>
/// <param name="value">The line.</param>
/// <returns>The bullets.</returns>
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;
}
/// <summary>
/// Builds the TOC of a document.
/// </summary>
/// <param name="hPos">The positions of headers.</param>
/// <returns>The TOC HTML markup.</returns>
private static string BuildToc(List<HPosition> hPos) {
StringBuilder sb = new StringBuilder();
hPos.Sort(new HPositionComparer());
// Table only used to workaround IE idiosyncrasies - use TocCointainer for styling
sb.Append(@"<table id=""TocContainerTable""><tr><td>");
sb.Append(@"<div id=""TocContainer"">");
sb.Append(@"<p class=""small"">");
sb.Append(TocTitlePlaceHolder);
sb.Append("</p>");
sb.Append(@"<div id=""Toc"">");
sb.Append("<p><br />");
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("&nbsp;&nbsp;&nbsp;");
break;
case 3:
sb.Append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
break;
case 4:
sb.Append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
break;
}
if(hPos[i].Level == 1) sb.Append("<b>");
if(hPos[i].Level == 4) sb.Append("<small>");
sb.Append(@"<a href=""#");
sb.Append(BuildHAnchor(hPos[i].Text, hPos[i].ID.ToString()));
sb.Append(@""">");
sb.Append(StripWikiMarkup(StripHtml(hPos[i].Text)));
sb.Append("</a>");
if(hPos[i].Level == 4) sb.Append("</small>");
if(hPos[i].Level == 1) sb.Append("</b>");
sb.Append("<br />");
}
sb.Append("</p>");
sb.Append("</div>");
sb.Append("</div>");
sb.Append("</td></tr></table>");
return sb.ToString();
}
/// <summary>
/// Builds a valid anchor name from a string.
/// </summary>
/// <param name="h">The string, usually a header (Hx).</param>
/// <returns>The anchor ID.</returns>
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();
}
/// <summary>
/// Builds a valid and unique anchor name from a string.
/// </summary>
/// <param name="h">The string, usually a header (Hx).</param>
/// <param name="uid">The unique ID.</param>
/// <returns>The anchor ID.</returns>
public static string BuildHAnchor(string h, string uid) {
return BuildHAnchor(h) + "_" + uid;
}
/// <summary>
/// Escapes ampersands in a URL.
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>The escaped URL.</returns>
private static string EscapeUrl(string url) {
return url.Replace("&", "&amp;");
}
/// <summary>
/// Builds a HTML table from WikiMarkup.
/// </summary>
/// <param name="table">The WikiMarkup.</param>
/// <returns>The HTML.</returns>
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 "<b>FORMATTER ERROR (Malformed Table)</b>";
}
StringBuilder sb = new StringBuilder();
sb.Append("<table");
if(lines[0].Length > 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("<caption>");
sb.Append(lines[1].Substring(3));
sb.Append("</caption>");
count++;
}
if(!lines[count].StartsWith("|-")) sb.Append("<tr>");
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("</tr>");
sb.Append("<tr");
if(lines[i].Length > 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("<td>");
sb.Append(item.Replace(" || ", "</td><td>"));
sb.Append("</td>");
}
else if(item.IndexOf(" | ") != -1) {
sb.Append("<td ");
sb.Append(item.Substring(0, item.IndexOf(" | ")));
sb.Append(">");
sb.Append(item.Substring(item.IndexOf(" | ") + 3));
sb.Append("</td>");
}
else {
sb.Append("<td>");
sb.Append(item);
sb.Append("</td>");
}
}
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("<th>");
sb.Append(item.Replace(" !! ", "</th><th>"));
sb.Append("</th>");
}
else if(item.IndexOf(" ! ") != -1) {
sb.Append("<th ");
sb.Append(item.Substring(0, item.IndexOf(" ! ")));
sb.Append(">");
sb.Append(item.Substring(item.IndexOf(" ! ") + 3));
sb.Append("</th>");
}
else {
sb.Append("<th>");
sb.Append(item);
sb.Append("</th>");
}
}
}
if(sb.ToString().EndsWith("<tr>")) {
sb.Remove(sb.Length - 4 - 1, 4);
sb.Append("</table>");
}
else {
sb.Append("</tr></table>");
}
sb.Replace("<tr></tr>", "");
// Add <thead>, <tbody> tags, if table contains header
if(thAdded) {
int thIndex = sb.ToString().IndexOf("<th");
//if(thIndex >= 4) sb.Insert(thIndex - 4, "<thead>");
sb.Insert(thIndex - 4, "<thead>");
// search for the last </th> tag in the first row of the table
int thCloseIndex = -1;
int thCloseIndex_temp = -1;
do {
thCloseIndex = thCloseIndex_temp;
thCloseIndex_temp = sb.ToString().IndexOf("</th>", thCloseIndex + 1);
}
while (thCloseIndex_temp != -1 && thCloseIndex_temp < sb.ToString().IndexOf("</tr>"));
sb.Insert(thCloseIndex + 10, "</thead><tbody>");
sb.Insert(sb.Length - 8, "</tbody>");
}
return sb.ToString();
}
/// <summary>
/// Builds an indented text block.
/// </summary>
/// <param name="indent">The input text.</param>
/// <returns>The result.</returns>
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 @"<div style=""margin: 0px; padding: 0px; padding-left: " + ((int)(colons * 15)).ToString() + @"px"">" + indent + "</div>";
}
/// <summary>
/// Builds the tag cloud.
/// </summary>
/// <param name="currentNamespace">The current namespace (<c>null</c> for the root).</param>
/// <returns>The tag cloud.</returns>
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<CategoryInfo> categories = Pages.GetCategories(currentNamespace);
for(int i = 0; i < categories.Count; i++) {
if(categories[i].Pages.Length > 0) {
//sb.Append(@"<a href=""AllPages.aspx?Cat=");
//sb.Append(Tools.UrlEncode(categories[i].FullName));
sb.Append(@"<a href=""");
UrlTools.BuildUrl(sb, "AllPages.aspx?Cat=", Tools.UrlEncode(categories[i].FullName));
sb.Append(@""" style=""font-size: ");
sb.Append(ComputeSize((float)categories[i].Pages.Length / (float)tot * 100F).ToString());
sb.Append(@"px;"">");
sb.Append(NameTools.GetLocalName(categories[i].FullName));
sb.Append("</a>");
}
if(i != categories.Count - 1) sb.Append(" ");
}
return sb.ToString();
}
/// <summary>
/// Computes the sixe of a category label in the category cloud.
/// </summary>
/// <param name="percentage">The occurrence percentage of the category.</param>
/// <returns>The computes size.</returns>
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
}
/// <summary>
/// Computes the positions of all NOWIKI tags.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="noWikiBegin">The output list of begin indexes of NOWIKI tags.</param>
/// <param name="noWikiEnd">The output list of end indexes of NOWIKI tags.</param>
private static void ComputeNoWiki(string text, ref List<int> noWikiBegin, ref List<int> 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);
}
}
/// <summary>
/// Determines whether a character is enclosed in a NOWIKI tag.
/// </summary>
/// <param name="index">The index of the character.</param>
/// <param name="noWikiBegin">The list of begin indexes of NOWIKI tags.</param>
/// <param name="noWikiEnd">The list of end indexes of NOWIKI tags.</param>
/// <param name="end">The end index of the NOWIKI tag that encloses the specified character, or zero.</param>
/// <returns><c>true</c> if the specified character is enclosed in a NOWIKI tag, <c>false</c> otherwise.</returns>
private static bool IsNoWikied(int index, List<int> noWikiBegin, List<int> 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;
}
/// <summary>
/// Performs the internal Phase 3 of the Formatting pipeline.
/// </summary>
/// <param name="raw">The raw data.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current PageInfo, if any.</param>
/// <returns>The formatted content.</returns>
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 = "&lt;root&gt;";
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 = "&lt;root&gt;";
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());
}
sb.Replace(SectionLinkTextPlaceHolder, Exchanger.ResourceExchanger.GetResource("LinkToThisSection"));
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("<b>");
dummy.Append(Exchanger.ResourceExchanger.GetResource("TableOfContents"));
dummy.Append(@"</b><span id=""ExpandTocSpan""> [<a href=""#"" onclick=""javascript:if(document.getElementById('Toc').style['display']=='none') document.getElementById('Toc').style['display']=''; else document.getElementById('Toc').style['display']='none'; return false;"">");
dummy.Append(Exchanger.ResourceExchanger.GetResource("HideShow"));
dummy.Append("</a>]</span>");
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(@"<span class=""signature"">");
dummy.Append(Users.UserLink(fields[0]));
dummy.Append(", ");
dummy.Append(Preferences.AlignWithTimezone(DateTime.Parse(fields[1])).ToString(Settings.DateTimeFormat));
dummy.Append("</span>");
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();
}
/// <summary>
/// Builds the current namespace drop-down list.
/// </summary>
/// <returns>The drop-down list HTML markup.</returns>
private static string BuildCurrentNamespaceDropDown() {
string ns = Tools.DetectCurrentNamespace();
if(ns == null) ns = "";
string currentUser = SessionFacade.GetCurrentUsername();
string[] currentGroups = SessionFacade.GetCurrentGroupNames();
List<NamespaceInfo> allNamespaces = Pages.GetNamespaces();
List<string> allowedNamespaces = new List<string>(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("<select class=\"namespacedropdown\" onchange=\"javascript:var sel = this.value; document.location = (sel != '' ? (sel + '.') : '') + 'Default.aspx';\">");
foreach(string nspace in allowedNamespaces) {
sb.AppendFormat("<option{0} value=\"{1}\">{2}</option>", nspace == ns ? " selected=\"selected\"" : "",
nspace, nspace == "" ? "&lt;root&gt;" : nspace);
}
sb.Append("</select>");
return sb.ToString();
}
/// <summary>
/// Gets the link to the profile page.
/// </summary>
/// <param name="username">The username.</param>
/// <returns>The link.</returns>
private static string GetProfileLink(string username) {
UserInfo user = Users.FindUser(username);
StringBuilder sb = new StringBuilder(200);
sb.Append("<a href=\"");
sb.Append(UrlTools.BuildUrl("Profile.aspx"));
sb.Append("\" class=\"systemlink\" title=\"");
sb.Append(Exchanger.ResourceExchanger.GetResource("GoToYourProfile"));
sb.Append("\">");
sb.Append(user != null ? Users.GetDisplayName(user) : username);
sb.Append("</a>");
return sb.ToString();
}
/// <summary>
/// Gets the link to the language page.
/// </summary>
/// <param name="username">The username.</param>
/// <returns>The link.</returns>
private static string GetLanguageLink(string username) {
UserInfo user = Users.FindUser(username);
StringBuilder sb = new StringBuilder(200);
sb.Append("<a href=\"");
sb.Append(UrlTools.BuildUrl("Language.aspx"));
sb.Append("\" class=\"systemlink\" title=\"");
sb.Append(Exchanger.ResourceExchanger.GetResource("SelectYourLanguage"));
sb.Append("\">");
sb.Append(user != null ? Users.GetDisplayName(user) : username);
sb.Append("</a>");
return sb.ToString();
}
/// <summary>
/// Gets the login link.
/// </summary>
/// <returns>The login link.</returns>
private static string GetLoginLink() {
string login = Exchanger.ResourceExchanger.GetResource("Login");
StringBuilder sb = new StringBuilder(200);
sb.Append("<a href=\"");
sb.Append(UrlTools.BuildUrl("Login.aspx?Redirect=", Tools.UrlEncode(HttpContext.Current.Request.Url.ToString())));
sb.Append("\" class=\"systemlink\" title=\"");
sb.Append(login);
sb.Append("\">");
sb.Append(login);
sb.Append("</a>");
return sb.ToString();
}
/// <summary>
/// Gets the logout link.
/// </summary>
/// <returns>The logout link.</returns>
private static string GetLogoutLink() {
string login = Exchanger.ResourceExchanger.GetResource("Logout");
StringBuilder sb = new StringBuilder(200);
sb.Append("<a href=\"");
sb.Append(UrlTools.BuildUrl("Login.aspx?ForceLogout=1&amp;Redirect=", Tools.UrlEncode(HttpContext.Current.Request.Url.ToString())));
sb.Append("\" class=\"systemlink\" title=\"");
sb.Append(login);
sb.Append("\">");
sb.Append(login);
sb.Append("</a>");
return sb.ToString();
}
}
/// <summary>
/// Represents a Header.
/// </summary>
public class HPosition {
private int index;
private string text;
private int level;
private int id;
/// <summary>
/// Initializes a new instance of the <b>HPosition</b> class.
/// </summary>
/// <param name="index">The Index.</param>
/// <param name="text">The Text.</param>
/// <param name="level">The Header level.</param>
/// <param name="id">The Unique ID of the Header (0-based counter).</param>
public HPosition(int index, string text, int level, int id) {
this.index = index;
this.text = text;
this.level = level;
this.id = id;
}
/// <summary>
/// Gets or sets the Index.
/// </summary>
public int Index {
get { return index; }
set { index = value; }
}
/// <summary>
/// Gets or sets the Text.
/// </summary>
public string Text {
get { return text; }
set { text = value; }
}
/// <summary>
/// Gets or sets the Level.
/// </summary>
public int Level {
get { return level; }
set { level = value; }
}
/// <summary>
/// Gets or sets the ID (0-based counter).
/// </summary>
public int ID {
get { return id; }
set { id = value; }
}
}
/// <summary>
/// Compares HPosition objects.
/// </summary>
public class HPositionComparer : IComparer<HPosition> {
/// <summary>
/// Performs the comparison.
/// </summary>
/// <param name="x">The first object.</param>
/// <param name="y">The second object.</param>
/// <returns>The comparison result.</returns>
public int Compare(HPosition x, HPosition y) {
return x.Index.CompareTo(y.Index);
}
}
}