예제 #1
0
        /// <summary>
        /// Renders the code
        /// </summary>
        /// <remarks>You must specify a colorizing engine setting the <see cref="SyntaxEngine" /> property
        /// before calling this method.</remarks>
        /// <param name="writer">HTML writer</param>
        protected override void Render(HtmlTextWriter writer)
        {
            if (!String.IsNullOrEmpty(text))
            {
                if (colorizer == null)
                {
                    writer.Write("<strong>Colorizer not set!</strong><br/>");
                    writer.Write(text);
                }
                else
                {
                    writer.Write(colorizer.ProcessAndHighlightText(text));
#if DEBUG && BENCHMARK
                    System.Diagnostics.Debug.WriteLine("Performance: " + colorizer.BenchmarkSec + " s, " +
                                                       colorizer.BenchmarkSecPerChar + " s/char, " + colorizer.BenchmarkAvgSec + " s");
#endif
                }
            }
        }
        /// <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);
            }
        }
예제 #3
0
        /// <summary>
        /// This is implemented to perform the code colorization.
        /// </summary>
        /// <param name="document">The XML document with which to work.</param>
        /// <param name="key">The key (member name) of the item being
        /// documented.</param>
        public override void Apply(XmlDocument document, string key)
        {
            XPathNavigator root, navDoc = document.CreateNavigator();

            XPathNavigator[] codeList;
            XPathExpression  nestedCode;
            XmlAttribute     attr;
            XmlNode          code, titleDiv, preNode, container, refLink;

            string language, title, codeBlock;
            bool   nbrLines, outline, seeTags, filter;
            int    tabSize, start, end, blockId = 1, id = 1;

            // Clear the dictionary
            colorizedCodeBlocks.Clear();

            // Select all code nodes.  The location depends on the build type.
            root = navDoc.SelectSingleNode(referenceRoot);

            // If not null, it's a reference (API) build.  If null, it's
            // a conceptual build.
            if (root != null)
            {
                codeList = BuildComponentUtilities.ConvertNodeIteratorToArray(
                    root.Select(referenceCode));
                nestedCode = nestedRefCode;
            }
            else
            {
                root       = navDoc.SelectSingleNode(conceptualRoot);
                nestedCode = nestedConceptCode;

                if (root == null)
                {
                    base.WriteMessage(MessageLevel.Warn,
                                      "Root content node not found.  Cannot colorize code.");
                    return;
                }

                codeList = BuildComponentUtilities.ConvertNodeIteratorToArray(
                    root.Select(conceptualCode));
            }

            foreach (XPathNavigator navCode in codeList)
            {
                code = ((IHasXmlNode)navCode).GetNode();

                // If the parent is null, it was a nested node and has already
                // been handled.
                if (code.ParentNode == null)
                {
                    continue;
                }

                // Set the defaults
                language = defaultLanguage;
                nbrLines = numberLines;
                outline  = outliningEnabled;
                seeTags  = keepSeeTags;
                filter   = languageFilter;
                tabSize  = 0;
                title    = String.Empty;

                // If there are nested code blocks, load them.  Source and
                // region attributes will be ignored on the parent.  All
                // other attributes will be applied to the combined block of
                // code.  If there are no nested blocks, source and region
                // will be used to load the code if found.  Otherwise, the
                // existing inner XML is used for the code.
                if (navCode.SelectSingleNode(nestedCode) != null)
                {
                    codeBlock = this.LoadNestedCodeBlocks(navCode, nestedCode);
                }
                else
                if (code.Attributes["source"] != null)
                {
                    codeBlock = this.LoadCodeBlock(code);
                }
                else
                {
                    codeBlock = code.InnerXml;
                }

                // Check for option overrides
                if (code.Attributes["numberLines"] != null)
                {
                    nbrLines = Convert.ToBoolean(code.Attributes[
                                                     "numberLines"].Value, CultureInfo.InvariantCulture);
                }

                if (code.Attributes["outlining"] != null)
                {
                    outline = Convert.ToBoolean(code.Attributes[
                                                    "outlining"].Value, CultureInfo.InvariantCulture);
                }

                if (code.Attributes["keepSeeTags"] != null)
                {
                    seeTags = Convert.ToBoolean(code.Attributes[
                                                    "keepSeeTags"].Value, CultureInfo.InvariantCulture);
                }

                if (code.Attributes["filter"] != null)
                {
                    filter = Convert.ToBoolean(code.Attributes[
                                                   "filter"].Value, CultureInfo.InvariantCulture);
                }

                if (code.Attributes["tabSize"] != null)
                {
                    tabSize = Convert.ToInt32(code.Attributes["tabSize"].Value,
                                              CultureInfo.InvariantCulture);
                }

                // If either language option is set to "none" or an unknown
                // language, it just strips excess leading whitespace and
                // optionally numbers the lines and adds outlining based on
                // the other settings.
                if (code.Attributes["lang"] != null)
                {
                    language = code.Attributes["lang"].Value;
                }
                else
                if (code.Attributes["language"] != null)
                {
                    language = code.Attributes["language"].Value;
                }

                // Use the title if one is supplied.
                if (code.Attributes["title"] != null)
                {
                    title = HttpUtility.HtmlEncode(code.Attributes["title"].Value);
                }

                // Process the code.  The colorizer is built to highlight <pre> tags in an HTML file so we'll
                // wrap the code in a <pre> tag with the settings.
                codeBlock = colorizer.ProcessAndHighlightText(String.Format(CultureInfo.InvariantCulture,
                                                                            "<pre lang=\"{0}\" numberLines=\"{1}\" outlining=\"{2}\" " +
                                                                            "keepSeeTags=\"{3}\" tabSize=\"{4}\" {5}>{6}</pre>",
                                                                            language, nbrLines, outline, seeTags, tabSize,
                                                                            (title.Length != 0) ? "title=\"" + title + "\"" : String.Empty, codeBlock));

                // Non-breaking spaces are replaced with a space entity.  If not, they disappear in the rendered
                // HTML.  Seems to be an XML or XSLT thing.
                codeBlock = codeBlock.Replace("&nbsp;", "&#x20;");

                // Move the title above the code block so that it doesn't interfere with the stuff generated
                // by the transformation component.  The post-transform component will perform some clean-up
                // to get rid of similar stuff added by it.
                title     = codeBlock.Substring(0, codeBlock.IndexOf("</div>", StringComparison.Ordinal) + 6);
                codeBlock = codeBlock.Substring(title.Length);

                titleDiv          = document.CreateDocumentFragment();
                titleDiv.InnerXml = title;
                titleDiv          = titleDiv.ChildNodes[0];

                // Remove the colorizer's <pre> tag.  We'll add our own below.
                start = codeBlock.IndexOf('>') + 1;
                end   = codeBlock.LastIndexOf('<');

                // We need to add the xml:space="preserve" attribute on the <pre> element so that MS Help Viewer
                // doesn't remove significant whitespace between colorized elements.
                preNode    = document.CreateNode(XmlNodeType.Element, "pre", null);
                attr       = document.CreateAttribute("xml:space");
                attr.Value = "preserve";
                preNode.Attributes.Append(attr);
                preNode.InnerXml = codeBlock.Substring(start, end - start);

                // Convert <see> tags to <referenceLink> or <a> tags.  We need to do this so that the Resolve
                // Links component can do its job further down the line.  The code blocks are not present when
                // the transformations run so that the colorized HTML doesn't get stripped out in conceptual
                // builds.  This could be redone to use a <markup> element if the Sandcastle transformations
                // ever support it natively.
                foreach (XmlNode seeTag in preNode.SelectNodes("//see"))
                {
                    if (seeTag.Attributes["cref"] != null)
                    {
                        refLink = document.CreateElement("referenceLink");
                    }
                    else
                    {
                        refLink = document.CreateElement("a");
                    }

                    foreach (XmlAttribute seeAttr in seeTag.Attributes)
                    {
                        if (seeAttr.Name == "cref")
                        {
                            attr = document.CreateAttribute("target");
                        }
                        else
                        {
                            attr = (XmlAttribute)seeAttr.Clone();
                        }

                        attr.Value = seeAttr.Value;
                        refLink.Attributes.Append(attr);
                    }

                    if (seeTag.HasChildNodes)
                    {
                        refLink.InnerXml = seeTag.InnerXml;
                    }

                    seeTag.ParentNode.ReplaceChild(refLink, seeTag);
                }

                // The <span> tags cannot be self-closing if empty.  The colorizer renders them correctly but
                // when written out as XML, they get converted to self-closing tags which breaks them.  To fix
                // them, store an empty string in each empty span so that it renders as an opening and closing
                // tag.  Note that if null, InnerText returns an empty string by default.  As such, this looks
                // redundant but it really isn't (see note above).
                foreach (XmlNode span in preNode.SelectNodes("//span"))
                {
                    if (span.InnerText.Length == 0)
                    {
                        span.InnerText = String.Empty;
                    }
                }

                // Add language filter stuff if needed or just the title if there is one and we aren't
                // using the language filter.
                if (filter)
                {
                    // If necessary, map the language ID to one we will recognize
                    language = language.ToLower(CultureInfo.InvariantCulture);

                    if (colorizer.AlternateIds.ContainsKey(language))
                    {
                        language = colorizer.AlternateIds[language];
                    }

                    container = this.AddLanguageFilter(titleDiv, preNode, language, blockId++);
                }
                else
                {
                    container = document.CreateNode(XmlNodeType.Element, "span", null);
                    container.AppendChild(titleDiv);
                    container.AppendChild(preNode);
                }

                // Replace the code with a placeholder ID.  The post-transform
                // component will relace it with the code from the container.
                code.InnerXml = "@@_SHFB_" + id.ToString(CultureInfo.InvariantCulture);

                // Add the container to the code block dictionary
                colorizedCodeBlocks.Add(code.InnerXml, container);
                id++;
            }
        }
        /// <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>
        internal void ResolveLinksAndCopy(string sourceFile, string destFile, TocEntry entry)
        {
            Encoding enc = Encoding.Default;
            string   content, script, syntaxFile;
            int      pos;

            // For topics, change the extension back to ".topic".  It's ".html" in the TOC as that's what it ends
            // up as after transformation.
            if (sourceFile.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
            {
                destFile = Path.ChangeExtension(destFile, ".topic");
            }

            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);

            // 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(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");
                entry.HasProjectTags = true;

                // Add the links to the colorizer style sheet and script files unless it's going to be
                // transformed.  In which case, the links should be in the XSL style sheet.
                if (!sourceFile.EndsWith(".topic", StringComparison.OrdinalIgnoreCase) &&
                    !sourceFile.EndsWith(".xsl", StringComparison.OrdinalIgnoreCase))
                {
                    script = String.Format(CultureInfo.InvariantCulture,
                                           "<link type='text/css' rel='stylesheet' href='{0}styles/highlight.css' />" +
                                           "<script type='text/javascript' src='{0}scripts/highlight_ac.js'></script>", pathToRoot);

                    pos = content.IndexOf("</head>", StringComparison.Ordinal);

                    // Create a <head> section if one doesn't exist
                    if (pos == -1)
                    {
                        script = "<head>" + script + "</head>";
                        pos    = content.IndexOf("<html>", StringComparison.Ordinal);

                        if (pos != -1)
                        {
                            pos += 6;
                        }
                        else
                        {
                            pos = 0;
                        }
                    }

                    content = content.Insert(pos, script);
                }

                // Copy the colorizer files if not already there
                this.EnsureOutputFoldersExist("icons");
                this.EnsureOutputFoldersExist("styles");
                this.EnsureOutputFoldersExist("scripts");

                foreach (string baseFolder in this.HelpFormatOutputFolders)
                {
                    if (!File.Exists(baseFolder + @"styles\highlight.css"))
                    {
                        syntaxFile = baseFolder + @"styles\highlight.css";
                        File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\highlight.css",
                                  syntaxFile);
                        File.SetAttributes(syntaxFile, FileAttributes.Normal);

                        syntaxFile = baseFolder + @"scripts\highlight_ac.js";
                        File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\highlight_ac.js",
                                  syntaxFile);
                        File.SetAttributes(syntaxFile, FileAttributes.Normal);

                        // Always copy the image files, they may be different.  Also, delete the destination file
                        // first if it exists as the filename casing may be different.
                        syntaxFile = baseFolder + @"icons\CopyCode.gif";

                        if (File.Exists(syntaxFile))
                        {
                            File.SetAttributes(syntaxFile, FileAttributes.Normal);
                            File.Delete(syntaxFile);
                        }

                        File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\CopyCode.gif",
                                  syntaxFile);
                        File.SetAttributes(syntaxFile, FileAttributes.Normal);

                        syntaxFile = baseFolder + @"icons\CopyCode_h.gif";

                        if (File.Exists(syntaxFile))
                        {
                            File.SetAttributes(syntaxFile, FileAttributes.Normal);
                            File.Delete(syntaxFile);
                        }

                        File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\CopyCode_h.gif",
                                  syntaxFile);
                        File.SetAttributes(syntaxFile, FileAttributes.Normal);
                    }
                }
            }

            // 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 (entry.HasLinks || entry.HasCodeBlocks)
            {
                content = reResolveLinks.Replace(content, linkMatchEval);
            }

            // Replace project option tags with project option values
            if (entry.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);
            }

            // Transform .topic files into .html files
            if (sourceFile.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
            {
                this.XslTransform(destFile);
            }
        }
예제 #5
0
        /// <summary>
        /// This is implemented to perform the code colorization.
        /// </summary>
        /// <param name="document">The XML document with which to work.</param>
        /// <param name="key">The key (member name) of the item being
        /// documented.</param>
        public override void Apply(XmlDocument document, string key)
        {
            XmlNodeList codeList;
            XmlNode     titleDiv;

            string language, title, codeBlock;
            bool   nbrLines, outline;
            int    tabSize, start, end;

            // Reset the flag
            needsHighlightFiles = false;

            // Select all code nodes
            codeList = document.SelectNodes("//code");

            foreach (XmlNode code in codeList)
            {
                // Set the defaults
                language = defaultLanguage;
                nbrLines = numberLines;
                outline  = outliningEnabled;
                tabSize  = defaultTabSize;
                title    = String.Empty;

                // If there's a source attribute, load the code from the file
                if (code.Attributes["source"] != null)
                {
                    codeBlock = this.LoadCodeBlock(code);
                }
                else
                {
                    codeBlock = code.InnerXml;
                }

                // Check for option overrides
                if (code.Attributes["numberLines"] != null)
                {
                    nbrLines = Convert.ToBoolean(
                        code.Attributes["numberLines"].Value,
                        CultureInfo.InvariantCulture);
                }

                if (code.Attributes["outlining"] != null)
                {
                    outline = Convert.ToBoolean(
                        code.Attributes["outlining"].Value,
                        CultureInfo.InvariantCulture);
                }

                if (code.Attributes["tabSize"] != null)
                {
                    tabSize = Convert.ToInt32(
                        code.Attributes["numberLines"].Value,
                        CultureInfo.InvariantCulture);
                }

                // If either language option is set to "none" or an unknown
                // language, it just strips excess leading whitespace and
                // optionally numbers the lines and adds outlining based on
                // the other settings.
                if (code.Attributes["lang"] != null)
                {
                    language = code.Attributes["lang"].Value;
                }

                // Use the title if one is supplied.
                if (code.Attributes["title"] != null)
                {
                    title = HttpUtility.HtmlEncode(
                        code.Attributes["title"].Value);
                }

                // Process the code.  The colorizer is built to highlight
                // <pre> tags in an HTML file so we'll wrap the code in a
                // <pre> tag with the settings.
                codeBlock = colorizer.ProcessAndHighlightText(
                    String.Format(CultureInfo.InvariantCulture,
                                  "<pre lang=\"{0}\" numberLines=\"{1}\" " +
                                  "outlining=\"{2}\" tabSize=\"{3}\" {4}>{5}</pre>",
                                  language, nbrLines, outline, tabSize,
                                  (title.Length != 0) ? "title=\"" + title + "\"" : String.Empty,
                                  codeBlock));

                // Non-breaking spaces are replaced with a space entity.  If
                // not, they disappear in the rendered HTML.  Seems to be an
                // XML or XSLT thing.
                codeBlock = codeBlock.Replace("&nbsp;", "&#x20;");

                // Move the title above the code block so that it doesn't
                // interfere with the stuff generated by the transformation
                // component.  The post-transform component will perform some
                // clean-up to get rid of similar stuff added by it.
                if (codeBlock.StartsWith("<div"))
                {
                    title = codeBlock.Substring(0, codeBlock.IndexOf(
                                                    "</div>") + 6);
                    codeBlock = codeBlock.Substring(title.Length);

                    titleDiv          = document.CreateDocumentFragment();
                    titleDiv.InnerXml = title;
                    titleDiv          = titleDiv.ChildNodes[0];
                }
                else
                {
                    titleDiv = null;
                }

                // Remove the colorizer's <pre> tag and put it back into
                // the document.  The Sandcastle transform puts the code in
                // <div> and <pre> tags itself.
                start = codeBlock.IndexOf(">") + 1;
                end   = codeBlock.LastIndexOf("<");

                code.InnerXml = codeBlock.Substring(start, end - start);

                // The <span> tags cannot be self-closing if empty.  The
                // colorizer renders them correctly but when written out as
                // XML, they get converted to self-closing tags which breaks
                // them.  To fix them, store an empty string in each empty
                // span so that it renders as an opening and closing tag.
                XmlNodeList spans = code.SelectNodes("//span");

                // Note that if null, InnerText returns an empty string
                // by default.  As such, this looks redundant but it
                // really isn't (see note above).
                foreach (XmlNode span in spans)
                {
                    if (span.InnerText.Length == 0)
                    {
                        span.InnerText = String.Empty;
                    }
                }

                // Add language filter stuff if needed or just the title if
                // there is one and we aren't using the language filter.
                if (languageFilter)
                {
                    CodeBlockComponent.AddLanguageFilter(titleDiv, code, language);
                }
                else
                if (titleDiv != null)
                {
                    code.ParentNode.InsertBefore(titleDiv, code);
                }

                // Set the flag if needed to have the PostTransformComponent
                // include the stylesheet and script files.
                if (!needsHighlightFiles && (outline || language != "none" ||
                                             titleDiv != null))
                {
                    needsHighlightFiles = true;
                }
            }
        }