< Summary - SonghayCore

Information
Class: Songhay.Xml.HtmlUtility
Assembly: SonghayCore
File(s): /home/rasx/sourceRoot/SonghayCore/SonghayCore/Xml/HtmlUtility.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 128
Coverable lines: 128
Total lines: 267
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 50
Branch coverage: 0%
Method coverage

Method coverage is only available for sponsors.

Upgrade to PRO version

Metrics

File(s)

/home/rasx/sourceRoot/SonghayCore/SonghayCore/Xml/HtmlUtility.cs

#LineLine coverage
 1namespace Songhay.Xml;
 2
 3/// <summary>
 4/// Static members for HTML text processing.
 5/// </summary>
 6public static class HtmlUtility
 7{
 8    /// <summary>
 9    /// Returns a string of marked up text compatible
 10    /// with browsers that do not support XHTML
 11    /// (loosely towards HTML 4.x W3C standard).
 12    /// </summary>
 13    /// <param name="input">A <see cref="string"/> of markup.</param>
 14    public static string? ConvertToHtml(string? input)
 015    {
 016        if (string.IsNullOrWhiteSpace(input)) return null;
 17
 18        //Minimize selected XHTML block elements.
 019        input
 020            = Regex.Replace(input, @"</(base|isindex|link|meta)>",
 021                string.Empty, RegexOptions.IgnoreCase);
 22
 23        //Remove XHTML html element attributes.
 024        input
 025            = Regex.Replace(input, @"<html*>",
 026                "<html>", RegexOptions.IgnoreCase);
 27
 28        //Remove XHTML element minimization.
 029        input = Regex.Replace(input, @"\s*/>", ">");
 30
 31        //Remove XHTML attribute minimization.
 032        foreach (Match mTag in Regex.Matches(input, @"<[^/][^>]*>"))
 033        {
 34            //An opening input element has been found.
 035            string strReplace = mTag.Value;
 036            foreach (Match mAttr in Regex.Matches(strReplace, @"\s+(.+)\s*=\s*""\1"""))
 037            {
 38                //XHTML minimization found (e.g. foo="foo").
 039                strReplace
 040                    = strReplace.Replace(mAttr.Value,
 041                        string.Concat(" ", mAttr.Groups[1].Value));
 042            }
 43
 044            input = input.Replace(mTag.Value, strReplace);
 045        }
 46
 047        return input;
 048    }
 49
 50    /// <summary>
 51    /// Attempts to convert HTML to well-formed XML.
 52    /// </summary>
 53    /// <param name="html">An HTML <see cref="string"/>.</param>
 54    /// <remarks>This task is simpler than converting to XHTML.</remarks>
 55    public static string? ConvertToXml(string? html)
 056    {
 057        if (string.IsNullOrWhiteSpace(html)) return null;
 58
 59        Regex re;
 60        MatchEvaluator me;
 61
 62        //Remove xmlns attributes:
 063        html = Regex.Replace(html, @"\s*xmlns\s*=\s*""[^""]+""\s*", string.Empty);
 64
 65        //Close open elements:
 066        me = EvaluateOpenElement;
 67
 068        re = new Regex(@"<\s*(br|hr|img|link|meta)([^>]*)(>)", RegexOptions.IgnoreCase);
 069        html = re.Replace(html, me);
 70
 71        //Find attribute minimization:
 072        me = EvaluateElementForMinimizedAttribute;
 73
 074        re = new Regex(@"<[^>]+>", RegexOptions.IgnoreCase);
 075        html = re.Replace(html, me);
 76
 77        //Find attributes without quotes:
 078        me = EvaluateElementForMalformedAttribute;
 79
 080        re = new Regex(@"<[^>]+>", RegexOptions.IgnoreCase);
 081        html = re.Replace(html, me);
 82
 83        //Generate attributes:
 084        me = EvaluateAttribute;
 85
 086        re = new Regex(@"<\s*[^>]+\s(checked|nobreak|nosave|selected)[^=>]*\/*>", RegexOptions.IgnoreCase);
 087        html = re.Replace(html, me);
 88
 89        //Look for Query strings with raw ampersands:
 090        foreach (Match m in Regex.Matches(html, @"href\s*=\s*""[^""]+"""))
 091        {
 092            if (!m.Value.Contains("&amp;")) html = html.Replace(m.Value, m.Value.Replace("&", "&amp;"));
 093        }
 94
 95        //Replace the CDATA "xmlns" with "x…mlns" (adds a soft-hyphen):
 096        html = html.Replace("xmlns", "x…mlns");
 97
 098        return html;
 099    }
 100
 101    /// <summary>
 102    /// Returns an XHTML string derived from a .NET procedure.
 103    /// </summary>
 104    /// <param name="xmlFragment">
 105    /// A well-formed <see cref="string"/> of XML.
 106    /// </param>
 107    /// <remarks>
 108    /// This member addresses certain quirks
 109    /// that well-formed XML cannot have in a contemporary Web browser.
 110    /// </remarks>
 111    public static string? FormatXhtmlElements(string? xmlFragment)
 0112    {
 0113        if (string.IsNullOrWhiteSpace(xmlFragment)) return null;
 114
 115        //Maximize selected empty minimized block elements.
 0116        foreach (Match m in Regex.Matches(xmlFragment, @"<(a|iframe|td|th|script)\s+[^>]*\s*(\/>)",
 0117                     RegexOptions.IgnoreCase))
 0118        {
 0119            if (m.Groups.Count == 2)
 0120            {
 0121                var newValue = m.Value.Replace(m.Groups[1].Value,
 0122                    string.Concat("></", m.Groups[0].Value, ">"));
 0123                xmlFragment = xmlFragment.Replace(m.Value, newValue);
 0124            }
 0125        }
 126
 0127        return xmlFragment;
 0128    }
 129
 130    /// <summary>
 131    /// Returns the …inner… fragment of XML
 132    /// from the specified unique element.
 133    /// </summary>
 134    /// <param name="xmlFragment">
 135    /// A well-formed <see cref="string"/> of XML.
 136    /// </param>
 137    /// <param name="elementName">
 138    /// The local name of the element in the XML string.
 139    /// </param>
 140    public static string? GetInnerXml(string? xmlFragment, string? elementName)
 0141    {
 0142        if (string.IsNullOrWhiteSpace(xmlFragment)) return null;
 143
 0144        string ret = xmlFragment;
 145
 0146        string pattern = string.Format(CultureInfo.InvariantCulture, @"<{0}[^>]*>((\s*.+\s*)+)<\/{0}>", elementName);
 0147        foreach (Match m in Regex.Matches(ret, pattern, RegexOptions.IgnoreCase))
 0148        {
 0149            if (m.Groups.Count > 1) ret = m.Groups[1].Value;
 0150            break;
 151        }
 152
 153        //Remove first four spaces at start of line.
 0154        ret = Regex.Replace(ret, @"\r\n\W{4}", "\r\n");
 155
 0156        return ret;
 0157    }
 158
 159    /// <summary>
 160    /// Emits a public <c>DOCTYPE</c> tag.
 161    /// </summary>
 162    /// <param name="rootElement">
 163    /// The root element of the DTD.
 164    /// </param>
 165    /// <param name="publicIdentifier">
 166    /// The public identifier of the DTD.
 167    /// </param>
 168    /// <param name="resourceReference">
 169    /// The link to reference material of the DTD.
 170    /// </param>
 171    /// <returns>
 172    /// A public <c>DOCTYPE</c> tag.
 173    /// </returns>
 174    public static string PublicDocType(string? rootElement = "html",
 175        string? publicIdentifier = "-//W3C//DTD XHTML 1.0 Transitional//EN",
 176        string? resourceReference = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") =>
 0177        string.Format(CultureInfo.InvariantCulture, "<!DOCTYPE {0} PUBLIC \"{1}\" \"{2}\">",
 0178            rootElement, publicIdentifier, resourceReference);
 179
 180    #region Regular Expression Match Evaluators
 181
 182    static string EvaluateAttribute(Match match)
 0183    {
 0184        var s = match.Value;
 0185        if (match.Groups.Count != 2) return s;
 186
 0187        var group1Value = match.Groups[1].Value;
 0188        s = match.Groups[0].Value.Replace(group1Value,
 0189            string.Format(CultureInfo.InvariantCulture, @"{0}=""{0}""", group1Value));
 190
 0191        return s;
 0192    }
 193
 194    static string EvaluateElementForMalformedAttribute(Match match)
 0195    {
 0196        var s = match.Value;
 0197        var re = new Regex(@"([^\""\s]+)(\s*=\s*)([^\""\s]+)\s", RegexOptions.IgnoreCase);
 0198        var me = new MatchEvaluator(EvaluateMalformedAttribute);
 0199        return re.Replace(s, me);
 0200    }
 201
 202    static string EvaluateElementForMinimizedAttribute(Match match)
 0203    {
 0204        var s = match.Value;
 205
 0206        var re = new Regex(@"\<\s*/");
 0207        if (re.IsMatch(s)) return s; //ignore closing element
 208
 0209        var placeholderPrefix = "!*m";
 0210        var placeholderTemplate = string.Concat(placeholderPrefix, "{0}");
 211
 212        //remove strings between quotes:
 0213        var betweenQuotes = Regex.Matches(s, @"([""'])(?:(?=(\\?))\2.)*?\1", RegexOptions.IgnoreCase);
 0214        foreach (Match m in betweenQuotes)
 0215        {
 0216            var placeholder = string.Format(placeholderTemplate, m.Index);
 0217            s = s.Replace(m.Value, string.Format(placeholder, m.Index));
 0218        }
 219
 220        //evaluate what was not removed:
 0221        var possibilities = Regex.Matches(s, @"(\b[^\s]+\b)", RegexOptions.IgnoreCase);
 0222        foreach (Match m in possibilities)
 0223        {
 0224            if (m.Index == 1) continue; //match should not be element name
 0225            if (m.Value.Contains('=')) continue; //match should not be attribute-value pair
 0226            s = s.Replace(m.Value, string.Format(@"{0}=""{0}""", m.Value));
 0227        }
 228
 229        //restore strings between quotes:
 0230        foreach (Match m in betweenQuotes)
 0231        {
 0232            var reArg = string.Concat(Regex.Escape(placeholderPrefix), m.Index, @"\b");
 0233            re = new Regex(reArg);
 0234            s = re.Replace(s, m.Value, 1);
 0235        }
 236
 0237        return s;
 0238    }
 239
 240    static string EvaluateMalformedAttribute(Match match)
 0241    {
 0242        var s = match.Value;
 0243        if (match.Groups.Count != 4) return s;
 0244        if (s.Contains('\'')) return s;
 245
 0246        return s.Contains('"')
 0247            ? s
 0248            : $@" {match.Groups[1].Value.Trim()}{match.Groups[2].Value.Trim()}""{match.Groups[3].Value.Trim()}"" ";
 0249    }
 250
 251    static string EvaluateOpenElement(Match match)
 0252    {
 0253        var s = match.Value;
 0254        if (match.Groups.Count != 4) return s;
 255
 256        //Refuse closed elements:
 0257        if (match.Groups[2].Value.Trim().EndsWith("/", StringComparison.OrdinalIgnoreCase)) return s;
 258
 0259        string oldValue = match.Groups[3].Value;
 260
 0261        if (oldValue.IndexOf(">", StringComparison.OrdinalIgnoreCase) != -1) s = s.Replace(oldValue, " />");
 262
 0263        return s;
 0264    }
 265
 266    #endregion
 267}