private static string CleanseSDKTypeDocumentation(string documentation)
        {
            bool isFirst  = true;
            var  modified = DocumentationUtils.ProcessLines(documentation, l =>
            {
                string t = l;
                if (t.StartsWith(SDKLeadingSpaces, StringComparison.Ordinal))
                {
                    t = t.Substring(SDKLeadingSpaces.Length);
                }

                if (isFirst)
                {
                    isFirst = false;
                    if (t.StartsWith(SDKTypeFirstLineStart, StringComparison.Ordinal) &&
                        t.EndsWith(SDKTypeFirstLineEnd, StringComparison.Ordinal))
                    {
                        return(null);
                    }
                }
                return(t);
            });

            string xml = RemoveOuterParaTag(modified);

            return(xml);
        }
        protected override void GenerateHelper()
        {
            base.GenerateHelper();

            if (string.IsNullOrEmpty(SDKHelpRoot))
            {
                SDKHelpRoot = "http://docs.aws.amazon.com/sdkfornet/v3/apidocs/";
            }
            else if (!SDKHelpRoot.EndsWith("/"))
            {
                SDKHelpRoot = SDKHelpRoot + "/";
            }

            Console.WriteLine("Generating web help documentation:");
            Console.WriteLine(".... SDK help base URI set to {0}", SDKHelpRoot);
            Console.WriteLine(".... writing doc output to {0}", OutputFolder);

            var buildLogsPath = Path.Combine(this.Options.RootPath, "logs");

            if (!Directory.Exists(buildLogsPath))
            {
                Directory.CreateDirectory(buildLogsPath);
            }

            var logFile   = Path.Combine(buildLogsPath, Name + ".dll-WebHelp.log");
            var oldWriter = Console.Out;

            try
            {
                using (var consoleWriter = new StreamWriter(File.OpenWrite(logFile)))
                {
                    Console.SetOut(consoleWriter);

                    CleanWebHelpOutputFolder(OutputFolder);
                    CopyWebHelpStaticFiles(OutputFolder);
                    CreateVersionInfoFile(Path.Combine(OutputFolder, "items"));

                    var tocWriter = new TOCWriter(Options, OutputFolder);
                    tocWriter.AddFixedSection();

                    Parallel.ForEach(CmdletTypes, (cmdletType) =>
                    {
                        var(moduleName, serviceName) = DetermineCmdletServiceOwner(cmdletType);
                        var cmdletInfo = InspectCmdletAttributes(cmdletType);

                        string synopsis = null;
                        if (cmdletInfo.AWSCmdletAttribute == null)
                        {
                            Console.WriteLine("Unable to find AWSCmdletAttribute for type " + cmdletType.FullName);
                        }
                        else
                        {
                            var cmdletReturnAttributeType = cmdletInfo.AWSCmdletAttribute.GetType();
                            synopsis =
                                cmdletReturnAttributeType.GetProperty("Synopsis").GetValue(cmdletInfo.AWSCmdletAttribute, null) as
                                string;
                        }

                        foreach (var cmdletAttribute in cmdletInfo.CmdletAttributes)
                        {
                            var typeDocumentation = DocumentationUtils.GetTypeDocumentation(cmdletType,
                                                                                            AssemblyDocumentation);
                            typeDocumentation = DocumentationUtils.FormatXMLForPowershell(typeDocumentation, true);
                            Console.WriteLine($"Cmdlet = {cmdletType.FullName}");
                            Console.WriteLine($"Documentation = {typeDocumentation}");

                            var cmdletName = cmdletAttribute.VerbName + "-" + cmdletAttribute.NounName;

                            var allProperties = GetRootSimpleProperties(cmdletType);

                            var parameterPartitioning = new CmdletParameterSetPartitions(allProperties, cmdletAttribute.DefaultParameterSetName);

                            var serviceAbbreviation = GetServiceAbbreviation(cmdletType);

                            var cmdletPageWriter = new CmdletPageWriter(Options, OutputFolder, serviceName, moduleName, cmdletName);

                            WriteDetails(cmdletPageWriter, typeDocumentation, synopsis, cmdletInfo.AWSCmdletAttribute);
                            WriteSyntax(cmdletPageWriter, cmdletName, parameterPartitioning);
                            WriteParameters(cmdletPageWriter, cmdletName, allProperties, false);
                            WriteParameters(cmdletPageWriter, cmdletName, allProperties, true);
                            WriteOutputs(cmdletPageWriter, cmdletInfo.AWSCmdletOutputAttributes);
                            WriteNotes(cmdletPageWriter);
                            WriteExamples(cmdletPageWriter, cmdletName);
                            WriteRelatedLinks(cmdletPageWriter, serviceAbbreviation, cmdletName);

                            cmdletPageWriter.Write();

                            lock (tocWriter)
                            {
                                var legacyAlias = InspectForLegacyAliasAttribution(moduleName, cmdletName, cmdletInfo.AWSCmdletAttribute);
                                tocWriter.AddServiceCmdlet(moduleName, serviceName, cmdletName, cmdletPageWriter.GetTOCID(), synopsis, legacyAlias);
                            }
                        }
                    });

                    tocWriter.Write();

                    WriteLegacyAliasesPage();
                }
            }
            finally
            {
                Console.SetOut(oldWriter);
            }
        }
        public static string FormatXMLForPowershell(string xml, bool forWebUse = false)
        {
            var sb = new StringBuilder();

            using (var reader = new XmlTextReader(xml, XmlNodeType.Element, null))
            {
                while (reader.Read())
                {
                    var type  = reader.NodeType;
                    var name  = reader.Name.ToLowerInvariant();
                    var value = reader.Value;

                    if (type == XmlNodeType.Element)
                    {
                        switch (name)
                        {
                        case "ul":
                        case "ol":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                            }
                            else
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "li":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                                sb.Append(" -");
                            }
                            else
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        // very temp hack for ElastiCache in 2.3.5.0 release -- this is the only known set of table elements,
                        // we want them in web help but not maml docs
                        case "table":
                            if (!forWebUse)
                            {
                                sb.Append(newAvailabilityZonesTableReplacement);
                                do
                                {
                                    reader.Read();
                                } while (!reader.Name.Equals("table", StringComparison.OrdinalIgnoreCase));
                            }
                            else
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "th":
                        case "tr":
                        case "td":
                        case "div":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "dl":
                        case "dt":
                        case "dd":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "strong":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "pre":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "link":
                        case "filename":
                        case "replaceable":
                        case "seealso":
                            break;

                        default:
                            if (!HandleElement(sb, reader, name, forWebUse))
                            {
                                throw new InvalidOperationException("Unsupported node of type " + name + ". Full XML: " + xml);
                            }
                            break;
                        }
                    }
                    else if (type == XmlNodeType.EndElement)
                    {
                        switch (name)
                        {
                        case "li":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                            }
                            else
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "ul":
                        case "ol":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                                sb.AppendLine();
                            }
                            else
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        // very temp hack for ElastiCache in 2.3.5.0 release -- this is the only known set of table elements,
                        // we want them in web help but not maml docs
                        case "table":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "th":
                        case "tr":
                        case "td":
                        case "div":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "dl":
                        case "dt":
                        case "dd":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "strong":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "pre":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "link":
                        case "filename":
                        case "replaceable":
                        case "seealso":
                            break;

                        default:
                            if (!HandleEndElement(sb, name, forWebUse))
                            {
                                throw new InvalidOperationException("Unsupported node of type " + name +
                                                                    ". Full XML: " + xml);
                            }
                            break;
                        }
                    }
                    else if (type == XmlNodeType.Text)
                    {
                        sb.Append(System.Net.WebUtility.HtmlEncode(value));
                    }
                }

                string composed = sb.ToString();
                string final    = DocumentationUtils.ProcessLines(composed, l => l, compressConsequitiveNonemptyLines: true, compressConsequitiveEmptyLines: true, skipEmptyLines: true);
                return(final);
            }
        }
Example #4
0
        private void WriteExamples(XmlWriter writer, string cmdletName)
        {
            XmlDocument document;

            if (!ExamplesCache.TryGetValue(cmdletName, out document))
            {
                Console.WriteLine("NO EXAMPLES - {0}", cmdletName);
                return;
            }

            var set = document.SelectSingleNode("examples");

            if (set == null || !set.HasChildNodes)
            {
                Console.WriteLine("NO EXAMPLES - {0} (file present but empty)", cmdletName);
                return;
            }

            writer.WriteStartElement("examples");
            {
                int exampleIndex = 1;
                var examples     = set.SelectNodes("example");
                foreach (XmlNode example in examples)
                {
                    writer.WriteStartElement("example");
                    {
                        writer.WriteElementString("title", string.Format(exampleTitleFormat, exampleIndex));
                        {
                            // code tag CANNOT HAVE PARA TAGS
                            var code = example.SelectSingleNode("code");
                            if (code == null)
                            {
                                Logger.LogError("Unable to find examples <code> tag for cmdlet " + cmdletName);
                            }

                            writer.WriteRawElementString("code", code.InnerXml);

                            // remarks tag MUST HAVE PARA TAGS
                            var description = example.SelectSingleNode("description");
                            if (description == null)
                            {
                                Logger.LogError("Unable to find examples <description> tag for cmdlet " + cmdletName);
                            }
                            var correctedText = DocumentationUtils.ProcessLines(description.InnerXml, s => string.Format("<para>{0}</para>", s));
                            // we use br/ tags to format the description for webhelp, for native help we can replace
                            // with simple newlines
                            correctedText = correctedText.Replace("<br />", "\n"); // xml handler shows us <br/> as <br />
                            // <url>link</url> elements are stripped to leave the inner link text for the user to copy/paste
                            // (web help converts these to <a href="link" />)
                            correctedText = correctedText.Replace("<url>", "");
                            correctedText = correctedText.Replace("</url>", "");
                            var remarks = string.Format(remarksFormat, correctedText);
                            writer.WriteUnescapedElementString("remarks", remarks);
                        }
                    }
                    writer.WriteEndElement();
                    exampleIndex++;
                }
            }
            writer.WriteEndElement();
        }
Example #5
0
        protected override void GenerateHelper()
        {
            Console.WriteLine("Generating Native PowerShell help (Get-Help) documentation file");
            base.GenerateHelper();

            var buildLogsPath = Path.Combine(this.Options.RootPath, "logs");

            if (!Directory.Exists(buildLogsPath))
            {
                Directory.CreateDirectory(buildLogsPath);
            }

            var logFilename = Path.Combine(buildLogsPath, Name + ".dll-Help.log");
            var oldWriter   = Console.Out;

            try
            {
                using (var consoleWriter = new StreamWriter(File.OpenWrite(logFilename)))
                {
                    Console.SetOut(consoleWriter);
                    var outputFile = Path.Combine(this.OutputFolder, Name + PsHelpFilenameTail);

                    var settings = new XmlWriterSettings
                    {
                        Indent = true
                    };

                    using (var psHelpWriter = new XmlTextWriter(outputFile, Encoding.UTF8))
                    {
                        psHelpWriter.Formatting = Formatting.Indented;

                        psHelpWriter.WriteStartElement("helpItems");
                        psHelpWriter.WriteAttributeString("schema", "maml");

                        Parallel.ForEach(CmdletTypes, (cmdletType) =>
                        {
                            CmdletInfo cmdletInfo = InspectCmdletAttributes(cmdletType);

                            using (StringWriter sectionWriter = new StringWriter())
                                using (var sectionXmlWriter = XmlWriter.Create(sectionWriter, new XmlWriterSettings()
                                {
                                    OmitXmlDeclaration = true,
                                    ConformanceLevel = ConformanceLevel.Fragment,
                                    CloseOutput = true
                                }))
                                {
                                    string synopsis = null;
                                    if (cmdletInfo.AWSCmdletAttribute == null)
                                    {
                                        Console.WriteLine("Unable to find AWSCmdletAttribute for type " + cmdletType.FullName);
                                    }
                                    else
                                    {
                                        var cmdletReturnAttributeType = cmdletInfo.AWSCmdletAttribute.GetType();
                                        synopsis = cmdletReturnAttributeType.GetProperty("Synopsis").GetValue(cmdletInfo.AWSCmdletAttribute, null) as string;
                                    }

                                    foreach (var cmdletAttribute in cmdletInfo.CmdletAttributes)
                                    {
                                        sectionXmlWriter.WriteStartElement("command");
                                        sectionXmlWriter.WriteAttributeString("xmlns", "maml", null, "http://schemas.microsoft.com/maml/2004/10");
                                        sectionXmlWriter.WriteAttributeString("xmlns", "command", null, "http://schemas.microsoft.com/maml/dev/command/2004/10");
                                        sectionXmlWriter.WriteAttributeString("xmlns", "dev", null, "http://schemas.microsoft.com/maml/dev/2004/10");
                                        {
                                            var typeDocumentation = DocumentationUtils.GetTypeDocumentation(cmdletType, AssemblyDocumentation);
                                            typeDocumentation     = DocumentationUtils.FormatXMLForPowershell(typeDocumentation);
                                            Console.WriteLine($"Cmdlet = {cmdletType.FullName}");
                                            Console.WriteLine($"Documentation = {typeDocumentation}");

                                            var cmdletName = cmdletAttribute.VerbName + "-" + cmdletAttribute.NounName;

                                            var allProperties         = GetRootSimpleProperties(cmdletType);
                                            var parameterPartitioning = new CmdletParameterSetPartitions(allProperties, cmdletAttribute.DefaultParameterSetName);

                                            var serviceAbbreviation = GetServiceAbbreviation(cmdletType);

                                            WriteDetails(sectionXmlWriter, cmdletAttribute, typeDocumentation, cmdletName, synopsis);
                                            WriteSyntax(sectionXmlWriter, cmdletName, parameterPartitioning);
                                            WriteParameters(sectionXmlWriter, cmdletName, allProperties);
                                            WriteReturnValues(sectionXmlWriter, cmdletInfo.AWSCmdletOutputAttributes);
                                            WriteRelatedLinks(sectionXmlWriter, serviceAbbreviation, cmdletName);
                                            WriteExamples(sectionXmlWriter, cmdletName);
                                        }
                                        sectionXmlWriter.WriteEndElement();
                                    }

                                    sectionXmlWriter.Close();
                                    lock (psHelpWriter)
                                    {
                                        psHelpWriter.WriteRaw(sectionWriter.ToString());
                                    }
                                }
                        });

                        psHelpWriter.WriteEndElement();
                    }

                    Console.WriteLine($"Verifying help file {outputFile} using XmlDocument...");
                    try
                    {
                        var document = new XmlDocument();
                        document.Load(outputFile);
                    }
                    catch (Exception e)
                    {
                        Logger.LogError(e, $"Error when loading file {outputFile} as XmlDocument");
                    }
                }
            }
            finally
            {
                Console.SetOut(oldWriter);
            }
        }
        protected override void GenerateHelper()
        {
            Console.WriteLine("Generating Native PowerShell help (Get-Help) documentation file");
            base.GenerateHelper();

            var buildLogsPath = Path.Combine(this.Options.RootPath, "logs");

            if (!Directory.Exists(buildLogsPath))
            {
                Directory.CreateDirectory(buildLogsPath);
            }

            var logFilename = Path.Combine(buildLogsPath, Name + ".dll-Help.log");
            var oldWriter   = Console.Out;

            try
            {
                using (var consoleWriter = new StreamWriter(File.OpenWrite(logFilename)))
                {
                    Console.SetOut(consoleWriter);
                    var outputFile = Path.Combine(this.OutputFolder, Name + PsHelpFilenameTail);

                    var settings = new XmlWriterSettings
                    {
                        Indent = true
                    };

                    using (var psHelpWriter = new XmlTextWriter(outputFile, Encoding.UTF8))
                    {
                        psHelpWriter.Formatting = Formatting.Indented;

                        psHelpWriter.WriteStartElement("helpItems");
                        psHelpWriter.WriteAttributeString("schema", "maml");

                        foreach (var cmdletType in CmdletTypes)
                        {
                            InspectCmdletAttributes(cmdletType);

                            string synopsis = null;
                            if (AWSCmdletAttribute == null)
                            {
                                Console.WriteLine("Unable to find AWSCmdletAttribute for type " + cmdletType.FullName);
                            }
                            else
                            {
                                var cmdletReturnAttributeType = AWSCmdletAttribute.GetType();
                                synopsis = cmdletReturnAttributeType.GetProperty("Synopsis").GetValue(AWSCmdletAttribute, null) as string;
                            }

                            foreach (var cmdletAttribute in CmdletAttributes)
                            {
                                psHelpWriter.WriteStartElement("command");
                                psHelpWriter.WriteAttributeString("xmlns", "maml", null, "http://schemas.microsoft.com/maml/2004/10");
                                psHelpWriter.WriteAttributeString("xmlns", "command", null, "http://schemas.microsoft.com/maml/dev/command/2004/10");
                                psHelpWriter.WriteAttributeString("xmlns", "dev", null, "http://schemas.microsoft.com/maml/dev/2004/10");
                                {
                                    var hasDynamicParams = DynamicParamsType.IsAssignableFrom(cmdletType);

                                    var typeDocumentation = DocumentationUtils.GetTypeDocumentation(cmdletType, AssemblyDocumentation);
                                    typeDocumentation = DocumentationUtils.FormatXMLForPowershell(typeDocumentation);
                                    Console.WriteLine("Cmdlet = {0}", cmdletType.FullName);
                                    if (hasDynamicParams)
                                    {
                                        Console.WriteLine("This cmdlet has dynamic parameters!");
                                    }
                                    Console.WriteLine("Documentation = {0}", typeDocumentation);

                                    var cmdletName = cmdletAttribute.VerbName + "-" + cmdletAttribute.NounName;

                                    var allProperties         = GetRootSimpleProperties(cmdletType);
                                    var parameterPartitioning = new CmdletParameterSetPartitions(allProperties, cmdletAttribute.DefaultParameterSetName);

                                    var serviceAbbreviation = GetServiceAbbreviation(cmdletType);

                                    WriteDetails(psHelpWriter, cmdletAttribute, typeDocumentation, cmdletName, synopsis);
                                    WriteSyntax(psHelpWriter, cmdletName, parameterPartitioning);
                                    WriteParameters(psHelpWriter, cmdletName, allProperties);
                                    WriteReturnValues(psHelpWriter, AWSCmdletOutputAttributes);
                                    WriteRelatedLinks(psHelpWriter, serviceAbbreviation, cmdletName);
                                    WriteExamples(psHelpWriter, cmdletName);
                                }
                                psHelpWriter.WriteEndElement();
                            }
                        }

                        psHelpWriter.WriteEndElement();
                    }

                    Console.WriteLine("Verifying help file {0} using XmlDocument...", outputFile);
                    try
                    {
                        var document = new XmlDocument();
                        document.Load(outputFile);
                    }
                    catch (Exception e)
                    {
                        Logger.LogError(e, "Error when loading file {0} as XmlDocument", outputFile);
                    }
                }
            }
            finally
            {
                Console.SetOut(oldWriter);
            }
        }
Example #7
0
        public static string FormatXMLForPowershell(string xml, bool forWebUse = false)
        {
            var sb = new StringBuilder();

            using (var reader = new XmlTextReader(xml, XmlNodeType.Element, null))
            {
                while (reader.Read())
                {
                    var type  = reader.NodeType;
                    var name  = reader.Name.ToLowerInvariant();
                    var value = reader.Value;

                    if (type == XmlNodeType.Element)
                    {
                        switch (name)
                        {
                        case "ul":
                        case "ol":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                            }
                            else
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "li":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                                sb.Append(" -");
                            }
                            else
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        // very temp hack for ElastiCache in 2.3.5.0 release -- this is the only known set of table elements,
                        // we want them in web help but not maml docs
                        case "table":
                            if (!forWebUse)
                            {
                                sb.Append(newAvailabilityZonesTableReplacement);
                                do
                                {
                                    reader.Read();
                                } while (!reader.Name.Equals("table", StringComparison.OrdinalIgnoreCase));
                            }
                            else
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "th":
                        case "tr":
                        case "td":
                        case "div":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "dl":
                        case "dt":
                        case "dd":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "strong":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "pre":
                            if (forWebUse)
                            {
                                sb.AppendFormat("<{0}>", name);
                            }
                            break;

                        case "link":
                        case "filename":
                        case "replaceable":
                        case "seealso":
                            break;

                        default:
                            if (!HandleElement(sb, reader, name, forWebUse))
                            {
                                throw new InvalidOperationException("Unsupported node of type " + name + ". Full XML: " + xml);
                            }
                            break;
                        }
                    }
                    else if (type == XmlNodeType.EndElement)
                    {
                        switch (name)
                        {
                        case "li":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                            }
                            else
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "ul":
                        case "ol":
                            if (!forWebUse)
                            {
                                sb.AppendLine();
                                sb.AppendLine();
                            }
                            else
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        // very temp hack for ElastiCache in 2.3.5.0 release -- this is the only known set of table elements,
                        // we want them in web help but not maml docs
                        case "table":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "th":
                        case "tr":
                        case "td":
                        case "div":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "dl":
                        case "dt":
                        case "dd":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "strong":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "pre":
                            if (forWebUse)
                            {
                                sb.AppendFormat("</{0}>", name);
                            }
                            break;

                        case "link":
                        case "filename":
                        case "replaceable":
                        case "seealso":
                            break;

                        default:
                            if (!HandleEndElement(sb, name, forWebUse))
                            {
                                throw new InvalidOperationException("Unsupported node of type " + name +
                                                                    ". Full XML: " + xml);
                            }
                            break;
                        }
                    }
                    else if (type == XmlNodeType.Text)
                    {
                        // XmlReader unescapes by default, with no apparent option to turn off - so docs
                        // that have escaped <> characters can form invalid xml when doc writers use them
                        // to illustrate replaced values in a string. This test makes sure we don't have
                        // any unclosed tags and if we do, re-escapes the string.
                        try
                        {
                            var x = new XmlDocument();
                            // need dummy outer tags for the purposes of LoadXml
                            x.LoadXml("<a>" + value + "</a>");
                        }
                        catch
                        {
                            value = value.Replace("<", "&lt;").Replace(">", "&gt;");
                        }
                        sb.Append(value);
                    }
                }

                string composed = sb.ToString();
                string final    = DocumentationUtils.ProcessLines(composed, l => l, compressConsequitiveNonemptyLines: true, compressConsequitiveEmptyLines: true, skipEmptyLines: true);
                return(final);
            }
        }