/// <summary> /// This is used to extract table of content information from a file /// that will appear in the help file's table of content. /// </summary> /// <param name="filename">The file from which to extract the /// information</param> /// <returns>The table of content entry</returns> private static TocEntry GetTocInfo(string filename) { TocEntry tocEntry; Encoding enc = Encoding.Default; string content; content = BuildProcess.ReadWithEncoding(filename, ref enc); tocEntry = new TocEntry(); tocEntry.IncludePage = !reTocExclude.IsMatch(content); tocEntry.IsDefaultTopic = reIsDefaultTopic.IsMatch(content); Match m = reSortOrder.Match(content); if (m.Success) { tocEntry.SortOrder = Convert.ToInt32( m.Groups["SortOrder"].Value, CultureInfo.InvariantCulture); } // Get the page title if possible. If not found, use the filename // without the path or extension as the page title. m = rePageTitle.Match(content); if (!m.Success) { tocEntry.Title = Path.GetFileNameWithoutExtension(filename); } else { tocEntry.Title = HttpUtility.HtmlDecode( m.Groups["Title"].Value).Replace("\r", "").Replace("\n", ""); } // Since we've got the file loaded, see if there are links // that need to be resolved when the file is copied, if it // contains <pre> blocks that should be colorized, or if it // contains tags or shared content items that need replacing. tocEntry.HasLinks = reResolveLinks.IsMatch(content); tocEntry.HasCodeBlocks = reCodeBlock.IsMatch(content); tocEntry.NeedsColorizing = reColorizeCheck.IsMatch(content); tocEntry.HasProjectTags = (reProjectTags.IsMatch(content) || reSharedContent.IsMatch(content)); return(tocEntry); }
/// <summary> /// This is called to load an additional content file, resolve links /// to namespace content and copy it to the output folder. /// </summary> /// <param name="sourceFile">The source filename to copy</param> /// <param name="destFile">The destination filename</param> /// <param name="entry">The entry being resolved.</param> private void ResolveLinksAndCopy(string sourceFile, string destFile, TocEntry entry) { Encoding enc = Encoding.Default; string content, script, syntaxFile; int pos; this.ReportProgress("{0} -> {1}", sourceFile, destFile); // When reading the file, use the default encoding but detect the // encoding if byte order marks are present. content = BuildProcess.ReadWithEncoding(sourceFile, ref enc); // Use a regular expression to find and replace all <see> // tags with link to the help file content. if (entry.HasLinks) { content = reResolveLinks.Replace(content, linkMatchEval); } // Expand <code> tags if necessary if (entry.HasCodeBlocks) { content = reCodeBlock.Replace(content, codeBlockMatchEval); } // Colorize <pre> tags if necessary if (entry.NeedsColorizing || entry.HasCodeBlocks) { // Initialize code colorizer on first use if (codeColorizer == null) { codeColorizer = new CodeColorizer( shfbFolder + @"Colorizer\highlight.xml", shfbFolder + @"Colorizer\highlight.xsl"); } // Set the path the "Copy" image codeColorizer.CopyImageUrl = pathToRoot + "icons/CopyCode.gif"; // Colorize it and replace the "Copy" literal text with the // shared content include item so that it gets localized. content = codeColorizer.ProcessAndHighlightText(content); content = content.Replace(codeColorizer.CopyText + "</span", "<include item=\"copyCode\"/></span"); entry.HasProjectTags = true; // Add the links to the colorizer stylesheet and script files script = String.Format(CultureInfo.InvariantCulture, "<link type='text/css' rel='stylesheet' " + "href='{0}html/highlight.css' />" + "<script type='text/javascript' " + "src='{0}html/highlight.js'></script>", pathToRoot); pos = content.IndexOf("</head>"); // Create a <head> section if one doesn't exist if (pos == -1) { script = "<head>" + script + "</head>"; pos = content.IndexOf("<html>"); if (pos != -1) { pos += 6; } else { pos = 0; } } content = content.Insert(pos, script); // Copy the colorizer files if not already there if (!File.Exists(workingFolder + @"Output\html\highlight.css")) { syntaxFile = shfbFolder + @"Colorizer\highlight.css"; File.Copy(syntaxFile, workingFolder + @"Output\html\highlight.css"); File.SetAttributes(syntaxFile, FileAttributes.Normal); syntaxFile = shfbFolder + @"Colorizer\highlight.js"; File.Copy(syntaxFile, workingFolder + @"Output\html\highlight.js"); File.SetAttributes(syntaxFile, FileAttributes.Normal); // This one may exist as the default presentation styles // contain an image by this name. syntaxFile = shfbFolder + @"Colorizer\CopyCode.gif"; File.Copy(syntaxFile, workingFolder + @"Output\icons\CopyCode.gif", true); File.SetAttributes(syntaxFile, FileAttributes.Normal); } } // Replace project option tags with project option values if (entry.HasProjectTags) { content = reProjectTags.Replace(content, fieldMatchEval); // Shared content items can be nested while (reSharedContent.IsMatch(content)) { content = reSharedContent.Replace(content, contentMatchEval); } } // Write the file back out with the appropriate encoding using (StreamWriter sw = new StreamWriter(destFile, false, enc)) { sw.Write(content); } }
/// <summary> /// This copies additional content files from the specified source /// folder to the specified destination folder and builds table of /// content entries for them. If any subfolders are found below the /// source folder and the wildcard is "*.*", the subfolders are also /// copied recursively. /// </summary> /// <param name="previewing">Pass true to generate the table of content /// collection for previewing without actually copying anything.</param> /// <param name="sourcePath">The source path from which to copy.</param> /// <param name="destPath">The destination path to which to copy.</param> /// <returns>The table of content entry for the folder and all /// of its children or null if there is nothing to show in the table /// of content for this item.</returns> protected TocEntry RecursiveContentCopy(bool previewing, string sourcePath, string destPath) { TocEntry tocFolder, tocEntry; string[] files; string filename, nameLower, rootPath = workingFolder + @"Output\"; bool hasContent = false; if (sourcePath == null) { throw new ArgumentNullException("sourcePath"); } if (destPath == null) { throw new ArgumentNullException("destPath"); } if (toc == null) { throw new ArgumentNullException("toc"); } int idx = sourcePath.LastIndexOf('\\'); string dirName = sourcePath.Substring(0, idx), fileSpec = sourcePath.Substring(idx + 1); tocFolder = new TocEntry(); tocFolder.SourceFile = dirName + @"\"; tocFolder.Title = dirName.Substring(dirName.LastIndexOf("\\") + 1); // Copy all the files in the folder files = Directory.GetFiles(dirName, fileSpec); foreach (string name in files) { if (exclusionList.ContainsKey(name)) { continue; } filename = destPath + Path.GetFileName(name); nameLower = name.ToLower(CultureInfo.InvariantCulture); if (nameLower.EndsWith(".htm") || nameLower.EndsWith(".html")) { tocEntry = BuildProcess.GetTocInfo(name); } else { tocEntry = null; } // If the filename matches the folder name and it's an HTML // file, extract the TOC info from it for the root node. // If there is no info or its excluded, the folder entry won't // have an associated page. Even if excluded, it will still use // the title, default topic, and sort order properties though. if (tocEntry != null) { if (Path.GetFileNameWithoutExtension(name) == tocFolder.Title) { tocFolder.Title = tocEntry.Title; tocFolder.IsDefaultTopic = tocEntry.IsDefaultTopic; tocFolder.SortOrder = tocEntry.SortOrder; tocFolder.SourceFile = name; hasContent = true; // In case it's this page only if (tocEntry.IncludePage) { tocFolder.DestinationFile = filename.Remove(0, rootPath.Length); if (tocFolder.IsDefaultTopic) { defaultTopic = tocFolder.DestinationFile; } } else { tocFolder.DestinationFile = null; } } else { if (tocEntry.IncludePage) { tocEntry.SourceFile = name; tocEntry.DestinationFile = filename.Remove(0, rootPath.Length); tocFolder.Children.Add(tocEntry); } if (tocEntry.IsDefaultTopic) { defaultTopic = tocEntry.DestinationFile; } } } if (!previewing && !Directory.Exists(destPath)) { Directory.CreateDirectory(destPath); } // If the file contains links that need to be resolved, // it is handled separately. if (!previewing && tocEntry != null && (tocEntry.HasLinks || tocEntry.HasCodeBlocks || tocEntry.NeedsColorizing || tocEntry.HasProjectTags)) { // Figure out the path to the root if needed string[] parts = destPath.Remove(0, rootPath.Length).Split('\\'); pathToRoot = String.Empty; for (int part = 0; part < parts.Length - 1; part++) { pathToRoot += "../"; } this.ResolveLinksAndCopy(name, filename, tocEntry); } else { this.ReportProgress("{0} -> {1}", name, filename); // All attributes are turned off so that we can delete // it later. if (!previewing) { File.Copy(name, filename, true); File.SetAttributes(filename, FileAttributes.Normal); } } } // For "*.*", copy subfolders too if (fileSpec == "*.*") { string[] subFolders = Directory.GetDirectories(dirName); foreach (string folder in subFolders) { tocEntry = this.RecursiveContentCopy(previewing, folder + @"\*.*", destPath + folder.Substring(dirName.Length + 1) + @"\"); if (tocEntry != null) { tocFolder.Children.Add(tocEntry); } } } return((hasContent || tocFolder.Children.Count != 0) ? tocFolder : null); }