36

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

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

4

9 回答 9

39

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

扩展@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>')

返回假。

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

返回真。

编辑:正如@Quentin 下面所指出的,由于各种原因,这过于严格:浏览器通常会修复省略的结束标签,即使结束标签对于该标签是可选的。例如:

<p>one para
<p>second para

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

于 2013-01-08T13:50:54.310 回答
20

好吧,这段代码:

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

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

于 2012-04-05T10:12:03.340 回答
10

到目前为止,没有一个解决方案在回答原始问题方面做得很好,尤其是在涉及到

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

tldr >> 检查JSFiddle

因此,我使用了有关该主题的答案和评论的输入,并创建了一个执行以下操作的方法:

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

退货

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

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

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

检查会考虑以下例外情况

  • 忽略单引号到双引号的规范化
  • image和其他具有src属性的标签在渲染期间被“解除”
  • (如果不严格)忽略<br/>>><br>转换
  • (如果不严格)忽略空属性的规范化(<p disabled>>> <p disabled="">
  • 读取时对最初未编码的 & 符号进行编码.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

.

于 2018-04-07T20:43:57.370 回答
6

9 年后,使用DOMParser怎么样?

它接受字符串作为参数并返回文档类型,就像 HTML 一样。因此,当它发生错误时,返回的文档对象中包含<parsererror>元素。

如果您将 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 有一些类似代码的文本;<script>, <style>, 等等,尽管它是有效的 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');
  }
}
于 2021-02-11T19:12:10.417 回答
3
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');
}
于 2015-09-29T05:43:41.073 回答
0

从上面扩展@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(除了少数例外)。然而,这不会通过我自己的健全性检查。

于 2021-05-19T20:00:55.283 回答
0
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;
}

祝你好运!

于 2016-07-27T20:17:50.383 回答
0

使用纯 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上找到。

于 2015-09-29T08:07:11.377 回答
0

这取决于您使用的 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;
}
于 2018-03-22T14:56:38.093 回答