【问题标题】:Check if HTML snippet is valid with JavaScript检查 HTML 片段是否对 JavaScript 有效
【发布时间】:2012-04-19 01:50:00
【问题描述】:

我需要一个可靠的 JavaScript 库/函数来检查我可以从我的代码中调用的 HTML sn-p 是否有效。例如,它应该检查打开的标签和引号是否闭合,嵌套是否正确等。

我不希望验证失败,因为某些东西不是 100% 标准的(但无论如何都会起作用)。

【问题讨论】:

    标签: javascript html validation


    【解决方案1】:

    更新:这个答案是有限的 - 请参阅下面的编辑。

    扩展@kolink 的答案,我使用:

    var checkHTML = function(html) {
      var doc = document.createElement('div');
      doc.innerHTML = html;
      return ( doc.innerHTML === html );
    }
    

    即,我们使用 HTML 创建一个临时 div。为此,浏览器会根据 HTML 字符串创建一个 DOM 树,这可能涉及到结束标签等。

    将 div 的 HTML 内容与原始 HTML 进行比较将告诉我们浏览器是否需要更改任何内容。

    checkHTML('<a>hell<b>o</b>')
    

    返回 false。

    checkHTML('<a>hell<b>o</b></a>')
    

    返回真。

    编辑:正如@Quentin 在下面指出的那样,这是过于严格,原因有很多:浏览器通常会修复省略的结束标签,即使结束标签是可选的那个标签。例如:

    <p>one para
    <p>second para
    

    ...被认为是有效的(因为允许 Ps 省略结束标签)但 checkHTML 将返回 false。浏览器还将规范化标签大小写,并改变空白。在决定使用此方法时,您应该了解这些限制。

    【讨论】:

    • 这实际上不起作用。以checkHTML("&lt;p&gt;Test&lt;P&gt;test") 为例。这是完全有效的 HTML,但是当浏览器将其从innerHTML 中拉出时,它会对其进行规范化。
    • 从该有效 HTML 生成的 DOM 中的元素之外不会有文本。
    • 什么无效的HTML? p 元素的结束标记在 HTML 中是可选的。浏览器不会更正它,它会规范化它(通过在它从 DOM 生成新 HTML 时包含结束标记,而不是通过添加更多段落)。这使得此答案中的方法无法用于测试 HTML 是否有效。
    • 即使&lt;P&gt;&lt;/p&gt;,它仍然是有效的HTML。您可以在 div 中包含文本节点。无论如何,这只是一个例子。它将更改在属性值周围使用哪些引号。小写的属性和元素名称。更改空白。等等等等等等。它会产生 lots 的误报。
    • @Quentin 是的,我也注意到了这个案子。已修改说明,明确表示这过于严格。
    【解决方案2】:

    好吧,这段代码:

    function tidy(html) {
        var d = document.createElement('div');
        d.innerHTML = html;
        return d.innerHTML;
    }
    

    这将在浏览器的最大能力范围内“纠正”格式错误的 HTML。如果这对您有帮助,那么它比尝试验证 HTML 要容易得多。

    【讨论】:

    • 好吧,如果输入错误(复制粘贴错误),我需要让用户再次输入。纠正自己(例如使用 tidy)可以使 HTML 有效,但不能正常工作。
    • 除了纠正格式错误的 HTML,它还规范化了有效的 HTML。
    【解决方案3】:

    到目前为止,没有一个解决方案能够很好地回答最初的问题,尤其是在涉及到

    我不希望验证失败,因为某些事情不是 100% 标准(但无论如何都可以)。

    tldr >> 检查JSFiddle

    所以我使用了关于这个主题的答案和 cmets 的输入,并创建了一个执行以下操作的方法:

    • 逐个标签检查html字符串标签是否有效
    • 尝试渲染 html 字符串
    • 理论上将创建的标签计数与实际渲染的html dom标签计数进行比较
    • 如果选中“严格”,&lt;br/&gt; 和空属性规范化="" 不会被忽略
    • 将呈现的 innerHTML 与给定的 html 字符串进行比较(同时忽略空格和引号)

    返回

    • true 如果呈现的 html 与给定的 html 字符串相同
    • false 如果其中一项检查失败
    • 规范化的 html 字符串 如果呈现的 html 看起来有效但不等于给定的 html 字符串

    规范化意味着,在渲染时,浏览器有时会忽略或修复输入的特定部分(例如为 &lt;p&gt; 添加缺少的结束标记并转换其他部分(例如单引号到双引号或编码& 号)。 区分“失败”和“规范化”允许将内容标记给用户,因为“这不会像您预期的那样呈现”。

    大多数时候规范化 会返回原始 html 字符串的一个稍微改动过的版本 - 尽管如此,有时结果还是完全不同的。所以这应该被使用,例如在将用户输入保存到数据库或盲目渲染之前标记用户输入以供进一步查看。 (有关规范化的示例,请参阅JSFiddle

    检查会考虑以下例外情况

    • 忽略单引号到双引号的规范化
    • image 和其他带有 src 属性的标签在渲染期间被“解除”
    • (如果不严格)忽略&lt;br/&gt; >> &lt;br&gt; 转换
    • (如果不严格)忽略空属性的规范化(&lt;p disabled&gt; >> &lt;p disabled=""&gt;
    • 在读取 .innerHTML 时对最初未编码的 & 符号进行编码,例如在属性值中

    .

    function simpleValidateHtmlStr(htmlStr, strictBoolean) {
      if (typeof htmlStr !== "string")
        return false;
    
      var validateHtmlTag = new RegExp("<[a-z]+(\s+|\"[^\"]*\"\s?|'[^']*'\s?|[^'\">])*>", "igm"),
        sdom = document.createElement('div'),
        noSrcNoAmpHtmlStr = htmlStr
          .replace(/ src=/, " svhs___src=") // disarm src attributes
          .replace(/&amp;/igm, "#svhs#amp##"), // 'save' encoded ampersands
        noSrcNoAmpIgnoreScriptContentHtmlStr = noSrcNoAmpHtmlStr
          .replace(/\n\r?/igm, "#svhs#nl##") // temporarily remove line breaks
          .replace(/(<script[^>]*>)(.*?)(<\/script>)/igm, "$1$3") // ignore script contents
          .replace(/#svhs#nl##/igm, "\n\r"),  // re-add line breaks
        htmlTags = noSrcNoAmpIgnoreScriptContentHtmlStr.match(/<[a-z]+[^>]*>/igm), // get all start-tags
        htmlTagsCount = htmlTags ? htmlTags.length : 0,
        tagsAreValid, resHtmlStr;
    
    
      if(!strictBoolean){
        // ignore <br/> conversions
        noSrcNoAmpHtmlStr = noSrcNoAmpHtmlStr.replace(/<br\s*\/>/, "<br>")
      }
    
      if (htmlTagsCount) {
        tagsAreValid = htmlTags.reduce(function(isValid, tagStr) {
          return isValid && tagStr.match(validateHtmlTag);
        }, true);
    
        if (!tagsAreValid) {
          return false;
        }
      }
    
    
      try {
        sdom.innerHTML = noSrcNoAmpHtmlStr;
      } catch (err) {
        return false;
      }
    
      // compare rendered tag-count with expected tag-count
      if (sdom.querySelectorAll("*").length !== htmlTagsCount) {
        return false;
      }
    
      resHtmlStr = sdom.innerHTML.replace(/&amp;/igm, "&"); // undo '&' encoding
    
      if(!strictBoolean){
        // ignore empty attribute normalizations
        resHtmlStr = resHtmlStr.replace(/=""/, "")
      }
    
      // compare html strings while ignoring case, quote-changes, trailing spaces
      var
        simpleIn = noSrcNoAmpHtmlStr.replace(/["']/igm, "").replace(/\s+/igm, " ").toLowerCase().trim(),
        simpleOut = resHtmlStr.replace(/["']/igm, "").replace(/\s+/igm, " ").toLowerCase().trim();
      if (simpleIn === simpleOut)
        return true;
    
      return resHtmlStr.replace(/ svhs___src=/igm, " src=").replace(/#svhs#amp##/, "&amp;");
    }
    

    在这里,您可以在 JSFiddle https://jsfiddle.net/abernh/twgj8bev/ 中找到它,以及不同的测试用例,包括

    "<a href='blue.html id='green'>missing attribute quotes</a>" // FAIL
    "<a>hell<B>o</B></a>"                                        // PASS
    '<a href="test.html">hell<b>o</b></a>'                       // PASS
    '<a href=test.html>hell<b>o</b></a>',                        // PASS
    "<a href='test.html'>hell<b>o</b></a>",                      // PASS
    '<ul><li>hell</li><li>hell</li></ul>',                       // PASS
    '<ul><li>hell<li>hell</ul>',                                 // PASS
    '<div ng-if="true && valid">ampersands in attributes</div>'  // PASS
    

    .

    【讨论】:

      【解决方案4】:
      function validHTML(html) {
        var openingTags, closingTags;
      
        html        = html.replace(/<[^>]*\/\s?>/g, '');      // Remove all self closing tags
        html        = html.replace(/<(br|hr|img).*?>/g, '');  // Remove all <br>, <hr>, and <img> tags
        openingTags = html.match(/<[^\/].*?>/g) || [];        // Get remaining opening tags
        closingTags = html.match(/<\/.+?>/g) || [];           // Get remaining closing tags
      
        return openingTags.length === closingTags.length ? true : false;
      }
      
      var htmlContent = "<p>your html content goes here</p>" // Note: String without any html tag will consider as valid html snippet. If it’s not valid in your case, in that case you can check opening tag count first.
      
      if(validHTML(htmlContent)) {
        alert('Valid HTML')
      }
      else {
        alert('Invalid HTML');
      }
      

      【讨论】:

      • 如果您愿意,可以将“自关闭标签”和“无需关闭的“HTML”标签”(第 4 行和第 5 行)合并在一起。
      • 因为它使用正则表达式,什么时候失败?另外,return之后的那个三元运算符...
      • &lt;foo&gt;&lt;/bar&gt; 返回 true。
      【解决方案5】:

      9年后,用DOMParser怎么样?

      它接受字符串作为参数并返回Document 类型,就像HTML 一样。 因此,当它出现错误时,返回的文档对象中包含&lt;parsererror&gt; 元素。

      如果您将 html 解析为 xml,至少您可以检查您的 html 是否符合 xhtml。

      例子

      > const parser = new DOMParser();
      > const doc = parser.parseFromString('<div>Input: <input /></div>', 'text/xml');
      > (doc.documentElement.querySelector('parsererror') || {}).innerText; // undefined
      

      把它包装成一个函数

      function isValidHTML(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/xml');
        if (doc.documentElement.querySelector('parsererror')) {
          return doc.documentElement.querySelector('parsererror').innerText;
        } else {
          return true;
        }
      }
      

      测试上述功能

      isValidHTML('<a>hell<B>o</B></a>') // true
      isValidHTML('<a href="test.html">hell</a>') // true
      isValidHTML('<a href='test.html'>hell</a>') // true
      isValidHTML("<a href=test.html>hell</a>")  // This page contains the following err..
      isValidHTML('<ul><li>a</li><li>b</li></ul>') // true
      isValidHTML('<ul><li>a<li>b</ul>') // This page contains the following err..
      isValidHTML('<div><input /></div>' // true
      isValidHTML('<div><input></div>' // This page contains the following err..
      

      以上内容适用于非常简单的 html。 但是,如果您的 html 有一些类似代码的文本; &lt;script&gt;&lt;style&gt; 等,尽管它是有效的 HTML,但您只需要为 XML 验证进行操作

      以下将类似代码的 html 更新为有效的 XML 语法。

      export function getHtmlError(html) {
        const parser = new DOMParser();
        const htmlForParser = `<xml>${html}</xml>`
          .replace(/(src|href)=".*?&.*?"/g, '$1="OMITTED"')
          .replace(/<script[\s\S]+?<\/script>/gm, '<script>OMITTED</script>')
          .replace(/<style[\s\S]+?<\/style>/gm, '<style>OMITTED</style>')
          .replace(/<pre[\s\S]+?<\/pre>/gm, '<pre>OMITTED</pre>')
          .replace(/&nbsp;/g, '&#160;');
      
        const doc = parser.parseFromString(htmlForParser, 'text/xml');
        if (doc.documentElement.querySelector('parsererror')) {
          console.error(htmlForParser.split(/\n/).map( (el, ndx) => `${ndx+1}: ${el}`).join('\n'));
          return doc.documentElement.querySelector('parsererror');
        }
      }
      

      【讨论】:

        【解决方案6】:

        使用纯 JavaScript,您可以使用以下函数检查元素是否存在:

        if (typeof(element) != 'undefined' && element != null)
        

        使用以下代码,我们可以对其进行实际测试:

        HTML:

        <input type="button" value="Toggle .not-undefined" onclick="toggleNotUndefined()">
        <input type="button" value="Check if .not-undefined exists" onclick="checkNotUndefined()">
        <p class=".not-undefined"></p>
        

        CSS:

        p:after {
            content: "Is 'undefined'";
            color: blue;
        }
        p.not-undefined:after {
            content: "Is not 'undefined'";
            color: red;
        }
        

        JavaScript:

        function checkNotUndefined(){
            var phrase = "not ";
            var element = document.querySelector('.not-undefined');
            if (typeof(element) != 'undefined' && element != null) phrase = "";
            alert("Element of class '.not-undefined' does "+phrase+"exist!");
            // $(".thisClass").length checks to see if our elem exists in jQuery
        }
        
        function toggleNotUndefined(){
            document.querySelector('p').classList.toggle('not-undefined');
        }
        

        可以在JSFiddle找到。

        【讨论】:

          【解决方案7】:
          function isHTML(str)
          {
           var a = document.createElement('div');
           a.innerHTML = str;
           for(var c= a.ChildNodes, i = c.length; i--)
           {
              if (c[i].nodeType == 1) return true;
           }
          return false;
          }
          

          祝你好运!

          【讨论】:

            【解决方案8】:

            这取决于你使用的 js-library。

            node.js 的 HTML 验证 https://www.npmjs.com/package/html-validator

            jQuery 的 HTML 验证器 https://api.jquery.com/jquery.parsehtml/

            但是,如前所述,使用浏览器验证损坏的 HTML 是个好主意:

            function tidy(html) {
                var d = document.createElement('div');
                d.innerHTML = html;
                return d.innerHTML;
            }
            

            【讨论】:

              【解决方案9】:

              从上面扩展@Tarun 的答案:

              function validHTML(html) { // checks the validity of html, requires all tags and property-names to only use alphabetical characters and numbers (and hyphens, underscore for properties)
                  html = html.toLowerCase().replace(/(?<=<[^>]+?=\s*"[^"]*)[<>]/g,"").replace(/(?<=<[^>]+?=\s*'[^']*)[<>]/g,""); // remove all angle brackets from tag properties
                  html = html.replace(/<script.*?<\/script>/g, '');  // Remove all script-elements
                  html = html.replace(/<style.*?<\/style>/g, '');  // Remove all style elements tags
                  html = html.toLowerCase().replace(/<[^>]*\/\s?>/g, '');      // Remove all self closing tags
                  html = html.replace(/<(\!|br|hr|img).*?>/g, '');  // Remove all <br>, <hr>, and <img> tags
                  //var tags=[...str.matchAll(/<.*?>/g)]; this would allow for unclosed initial and final tag to pass parsing
                  html = html.replace(/^[^<>]+|[^<>]+$|(?<=>)[^<>]+(?=<)/gs,""); // remove all clean text nodes, note that < or > in text nodes will result in artefacts for which we check and return false
                  tags = html.split(/(?<=>)(?=<)/);
                  if (tags.length%2==1) {
                      console.log("uneven number of tags in "+html)
                      return false;
                  }
                  var tagno=0;
                  while (tags.length>0) {
                      if (tagno==tags.length) {
                          console.log("these tags are not closed: "+tags.slice(0,tagno).join());
                          return false;
                      }
                      if (tags[tagno].slice(0,2)=="</") {
                          if (tagno==0) {
                              console.log("this tag has not been opened: "+tags[0]);
                              return false;
                          }
                          var tagSearch=tags[tagno].match(/<\/\s*([\w\-\_]+)\s*>/);
                          if (tagSearch===null) {
                              console.log("could not identify closing tag "+tags[tagno]+" after "+tags.slice(0,tagno).join());
                              return false;
                          } else tags[tagno]=tagSearch[1];
                          if (tags[tagno]==tags[tagno-1]) {
                              tags.splice(tagno-1,2);
                              tagno--;
                          } else {
                              console.log("tag '"+tags[tagno]+"' trying to close these tags: "+tags.slice(0,tagno).join());
                              return false;
                          }
                      } else {
                          tags[tagno]=tags[tagno].replace(/(?<=<\s*[\w_\-]+)(\s+[\w\_\-]+(\s*=\s*(".*?"|'.*?'|[^\s\="'<>`]+))?)*/g,""); // remove all correct properties from tag
                          var tagSearch=tags[tagno].match(/<(\s*[\w\-\_]+)/);
                          if ((tagSearch===null) || (tags[tagno]!="<"+tagSearch[1]+">")) {
                              console.log("fragmented tag with the following remains: "+tags[tagno]);
                              return false;
                          }
                          var tagSearch=tags[tagno].match(/<\s*([\w\-\_]+)/);
                          if (tagSearch===null) {
                              console.log("could not identify opening tag "+tags[tagno]+" after "+tags.slice(0,tagno).join());
                              return false;
                          } else tags[tagno]=tagSearch[1];
                          tagno++;
                      }
                  }
                  return true;
              }

              这会执行一些额外的检查,例如测试标签是否匹配以及属性是否会解析。由于它不依赖于现有的 DOM,因此可以在服务器环境中使用,但要注意:它很慢。此外,理论上,标签可以是更宽松的名称,因为您基本上可以在标签和属性名称中使用任何 unicode(除了少数例外)。但是,这不会通过我自己的健全性检查。

              【讨论】:

                猜你喜欢
                • 2011-04-07
                • 1970-01-01
                • 2010-10-12
                • 1970-01-01
                • 1970-01-01
                • 2021-12-12
                • 1970-01-01
                • 1970-01-01
                • 2011-03-30
                相关资源
                最近更新 更多