/// <summary> /// This is used to merge destination file information into the site /// map TOC. /// </summary> /// <param name="site">The site entry to update</param> /// <remarks>In addition, files in the site map that do not exist in /// the TOC built from the defined content will be processed and /// copied to the root folder.</remarks> private void MergeTocInfo(TocEntry site) { TocEntry match; string source, filename; if (site.SourceFile.Path.Length != 0) { match = toc.Find(site.SourceFile); if (match != null) { site.DestinationFile = match.DestinationFile; } else { source = site.SourceFile; site.DestinationFile = Path.GetFileName(source); filename = site.DestinationFile; // .topic files get transformed into .html files if (source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { site.DestinationFile = Path.ChangeExtension(site.DestinationFile, ".html"); } // Check to see if anything needs resolving if (source.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) || source.EndsWith(".html", StringComparison.OrdinalIgnoreCase) || source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { match = BuildProcess.GetTocInfo(source); } foreach (string baseFolder in this.HelpFormatOutputFolders) { // If the file contains items that need to be resolved, it is handled separately if (match != null && (match.HasLinks || match.HasCodeBlocks || match.NeedsColorizing || match.HasProjectTags || source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))) { // Files are always copied to the root pathToRoot = String.Empty; this.ResolveLinksAndCopy(source, baseFolder + filename, match); } else { this.ReportProgress("{0} -> {1}{2}", source, baseFolder, filename); // All attributes are turned off so that we can delete it later File.Copy(source, baseFolder + filename, true); File.SetAttributes(baseFolder + filename, FileAttributes.Normal); } } } } if (site.Children.Count != 0) { foreach (TocEntry entry in site.Children) { this.MergeTocInfo(entry); } } }
/// <summary> /// This is used to transform a *.topic file into a *.html file using an XSLT transformation based on the /// presentation style. /// </summary> /// <param name="sourceFile">The source topic filename</param> private void XslTransform(string sourceFile) { TocEntry tocInfo; XmlReader reader = null; XmlWriter writer = null; XsltSettings settings; XmlReaderSettings readerSettings; XmlWriterSettings writerSettings; Encoding enc = Encoding.Default; FileItemCollection transforms; string content; string sourceStylesheet, destFile = Path.ChangeExtension(sourceFile, ".html"); try { readerSettings = new XmlReaderSettings(); readerSettings.CloseInput = true; readerSettings.DtdProcessing = DtdProcessing.Parse; // Create the transform on first use if (xslTransform == null) { transforms = new FileItemCollection(project, BuildAction.TopicTransform); if (transforms.Count != 0) { if (transforms.Count > 1) { this.ReportWarning("BE0011", "Multiple topic transformations found. Using '{0}'", transforms[0].FullPath); } sourceStylesheet = transforms[0].FullPath; } else { sourceStylesheet = templateFolder + project.PresentationStyle + ".xsl"; } xslStylesheet = workingFolder + Path.GetFileName(sourceStylesheet); tocInfo = BuildProcess.GetTocInfo(sourceStylesheet); // The style sheet may contain shared content items so we must resolve it this way rather // than using TransformTemplate. this.ResolveLinksAndCopy(sourceStylesheet, xslStylesheet, tocInfo); xslTransform = new XslCompiledTransform(); settings = new XsltSettings(true, true); xslArguments = new XsltArgumentList(); xslTransform.Load(XmlReader.Create(xslStylesheet, readerSettings), settings, new XmlUrlResolver()); } this.ReportProgress("Applying XSL transformation '{0}' to '{1}'.", xslStylesheet, sourceFile); reader = XmlReader.Create(sourceFile, readerSettings); writerSettings = xslTransform.OutputSettings.Clone(); writerSettings.CloseOutput = true; writerSettings.Indent = false; writer = XmlWriter.Create(destFile, writerSettings); xslArguments.Clear(); xslArguments.AddParam("pathToRoot", String.Empty, pathToRoot); xslTransform.Transform(reader, xslArguments, writer); } catch (Exception ex) { throw new BuilderException("BE0017", String.Format(CultureInfo.CurrentCulture, "Unexpected error using '{0}' to transform additional content file '{1}' to '{2}'. The " + "error is: {3}\r\n{4}", xslStylesheet, sourceFile, destFile, ex.Message, (ex.InnerException == null) ? String.Empty : ex.InnerException.Message)); } finally { if (reader != null) { reader.Close(); } if (writer != null) { writer.Flush(); writer.Close(); } } // The source topic file is deleted as the transformed file takes its place File.Delete(sourceFile); // <span> and <script> tags cannot be self-closing if empty. The template may contain them correctly // but when written out as XML, they get converted to self-closing tags which breaks them. To fix // them, convert them to full start and close tags. content = BuildProcess.ReadWithEncoding(destFile, ref enc); content = reSpanScript.Replace(content, "<$1$2></$1>"); // An XSL transform might have added tags and include items that need replacing so run it through // those options if needed. tocInfo = BuildProcess.GetTocInfo(destFile); // Expand <code> tags if necessary if (tocInfo.HasCodeBlocks) { content = reCodeBlock.Replace(content, codeBlockMatchEval); } // Colorize <pre> tags if necessary if (tocInfo.NeedsColorizing || tocInfo.HasCodeBlocks) { // Initialize code colorizer on first use if (codeColorizer == null) { codeColorizer = new CodeColorizer(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\highlight.xml", ComponentUtilities.ToolsFolder + @"PresentationStyles\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"); tocInfo.HasProjectTags = true; } // Use a regular expression to find and replace all tags with cref attributes with a link to the help // file content. This needs to happen after the code block processing as they may contain <see> tags // that need to be resolved. if (tocInfo.HasLinks || tocInfo.HasCodeBlocks) { content = reResolveLinks.Replace(content, linkMatchEval); } // Replace project option tags with project option values if (tocInfo.HasProjectTags) { // Project tags can be nested while (reProjectTags.IsMatch(content)) { 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 is called to copy the additional content files and build a list of them for the help file /// project. /// </summary> /// <remarks>Note that for wildcard content items, the folders are copied recursively.</remarks> private void CopyAdditionalContent() { Dictionary <string, TocEntryCollection> tocItems = new Dictionary <string, TocEntryCollection>(); TocEntryCollection parentToc; TocEntry tocEntry, tocFolder; FileItemCollection contentItems; string projectPath, source, filename, dirName; string[] parts; int part; this.ReportProgress(BuildStep.CopyAdditionalContent, "Copying additional content files..."); if (this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { return; } // A plug-in might add or remove additional content so call // them before checking to see if there is anything to copy. this.ExecutePlugIns(ExecutionBehaviors.Before); if (!project.HasItems(BuildAction.Content) && !project.HasItems(BuildAction.SiteMap)) { this.ReportProgress("No additional content to copy"); this.ExecutePlugIns(ExecutionBehaviors.After); return; } toc = new TocEntryCollection(); tocItems.Add(String.Empty, toc); // Now copy the content files contentItems = new FileItemCollection(project, BuildAction.Content); projectPath = FolderPath.TerminatePath(Path.GetDirectoryName(originalProjectName)); foreach (FileItem fileItem in contentItems) { source = fileItem.Include; dirName = Path.GetDirectoryName(fileItem.Link.ToString().Substring(projectPath.Length)); filename = Path.Combine(dirName, Path.GetFileName(source)); if (source.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) || source.EndsWith(".html", StringComparison.OrdinalIgnoreCase) || source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { tocEntry = BuildProcess.GetTocInfo(source); // Exclude the page if so indicated via the item metadata if (fileItem.ExcludeFromToc) { tocEntry.IncludePage = false; } // .topic files get transformed into .html files if (source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { filename = Path.ChangeExtension(filename, ".html"); } tocEntry.SourceFile = new FilePath(source, project); tocEntry.DestinationFile = filename; // Figure out where to add the entry parts = tocEntry.DestinationFile.Split('\\'); pathToRoot = String.Empty; parentToc = toc; for (part = 0; part < parts.Length - 1; part++) { pathToRoot += parts[part] + @"\"; // Create place holders if necessary if (!tocItems.TryGetValue(pathToRoot, out parentToc)) { tocFolder = new TocEntry(project); tocFolder.Title = parts[part]; if (part == 0) { toc.Add(tocFolder); } else { tocItems[String.Join(@"\", parts, 0, part) + @"\"].Add(tocFolder); } parentToc = tocFolder.Children; tocItems.Add(pathToRoot, parentToc); } } parentToc.Add(tocEntry); if (tocEntry.IncludePage && tocEntry.IsDefaultTopic) { defaultTopic = tocEntry.DestinationFile; } } else { tocEntry = null; } this.EnsureOutputFoldersExist(dirName); foreach (string baseFolder in this.HelpFormatOutputFolders) { // If the file contains items that need to be resolved, // it is handled separately. if (tocEntry != null && (tocEntry.HasLinks || tocEntry.HasCodeBlocks || tocEntry.NeedsColorizing || tocEntry.HasProjectTags || source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))) { // Figure out the path to the root if needed parts = tocEntry.DestinationFile.Split('\\'); pathToRoot = String.Empty; for (part = 0; part < parts.Length - 1; part++) { pathToRoot += "../"; } this.ResolveLinksAndCopy(source, baseFolder + filename, tocEntry); } else { this.ReportProgress("{0} -> {1}{2}", source, baseFolder, filename); // All attributes are turned off so that we can delete it later File.Copy(source, baseFolder + filename, true); File.SetAttributes(baseFolder + filename, FileAttributes.Normal); } } } // Remove excluded nodes, merge folder item info into the root // nodes, and sort the items. If a site map isn't defined, this // will define the layout of the items. toc.RemoveExcludedNodes(null); toc.Sort(); codeColorizer = null; sharedContent = null; this.ExecutePlugIns(ExecutionBehaviors.After); }