Разбор XML с DOM, DOCTYPE стирается - PullRequest
14 голосов
/ 09 июля 2011

как получается, что Java с 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);
}

работает, но тип документа стирается! и я только что получил весь документ, но без части с доктайпом, что важно для меня, потому что это позволяет мне получать по id! как мы можем сохранить тип документа? почему это стирает? Я пробовал многие решения с помощью выходных ключей, например или omImpl.createDocumentType, но ни один из них не работал ...

спасибо!

Ответы [ 4 ]

11 голосов
/ 10 июля 2011

Ваш входной 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 класс (смотри здесь ). Если вы не можете использовать внешнюю ссылку DTD, вы можете вместо этого использовать класс DOM 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>
8 голосов
/ 10 июля 2011

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

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

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

2 голосов
/ 10 июля 2011

@ Гжегож Шпетковски имеет хорошую идею с использованием внешнего 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>
0 голосов
/ 05 августа 2011

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

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