/// <summary>
        /// Appends the contents of a constant value node, transformed into its best-matching XMLDoc equivalent.
        /// </summary>
        /// <param name="descriptionBuilder">The builder to append to.</param>
        /// <param name="function">The function the node is in.</param>
        /// <param name="parameter">The parameter the node is in.</param>
        /// <param name="constantNode">The node.</param>
        /// <param name="greedilyConsumedNodes">Nodes that have been greedily consumed.</param>
        private void AppendConstantNode
        (
            [NotNull] StringBuilder descriptionBuilder,
            [NotNull] FunctionDocumentation function,
            [NotNull] ParameterDocumentation parameter,
            [NotNull] XElement constantNode,
            [NotNull, ItemNotNull] ICollection <XNode> greedilyConsumedNodes
        )
        {
            var constantName = constantNode.Value;

            if (constantName.StartsWith("GL_"))
            {
                // It's an enum - transform its name, and look it up
                var translatedName = _identifierTranslator.Translate
                                     (
                    constantName.Remove(0, 3)
                                     );

                // Arbitrary number handling
                string arbitraryNumberName      = null;
                var    hasArbitraryNumberInsert = false;
                if (translatedName.EndsWith("i"))
                {
                    translatedName           = translatedName.Remove(translatedName.Length - 1);
                    hasArbitraryNumberInsert = true;
                    arbitraryNumberName      = "i";
                }

                var hasEmphasizedNumberInsert = constantNode.NextNode is XElement emphasisNode &&
                                                (emphasisNode.Attribute("class")?.Value == "emphasis" ||
                                                 emphasisNode.Attribute("class")?.Value == "replaceable");
                if (hasEmphasizedNumberInsert)
                {
                    var nextNode = (XElement)constantNode.NextNode;
                    arbitraryNumberName      = nextNode.Value;
                    hasArbitraryNumberInsert = true;
                }

                // See if it's followed by a number (emphasized)
                if (hasArbitraryNumberInsert)
                {
                    // It is a number, so we'll just stick a 0 on the end as a stand-in
                    translatedName = $"{translatedName}0";
                }

                if (hasEmphasizedNumberInsert)
                {
                    greedilyConsumedNodes.Add(constantNode.NextNode);
                }

                // See if we can find the name of the enum that the constant is in
                // Look up the function that the parameter is in
                var functionNameWithoutPrefix = new string(function.Name.SkipWhile(char.IsLower).ToArray());

                var actualFunction =
                    _apiProfile.FindFunctionWithEntrypoint(functionNameWithoutPrefix);

                if (actualFunction is null)
                {
                    throw new InvalidOperationException
                          (
                              $"Could not find a function named \"{functionNameWithoutPrefix}\""
                          );
                }

                var actualParameter =
                    actualFunction.Parameters.FirstOrDefault(p => p.Name == parameter.Name);

                var typeName = actualParameter?.Type.Name;
                if (typeName is null)
                {
                    Debug.WriteLine
                    (
                        $"Could not find the parameter named \"{parameter.Name}\" in " +
                        $"\"{actualFunction.Name}\". Consider adding a name override to " +
                        $"match the documentation."
                    );
                }

                if (typeName is null || _apiProfile.Enumerations.All(e => e.Name != typeName))
                {
                    var containingEnum = _apiProfile.FindContainingEnumeration(translatedName);
                    if (containingEnum is null)
                    {
                        typeName = "Unknown";
                    }
                    else
                    {
                        typeName = containingEnum.Name;
                    }

                    Debug.WriteLine
                    (
                        $"Falling back to a manual enum type search for " +
                        $"\"{translatedName}\"."
                    );
                }

                descriptionBuilder.Append
                (
                    $"<see cref=\"{typeName}.{translatedName}\"/>"
                );

                if (hasArbitraryNumberInsert)
                {
                    descriptionBuilder.Append($" (consider 0 as <i>{arbitraryNumberName}</i>)");
                }
            }
            else
            {
                // It's a constant value of some sort - transform it to lower, and inline
                descriptionBuilder.Append
                (
                    $"<value>{constantName.Transform(To.LowerCase)}</value>"
                );
            }
        }
        private ParameterDocumentation BakeParameterDocumentation
        (
            [NotNull] FunctionDocumentation function,
            [NotNull] ParameterDocumentation parameter
        )
        {
            var readerSettings = new XmlReaderSettings
            {
                ConformanceLevel = ConformanceLevel.Fragment,
                IgnoreComments   = true,
                IgnoreWhitespace = true
            };

            var descriptionBuilder = new StringBuilder();

            using (var sr = new StringReader(parameter.Description))
            {
                using (var xr = XmlReader.Create(sr, readerSettings))
                {
                    var descriptionFragment = XDocument.Load(xr).Root;

                    // Each node handler may consume nodes that come after it. When they do, they're placed in this list
                    // so that they can be skipped.
                    var greedilyConsumedNodes = new List <XNode>();

                    // ReSharper disable once PossibleNullReferenceException
                    foreach (var node in descriptionFragment.Nodes())
                    {
                        if (greedilyConsumedNodes.Contains(node))
                        {
                            continue;
                        }

                        switch (node)
                        {
                        case XText text:
                        {
                            var lines = text.Value.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                            foreach (var line in lines)
                            {
                                if (string.IsNullOrWhiteSpace(line))
                                {
                                    continue;
                                }

                                descriptionBuilder.AppendLine(line.Trim());
                            }

                            break;
                        }

                        case XElement element:
                        {
                            if (element.Name == "math")
                            {
                                // This is a MathML block. We'll just write it as-is.
                                descriptionBuilder.Append(element);
                                break;
                            }

                            if (element.Attribute("class") is null)
                            {
                                // Some sort of weird HTML tag
                                descriptionBuilder.Append(element);
                                break;
                            }

                            var elementClass = element.GetRequiredAttribute("class").Value;
                            switch (elementClass)
                            {
                            case "parameter":
                            {
                                AppendParameterNode(descriptionBuilder, element);
                                break;
                            }

                            case "constant":
                            {
                                AppendConstantNode(descriptionBuilder, function, parameter, element, greedilyConsumedNodes);
                                break;
                            }

                            case "replaceable":
                            case "code":
                            case "emphasis":
                            {
                                AppendEmphasisNode(descriptionBuilder, element);
                                break;
                            }

                            case "citerefentry":
                            case "function":
                            {
                                AppendFunctionNode(descriptionBuilder, element);
                                break;
                            }

                            case "footnote":
                            {
                                // Skip footnotes
                                break;
                            }

                            default:
                            {
                                throw new ArgumentOutOfRangeException
                                      (
                                          nameof(node),
                                          $"Unrecognized element class\"{elementClass}\"."
                                      );
                            }
                            }

                            break;
                        }

                        default:
                        {
                            throw new ArgumentOutOfRangeException(nameof(node), "Unrecognized node type.");
                        }
                        }
                    }
                }
            }

            var description = descriptionBuilder.ToString().Trim();

            if (!description.EndsWith("."))
            {
                description = $"{description}.";
            }

            return(new ParameterDocumentation(parameter.Name, description));
        }