private void ProcessInternalLink(Regex fileRegex, XDoc addr, Title baseTitle, IList<Title> linksKnown, IList<string> linksBroken, List<Title> linksFound, List<Title> templates, DekiContext deki, out Title lookupLink) { lookupLink = null; XmlElement current = (XmlElement)addr.AsXmlNode; string template = current.GetAttribute("template"); bool isTemplate = !String.IsNullOrEmpty(template); // if this is a link to an anchor on the current page, stop processing string href = current.GetAttribute("href").Trim(); if((href.StartsWithInvariant("#") && !isTemplate) || (href.StartsWithInvariant("{{") && href.EndsWithInvariant("}}"))) { return; } // if this is a link to a subpage from an imported template, process it if((ParserMode.SAVE == _mode) && isTemplate) { addr.RemoveAttr("template"); addr.RemoveAttr("class"); Title templateTitle = Title.FromUIUri(baseTitle, template, false); templates.Add(templateTitle); // update the link to be relative to the current page instead of the template string[] segments = templateTitle.AsUnprefixedDbSegments(); segments[0] = "."; template = String.Join("/", segments); addr.Attr("href", template); href = template; } List<string> css = new List<string>(); bool containsImage = !addr[".//img"].IsEmpty; uint fileid; Title title; // Extract the fileid and title information // If the link is of the form "@api/deki/files/{fileid}/={filename}, extract the fileid and filename from it. // Otherwise, use the full URL as the title. Match m = fileRegex.Match(XUri.Decode(href)); if(m.Success) { UInt32.TryParse(m.Groups["fileid"].Value, out fileid); title = Title.FromUIUri(null, "File:/" + m.Groups["filename"].Value); } else { uint.TryParse(current.GetAttribute("fileid"), out fileid); title = Title.FromUIUri(baseTitle, href); } // If the link has no text provide a default value if(addr.Contents.Length == 0) { addr.Value(title.IsFile ? title.Filename : Title.FromUIUri(null, href).AsUserFriendlyName()); } // add the db encoded title to the list of links to lookup if(!title.IsFile && title.IsEditable) { linksFound.Add(title); } switch(_mode) { case ParserMode.EDIT: // if the title is a file, return a link in the form @api/deki/files/{fileid}/={filename} // TODO (brigettek): Append the query param if(title.IsFile) { if(null != _relToTitle) { // Normalize file link if needed (for import/export) Title fileTitle = GetTitleFromFileId(fileid); if(null != fileTitle) { addr.RemoveAttr("href"); addr.Attr("href.path", fileTitle.AsRelativePath(_relToTitle)); addr.Attr("href.filename", fileTitle.Filename); } } else { if(0 == fileid) { addr.Attr("href", title.AsEditorUriPath()); } else { addr.Attr("href", deki.ApiUri.At("files", fileid.ToString(), Title.AsApiParam(title.Filename)).ToString()); } } } else { // TODO (brigettek): Prevent resolving redirects for pages with many links to improve performance // TODO (brigettek): potential vulnerability. The title is resolved without verifying that the user has browse permission to it. PageBE redirectedPage = PageBL.ResolveRedirects(PageBL.GetPageByTitle(title)); redirectedPage.Title.Anchor = title.Anchor; redirectedPage.Title.Query = title.Query; title = redirectedPage.Title; if(null != _relToTitle) { // Normalize link if needed (for import/export) addr.RemoveAttr("href"); addr.Attr("href.path", title.AsRelativePath(_relToTitle)); addr.Attr("href.anchor", title.Anchor); addr.Attr("href.query", title.Query); } else { addr.Attr("href", title.AsEditorUriPath()); } } if(string.IsNullOrEmpty(current.GetAttribute("title"))) { addr.Attr("title", title.IsFile ? title.Filename : title.AsPrefixedUserFriendlyPath()); } addr.RemoveAttr("fileid"); break; case ParserMode.VIEW_NO_EXECUTE: case ParserMode.VIEW: { string rel = addr["@rel"].AsText; addr.Attr("rel", "internal"); // check if path was generated, if so, keep it as it is if(string.IsNullOrEmpty(rel)) { // check if path is a reference to current page and, if so, make it bold ParserState parseState = GetParseState(); foreach(PageBE includingPage in parseState.ProcessingStack) { XDoc item; if((includingPage.Title == title && parseState.ProcessedPages.TryGetValue(GetParserCacheKey(includingPage, _mode), out item) && (item == null))) { XDoc strong = new XDoc("strong"); foreach(XmlNode node in addr.AsXmlNode.ChildNodes) { strong.AsXmlNode.AppendChild(strong.AsXmlNode.OwnerDocument.ImportNode(node, true)); } addr.Replace(strong); return; } } } } // check if link goes to a file attachment if(title.IsFile) { // if the file does not exist, display a message accordingly if(0 == fileid) { css.Add("new"); if(!containsImage) { var resources = DekiContext.Current.Resources; addr.AddNodesBefore(DekiScriptRuntime.CreateWarningElement(null, "(" + resources.Localize(DekiResources.MISSING_FILE(title.AsPrefixedUserFriendlyPath()) + ")"), null)); } } else { if(!containsImage) { css.Add("iconitext-16"); css.Add("ext-" + title.Extension); } addr.Attr("href", deki.ApiUri.At("files", fileid.ToString(), Title.AsApiParam(title.Filename))); } } else { // check if page exists by first inspecting the links table and, if not found, searching for the page title if(title.IsEditable && !linksKnown.Contains(Title.FromDbPath(title.Namespace, title.Path, null))) { if(linksBroken.Contains(title.AsPrefixedDbPath().ToLowerInvariant())) { css.Add("new"); } else { lookupLink = title; } } // check if link goes to a user's page if((title.IsUser) && (1 == title.AsUnprefixedDbSegments().Length)) { css.Add("link-user"); } // Update the link to use the site uri addr.Attr("href", Utils.AsPublicUiUri(title)); } if(css.Count > 0 && !containsImage) { css.Add(current.GetAttribute("class")); addr.Attr("class", string.Join(" ", css.ToArray())); } if(string.IsNullOrEmpty(current.GetAttribute("title"))) { addr.Attr("title", title.IsFile ? title.Filename : title.AsPrefixedUserFriendlyPath()); } addr.RemoveAttr("fileid"); break; } }