Разбор 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");