【问题标题】:Parsing xml with DOM, DOCTYPE gets erased用 DOM 解析 xml,DOCTYPE 被删除
【发布时间】:2011-07-09 19:42:34
【问题描述】:

为什么 dom with java 在编辑 xml 时会删除 doctype?

得到这个 xml 文件:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE map[ <!ELEMENT map (station*) >
                <!ATTLIST station  id   ID    #REQUIRED> ]>
<favoris>
<station id="5">test1</station>
<station id="6">test1</station>
<station id="8">test1</station>
</favoris> 

我的功能很基础:

public static void EditStationName(int id, InputStream is, String path, String name) throws ParserConfigurationException, SAXException, IOException, TransformerFactoryConfigurationError, TransformerException{
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

    DocumentBuilder builder = factory.newDocumentBuilder();
    Document dom = builder.parse(is);

    Element e = dom. getElementById(String.valueOf(id));
    e.setTextContent(name);
    // Write the DOM document to the file
    Transformer xformer = TransformerFactory.newInstance().newTransformer();
    FileOutputStream fos = new FileOutputStream(path);
    Result result = new StreamResult(fos);  
    Source source = new DOMSource(dom);


        xformer.setOutputProperty(
                OutputKeys.STANDALONE,"yes"     
                );

    xformer.transform(source, result);
}

它正在工作,但文档类型被删除了!我刚刚得到了整个文档,但没有 doctype 部分,这对我来说很重要,因为它允许我通过 id 检索! 我们如何保留文档类型?为什么要删除它? 我尝试了许多使用 outputkeys 或 omImpl.createDocumentType 的解决方案,但这些都不起作用......

谢谢!

【问题讨论】:

  • 我很惊讶你得到了什么;您的 XML 无效。
  • 两件事:1)您的文档类型(地图)与您的根元素(收藏夹)不匹配。 2) 未声明元素“站”。您应该为站添加一个元素声明,然后将“favoris”更改为“map”(或更改文档类型和元素声明)。
  • 对不起,你能写在这里吗?因为我对文档类型的东西完全陌生... :=)
  • 可能是这样的? ]>

标签: java xml dom doctype


【解决方案1】:

您的输入 XML 无效。那应该是:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE favoris [
    <!ELEMENT favoris (station)+>
    <!ELEMENT station (#PCDATA)>
    <!ATTLIST station id ID #REQUIRED>
]>
<favoris>
    <station id="i5">test1</station>
    <station id="i6">test1</station>
    <station id="i8">test1</station>
</favoris>

由于@DevNull 写的完全有效,你不能写&lt;station id="5"&gt;test1&lt;/station&gt;(但是对于Java,即使有这个问题它仍然可以工作)。


DOCTYPE 在输出 XML 文档中被删除:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<favoris>
    <station id="i5">new value</station>
    <station id="i6">test1</station>
    <station id="i8">test1</station>
</favoris>

我还没有找到缺少 DTD 的解决方案,但作为解决方法,您可以设置外部 DTD:

xformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "favoris.dtd");

结果(示例)文档:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE favoris SYSTEM "favoris.dtd">
<favoris>
    <station id="i5">new value</station>
    <station id="i6">test1</station>
    <station id="i8">test1</station>
</favoris>

编辑:

我认为不可能使用Transformer 类保存内联DTD(参见here)。如果不能使用外部 DTD 引用,则可以使用 DOM Level 3 LSSerializer 类来代替:

DOMImplementationLS domImplementationLS =
    (DOMImplementationLS) dom.getImplementation().getFeature("LS","3.0");
LSOutput lsOutput = domImplementationLS.createLSOutput();
FileOutputStream outputStream = new FileOutputStream("output.xml");
lsOutput.setByteStream((OutputStream) outputStream);
LSSerializer lsSerializer = domImplementationLS.createLSSerializer();
lsSerializer.write(dom, lsOutput);
outputStream.close();

带有所需 DTD 的输出(我看不到使用 LSSerializer 添加 standalone="yes" 的任何选项...):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE favoris [<!ELEMENT favoris (station)+>
<!ELEMENT station (#PCDATA)>
<!ATTLIST station id ID #REQUIRED>
]>
<favoris>
    <station id="i5">new value</station>
    <station id="i6">test1</station>
    <station id="i8">test1</station>
</favoris> 

另一种方法是使用 Apache Xerces2-J XMLSerializer 类:

import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
...

XMLSerializer serializer = new XMLSerializer();
serializer.setOutputCharStream(new java.io.FileWriter("output.xml"));
OutputFormat format = new OutputFormat();
format.setStandalone(true);
serializer.setOutputFormat(format);
serializer.serialize(dom);

结果:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE favoris [<!ELEMENT favoris (station)+>
<!ELEMENT station (#PCDATA)>
<!ATTLIST station id ID #REQUIRED>
]>
<favoris>
    <station id="i5">new value</station>
    <station id="i6">test1</station>
    <station id="i8">test1</station>
</favoris>

【讨论】:

    【解决方案2】:

    (此回复在某种程度上只是对@Grzegorz Szpetkowski 的回答的补充,为什么它有效)

    您丢失了 doctype 定义,因为您使用了产生 XSL 转换的 Transform 类。 XSLT 树模型中没有DOCTYPE 声明或docytype 定义对象/节点。当解析器将文档移交给 XSLT 处理器时,文档类型信息会丢失,因此无法保留或复制。 XSLT 对输出树的序列化提供了一些控制,包括添加带有公共或系统标识符的&lt;!DOCTYPE ... &gt; 声明。这些标识符的值需要事先知道,并且不能从输入树中读取。也不支持创建或保留嵌入式 DTD 或实体声明(尽管解决此障碍的一种解决方法是将其输出为带有 disable-output-escaping="yes" 的文本)。

    为了保留 DTD,您需要使用 XML 序列化程序而不是 XSL 转换来输出文档,就像 Grzegorz 已经建议的那样。

    【讨论】:

    • 感谢您的准确解释!这现在很清楚为什么它不可能......因为这是一个 android 应用程序,我不能真正使用所有这些 wordarounds。所以......我手动将我的 dom 转换为字符串,首先将 doctype 附加到我的 stringbuilder ! :/谢谢!
    【解决方案3】:

    @Grzegorz Szpetkowski 有一个使用外部 DTD 的好主意。但是,如果您保留这些 station/@id 值,XML 仍然无效。

    “ID”类型的任何属性都不能有以数字开头的值。您必须在其中添加一些内容,例如站的“s”:

    <!DOCTYPE favoris [
    <!ELEMENT favoris (station*)      > 
    <!ELEMENT station (#PCDATA)       > 
    <!ATTLIST station 
              id       ID   #REQUIRED > 
    ]>
    <favoris>
      <station id="s5">test1</station>
      <station id="s6">test1</station>
      <station id="s8">test1</station>
    </favoris>
    

    【讨论】:

    • 你说得非常对,我忘记了这条规则 :) 但是即使有这个问题,输出 XML 文档也有使用 LSSerializer 类而不是 Transformer 方法的内联 DTD。
    【解决方案4】:

    我遇到了几乎相同的问题,发现 this 可以与转换一起使用。它是有限的,因为它只允许引用 dtd,如果文档的 doctype 可以变化,则需要一些工作。不过在我的情况下就足够了,我只需要在转换后对 xhtml doctype 进行硬编码。

    xformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "publicId");
    xformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "systemId");
    

    【讨论】:

      猜你喜欢
      • 2014-05-19
      • 2010-12-25
      • 1970-01-01
      • 1970-01-01
      • 2011-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多