Java XML解析器块(非常不寻常和奇怪!)

时间:2011-04-01 22:35:19

标签: java xml parsing xslt

我有一个非常奇怪的案例:

我尝试使用默认的Java XML解析器解析几个符合XHTML的网站。解析过程中的测试块(不是在下载过程中)。

这可能是一个错误,还是解析器在解析期间尝试下载其他引用的资源(这将是一个“好的”反特征)?

使用简单的数据,它可以工作。 (TEST1)
对于复杂的数据,它会阻塞。 (TEST2)
(我尝试了en.wikipedia.orgvalidator.w3.org

发生阻塞时,CPU处于空闲状态。

使用JDK6和JDK7测试,结果相同。

请参阅测试用例,源代码已准备好进行复制+粘贴+运行。

来源

import java.io.*;
import java.net.*;
import java.nio.charset.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;

public class _XmlParsingBlocks {

  private static Document parseXml(String data)
      throws Exception {
    Transformer t = TransformerFactory.newInstance().newTransformer();
    DocumentBuilder b = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    DOMResult out = new DOMResult(b.newDocument());
    t.transform(new StreamSource(new StringReader(data)), out);
    return (Document) out.getNode();
  }

  private static byte[] streamToByteArray(InputStream is)
      throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    for (;;) {
      byte[] buffer = new byte[256];
      int count = is.read(buffer);
      if (count == -1) {
        is.close();
        break;
      }
      baos.write(buffer, 0, count);
    }

    return baos.toByteArray();
  }

  private static void test(byte[] data)
      throws Exception {
    String asString = new String(data, Charset.forName("UTF-8"));

    System.out.println("===== PARSING STARTED =====");
    Document doc = parseXml(asString);
    System.out.println("===== PARSING ENDED =====");
  }

  public static void main(String[] args)
      throws Exception {
    {
      System.out.println("********** TEST 1");
      test("<html>test</html>".getBytes("UTF-8"));
    }

    {
      System.out.println("********** TEST 2");
      URL url = new URL("http://validator.w3.org/");
      URLConnection connection = url.openConnection();
      InputStream is = connection.getInputStream();
      byte[] data = streamToByteArray(is);
      System.out.println("===== DOWNLOAD FINISHED =====");

      test(data);
    }
  }

}

输出

********** TEST 1
===== PARSING STARTED =====
===== PARSING ENDED =====
********** TEST 2
===== DOWNLOAD FINISHED =====
===== PARSING STARTED =====

[here it blocks]

4 个答案:

答案 0 :(得分:2)

查看您下载的网页,其中包含更多http:个网址。

这是开始:

<!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">

我可以想象XML解析器试图在这里下载引用的DTD,以便能够验证XML内容。

尝试在简单文档中添加序言,或尝试将其从复杂文档中删除,以查看是否会发生变化。

将解析器切换为非验证,并查看是否有帮助。 (或者,有一些选项可以配置解析器的行为方式 - 例如setURIResolver看起来很好。)

答案 1 :(得分:2)

W3C在过去几个月内开始阻止对XHTML DTD等常见DTD的请求 - 他们无法应对所产生的流量。如果您没有使用缓存DTD的代理服务器,则需要使用EntityResolver或目录将引用重定向到本地副本。

答案 2 :(得分:1)

解决方案:为自定义EntityResolver预取(或更好:使用离线存储)DTD。

如果需要,没有使用外部XML实体(例如&nbsp;),则可以返回空的InputSource,请参阅内部枚举。否则,可以使用准备好的DTD URI -> bytearray映射来防止在线下载DTD。

import java.io.*;
import java.util.*;
import javax.annotation.*;
import org.xml.sax.*;

public final class PrefetchedEntityResolver
    implements EntityResolver {

  /**
  * NOTE: {@see #RETURN_NULL} seems to cause default behavior
  * (which is: downloading the DTD);
  * use {@see #RETURN_EMPTY_DATA} to ensure "offline" behavior
  * (which could lead to entity parsing errors).
  */
  public static enum NoMatchBehavior {

    THROW_EXCEPTION, RETURN_NULL, RETURN_EMPTY_DATA;
  }

  private final SortedMap<String, byte[]> prefetched;
  private final NoMatchBehavior noMatchBehavior;

  public PrefetchedEntityResolver(NoMatchBehavior noMatchBehavior,
      @Nullable SortedMap<String, byte[]> prefetched) {
    this.noMatchBehavior = noMatchBehavior;
    this.prefetched = new TreeMap<>(prefetched == null
        ? Collections.<String, byte[]>emptyMap() : prefetched);
  }

  @Override
  public InputSource resolveEntity(String name, String uri)
      throws SAXException, IOException {
    byte[] data = prefetched.get(uri);
    if (data == null) {
      switch (noMatchBehavior) {
      case RETURN_NULL:
        return null;
      case RETURN_EMPTY_DATA:
        return new InputSource(new ByteArrayInputStream(new byte[]{}));
      case THROW_EXCEPTION:
        throw new SAXException("no prefetched DTD found for: " + uri);
      default:
        throw new Error("unsupported: " + noMatchBehavior.toString());
      }
    }

    return new InputSource(new ByteArrayInputStream(data));
  }

}

用法

public static Document parseXml(byte[] data)
    throws Exception {
  DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
  df.setValidating(false);
  df.setXIncludeAware(false);
  df.setCoalescing(false);
  df.setExpandEntityReferences(false);

  DocumentBuilder b = df.newDocumentBuilder();
  b.setEntityResolver(new PrefetchedEntityResolver(
      PrefetchedEntityResolver.NoMatchBehavior.RETURN_EMPTY_DATA,
      /* pass some prepared SortedMap<String, byte[]> */));
  ByteArrayInputStream bais = new ByteArrayInputStream(data);
  return b.parse(bais);
}

答案 3 :(得分:0)

也许你的“count == -1”条件需要变成“count&lt; = 0”?