Разбор xml с DOM, DOCTYPE стирается

Каким образом dom с java стирает doctype при редактировании xml?

получил этот 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 стирается! и я просто получил весь документ, но без части doctype, что важно для меня, потому что это позволяет мне получить по id! как мы можем сохранить doctype? почему он его стирает? Я пробовал много решений с выходными ключами, например, или omImpl.createDocumentType, но ни один из них не работал...

Благодарю вас!

Ответы

Ответ 1

(Этот ответ в некотором роде отвечает только за дополнение к @Grzegorz Szpetkowski, почему он работает)

Вы теряете определение doctype, потому что используете класс Transform, который создает преобразование XSL. В дереве XSLT нет объявления DOCTYPE или объекта определения docytype/node. Когда синтаксический анализатор передает документ процессору XSLT, информация типа doctype теряется и поэтому не может быть сохранена или дублирована. XSLT предлагает некоторый контроль над сериализацией дерева вывода, включая добавление объявления <!DOCTYPE ... > с общедоступным или системным идентификатором. Значения для этих идентификаторов должны быть известны заранее и не могут быть прочитаны из дерева ввода. Создание или сохранение встроенных объявлений DTD или сущности также не поддерживается (хотя одним из способов обхода этого препятствия является вывод его в виде текста с помощью disable-output-escaping="yes").

Чтобы сохранить DTD, вам нужно вывести свой документ с помощью сериализатора XML вместо преобразования XSL, как уже указывал Гжегож.

Ответ 2

Ваш 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 написал, что он полностью действителен, вы не можете писать <station id="5">test1</station> (однако для 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>

EDIT:

Я не думаю, что можно сохранить встроенный DTD, используя Transformer class (vide здесь). Если вы не можете использовать внешнюю ссылку 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 (я не вижу никакой возможности добавить standalone="yes" с помощью LSSerializer...):

<?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>

Ответ 3

@Grzegorz Szpetkowski имеет хорошую идею с использованием внешнего DTD. Тем не менее, XML по-прежнему недействителен, если вы сохраняете эти значения станции /@id.

Любой атрибут с типом "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>

Ответ 4

У меня была почти такая же проблема, и я нашел этот, который работает с преобразованием. Он ограничен, поскольку он позволяет ссылаться только на dtd, и это потребует некоторой работы, если документ документа может отличаться. В моем случае этого было достаточно, мне нужно было только жестко кодировать doctype xhtml после преобразования.

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