使用jQuery解析远程内容的最佳实践是什么?

37

在使用jQuery的ajax调用来检索整个XHTML文档之后,从结果字符串中选择特定元素的最佳方法是什么?也许有一个库或插件可以解决这个问题?

如果XHTML元素在W3C规范中通常允许在div中,则jQuery只能选择字符串中存在的XHTML元素;因此,我想知道如何选择诸如 <title><script><style> 这样的元素。

根据jQuery文档:

http://docs.jquery.com/Core/jQuery#htmlownerDocument

HTML字符串不能包含在 div 中无效的元素,例如 html、head、body 或 title 元素。

因此,既然我们已经确定jQuery没有提供这样做的方法,那么我该如何选择这些元素?例如,如果您可以向我展示如何选择远程页面的标题,那就太棒了!

谢谢,Pete


好时机!我现在也需要一个答案。 - Josh Stodola
我注意到即使尝试解析RSS源,这个问题也会出现。你不能获取例如在<item>标签内的<title>或<link>。 - slypete
10个回答

32

我建议您不要修改jQuery,而是暂时退出jQuery并使用原始的XML DOM方法。使用XML DOM方法,您可以这样做:

  window.onload = function(){ 
    $.ajax({
          type: 'GET', 
          url: 'text.html',
          dataType: 'html',
          success: function(data) {

            //cross platform xml object creation from w3schools
            try //Internet Explorer
              {
              xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
              xmlDoc.async="false";
              xmlDoc.loadXML(data);
              }
            catch(e)
              {
              try // Firefox, Mozilla, Opera, etc.
                {
                parser=new DOMParser();
                xmlDoc=parser.parseFromString(data,"text/xml");
                }
              catch(e)
                {
                alert(e.message);
                return;
                }
              }

            alert(xmlDoc.getElementsByTagName("title")[0].childNodes[0].nodeValue);
          }
    });
  }

不要使用iframe等复杂的元素。


1
我在使用这个方法和正则表达式之间犹豫不决。由于我正在寻求最佳实践,所以选择了这个答案。 - slypete

5

只是一个想法 - 在火狐/ Safari中测试 - 如果您创建一个iframe来临时存储文档,则似乎有效。当然,如果您这样做,最好使用iframe的src属性加载文档,并在其“onload”中执行任何操作。

  $(function() {
    $.ajax({
      type: 'GET', 
      url: 'result.html',
      dataType: 'html',
      success: function(data) {
        var $frame = $("<iframe src='about:blank'/>").hide();
        $frame.appendTo('body');
        var doc = $frame.get(0).contentWindow.document;
        doc.write(data);
        var $title = $("title", doc);
        alert('Title: '+$title.text() );
        $frame.remove();
      }
    });
  });

我不得不将iframe附加到body上,才能使它具有.contentWindow。


我为什么一开始没想到使用iframe呢?这太棒了!我唯一可能看到的问题是,如果你必须从另一个站点获取文档,那么iframe可能无法工作。虽然我没有尝试过,但我认为跨域脚本策略将禁止与源进行任何交互。 - Igor Zinov'yev
这个可以运行,但问题是寻找的是最佳实践而不是一个破解办法。 - slypete
@zinigor 这是一个无意义的论点,因为您甚至无法进行跨域AJAX请求。 - Josh Stodola
@Josh Stodola 是的,但我不是在谈论AJAX请求。您可以简单地编辑iframe的src属性,并使其指向另一个站点,它不必在同一域上。这就是相同源策略限制所有交互的地方。 - Igor Zinov'yev
@JoshStodola 针对这个问题有一个解决方法,你可以使用curl获取内容并将其输出,然后发起一个ajax请求到该文件。 - Andrei Cristian Prodan

3
此答案启发,但使用延迟处理:
function fetchDoc(url) {
  var dfd;
  dfd = $.Deferred();

  $.get(url).done(function (data, textStatus, jqXHR) {

    var $iframe = $('<iframe style="display:none;"/>').appendTo('body');
    var $doc = $iframe.contents();
    var doc = $doc[0];

    $iframe.load(function() {
      dfd.resolveWith(doc, [data, textStatus, jqXHR]);
      return $iframe.remove();
    });
    doc.open();
    doc.write(data);

    return doc.close();
  }).fail(dfd.reject);

  return dfd.promise();
};

并使用以下方法:

fetchDoc('/foo.html').done(function (data, textStatus, jqXHR) {
  alert($('title', this).text());
});

在线演示(点击“运行”)


2

这个可以工作。我只是为了更好的可读性,将构建块分开了。

查看解释和内联注释以理解其工作原理以及为什么必须像这样制作。

当然,这不能用于检索跨域内容,对于这个问题,您要么必须通过自己的脚本代理调用,要么考虑集成类似 flXHR(使用Flash进行跨域Ajax) 的东西。

call.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>asd</title>
    <script src="jquery.js" type="text/javascript"></script>
    <script src="xmlDoc.js" type="text/javascript"></script>
    <script src="output.js" type="text/javascript"></script>
    <script src="ready.js" type="text/javascript"></script>
  </head>
  <body>
    <div>
      <input type="button" id="getit" value="GetIt" />
    </div>
  </body>
</html>

jquery.js 是 (jQuery 1.3.2 未压缩版本) test.html 是一个有效的XHTML文档

xmlDoc.js

// helper function to create XMLDocument out of a string
jQuery.createXMLDocument = function( s ) {
  var xmlDoc;
  // is it a IE?
  if ( window.ActiveXObject ) {
    xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
    xmlDoc.async = "false";
    // prevent erros as IE tries to resolve the URL in the DOCTYPE
    xmlDoc.resolveExternals = false;
    xmlDoc.validateOnParse = false;
    xmlDoc.loadXML(s);
  } else {
    // non IE. give me DOMParser
    // theoretically this else branch should never be called
    // but just in case.
    xmlDoc = ( new DOMParser() ).parseFromString( s, "text/xml" );
  }
  return xmlDoc;
};

output.js

// Output the title of the loaded page
// And get the script-tags and output either the
// src attribute or code
function headerData(data) {
  // give me the head element
  var x = jQuery("head", data).eq(0);
  // output title
  alert(jQuery("title", x).eq(0).text());
  // for all scripttags which include a file out put src
  jQuery("script[src]", x).each(function(index) {
    alert((index+1)+" "+jQuery.attr(this, 'src'));
  });
  // for all scripttags which are inline javascript output code
  jQuery("script:not([src])", x).each(function(index) {
    alert(this.text);
  });
}

ready.js

$(document).ready(function() {
  $('#getit').click(function() {
    $.ajax({
      type : "GET",
      url : 'test.html',
      dataType : "xml",
      // overwrite content-type returned by server to ensure
      // the response getst treated as xml
      beforeSend: function(xhr) {
        // IE doesn't support this so check before using
        if (xhr.overrideMimeType) {
          xhr.overrideMimeType('text/xml');
        }
      },
      success: function(data) {
        headerData(data);
      },
      error : function(xhr, textStatus, errorThrown) {
        // if loading the response as xml failed try it manually
        // in theory this should only happen for IE
        // maybe some
        if (textStatus == 'parsererror') {
          var xmlDoc = jQuery.createXMLDocument(xhr.responseText);
          headerData(xmlDoc);
        } else {
          alert("Failed: " + textStatus + " " + errorThrown);
        }
      }
    });
  });
});

在Opera中,整个过程都不需要使用createXMLDocumentbeforeSend函数。
为了解决Firefox(3.0.11)和IE6(无法测试IE7、IE8和其他浏览器)的问题,需要额外的技巧,因为它们在服务器返回的Content-Type:没有指示它是xml时会出现问题。我的Web服务器针对test.html返回了Content-Type: text/html; charset=UTF-8。在这两个浏览器中,jQuery调用了error回调,并且textStatus显示parsererror。这是因为在jQuery.js的第3706行。
data = xml ? xhr.responseXML : xhr.responseText;

data 被设置为 null。在 FF 和 IE 中,xhr.responseXML 是 null。这是因为它们无法识别返回的数据为 xml(而 Opera 可以)。只有 xhr.responseText 包含整个 xhtml 代码。由于 data 是 null,所以第3708行出现了问题。

if ( xml && data.documentElement.tagName == "parsererror" )

抛出一个异常,该异常在第3584行被捕获,并将状态设置为parsererror

在Firefox中,我可以通过在发送请求之前使用overrideMimeType()函数来解决这个问题。

但是IE不支持XMLHttpRequest对象上的此函数,因此如果运行错误回调并且错误为parsererror,则必须自己生成XMLDocument。

test.html示例。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Plugins | jQuery Plugins</title>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript">var imagePath = '/content/img/so/';</script>
  </head>
  <body>
  </body>
</html>

2
如何快速更改标签名称?
$.ajax({
    type : "GET",
    url : 'results.html',
    dataType : "html",
    success: function(data) {

        data = data.replace(/html/g, "xhtmlx");
        data = data.replace(/head/g, "xheadx");
        data = data.replace(/title/g, "xtitlex");
        data = data.replace(/body/g, "xbodyx");

        alert($(data).find("xtitlex").text());
    }

});

@Ben Koehler:我想使用这段代码,但是惊讶的是,在IE8上无法正常工作(没有测试过IE7),但在Chrome 2.x和FF 3.x上可以正常运行。 - TheBoubou

1
不要脸地从我的另一个答案中复制并改编(Simple jQuery ajax example not finding elements in returned HTML),这个程序获取远程页面的HTML,然后parseHTML函数为其创建一个临时div元素,并将所有内容放入其中,运行并返回所请求的元素。jQuery然后弹出其中的text()。
$(document).ready(function(){
  $('input').click(function(){
    $.ajax({
      type : "POST",
      url : 'ajaxtestload.html',
      dataType : "html",
      success: function(data) {
        alert( data ); // shows whole dom
        var gotcha = parseHTML(data, 'TITLE'); // nodeName property returns uppercase
        if (gotcha) {
          alert($(gotcha).html()); // returns null
        }else{
          alert('Tag not found.');
        }
      },
      error : function() {
        alert("Sorry, The requested property could not be found.");
      }
    });
  });
});

function parseHTML(html, tagName) {
  var root = document.createElement("div");
  root.innerHTML = html;
  // Get all child nodes of root div
  var allChilds = root.childNodes;
  for (var i = 0; i < allChilds.length; i++) {
    if (allChilds[i].nodeName == tagName) {
      return allChilds[i];
    }
  }
  return false;
}

为了从一组脚本标签中获取多个项目,我认为您需要改进parseHTML函数,但是,嘿-概念验证 :-)

肯定地说,应用简单的线性搜索并不是最佳实践。 - slypete
当然不是-但是这个例子展示了一种方法,可以从身体外部的外部文档中选择标签,而jQuery无法做到。http://www.javascriptkit.com/dhtmltutors/treewalker.shtml可能会有用? - MSpreij
此外,我相信这个问题也会遇到与jQuery获取<head><title><meta>等标签相同的问题。请阅读有关htmlOwnerDocument的问题链接。 - gnarf
gnarf,不确定你的意思 - 我测试了代码,它可以工作?jQuery 实际上并不获取 HTML,只是获取“数据”。非 jQuery 函数将该数据解析为标签之一,然后返回给 jQuery。如果您遇到 jQuery 不喜欢的标签,您始终可以使用相同的函数解析其内容。 - MSpreij

0
如果您想查找特定命名字段的值(即表单中的输入),可以使用类似以下代码来查找它们:
var fields = ["firstname","surname", ...."foo"];

function findFields(form, fields) {
  var form = $(form);
  fields.forEach(function(field) {
    var val = form.find("[name="+field+"]").val();
    ....

谢谢,但那不是我想要做的。 - slypete
我看到你重新编辑了你的问题。如果你想处理整个XHTML片段,那么像Resig的微型模板这样的东西可以指引你朝着正确的方向前进...请参考http://ejohn.org/blog/javascript-micro-templating/ - timbo
不确定你们两个的回答怎么能间接地回答我的问题。我没有对这个问题进行重大编辑。 - slypete

0
$.get('yourpage.html',function(data){
    var content = $('<div/>').append(data).find('#yourelement').html();
});

你也可以简单地暂时将其包装在一个 div 中。甚至不需要将其添加到 DOM 中。


0

0

将XML字符串解析为XML DOM后,我会直接使用jQuery(可以通过向jQUery选择器提供上下文来实现,例如$(':title', xdoc.rootElement))或使用XPath(在Firefox中有效;据说IE有相应的库,但我没有成功过)。


从问题中:“既然我们已经确定jQuery没有提供这样的方法”... - slypete

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接