JAXB ~ Динамический синтаксический анализ нескольких пространств имен
Я пытаюсь создать демаршаллер, который будет работать со следующими XML-файлами:
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd
xml:lang="en">
[...]
</REQ-IF>
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:configuration="http://eclipse.org/rmf/pror/toolextensions/1.0"
xmlns:id="http://pror.org/presentation/id"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
[...]
</REQ-IF>
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:doors="http://www.ibm.com/rdm/doors/REQIF/xmlns/1.0"
xmlns:reqif="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:reqif-common="http://www.prostep.org/reqif"
xmlns:reqif-xhtml="http://www.w3.org/1999/xhtml"
xmlns:rm="http://www.ibm.com/rm"
xmlns:rm-reqif="http://www.ibm.com/rm/reqif"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
[...]
</REQ-IF>
Все эти файлы имеют одинаковую структуру и основаны на одном и том же пространстве имен верхнего уровня, но также содержат множество пространств имен подуровней переменных и другие "вещи" (которые, как я понимаю, должны быть атрибутами, но не являются), которые требуют для сохранения в системе.
Пока что мне удалось добраться до точки, где сэкономлено столько:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd">
[...]
</REQ-IF>
однако мой предполагаемый результат будет выглядеть так:
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd
xml:lang="en">
[...]
</REQ-IF>
Таким образом, пространство имен верхнего уровня сохраняется, но пространства имен подуровня и другие "вещи" теряются в процессе импорта / экспорта. Это плохо.
Как я могу сохранить эти другие подпространства имен и другие "вещи", учитывая, что они генерируются динамически?
По сути, я хочу сказать: "Сохраните все эти дополнительные атрибуты любым способом при синтаксическом анализе XML, и как только вы снова экспортируете XML, перезапишите их точно так, как они были".
2 ответа
Если ваш основной вариант использования - чтение файлов ReqIF, учтите, что существует реализация сериализатора ReqIF (de) с открытым исходным кодом по адресу https://www.eclipse.org/rmf/
К сожалению, кажется, что JAXB сам по себе не может динамически управлять всеми префиксами пространства имен, и вам нужно объединить его с другим механизмом синтаксического анализа.
Я бы попробовал реализовать что-то вроде этого (только примерная реализация, подробности ниже):
public class MyXmlHandler {
XMLInputFactory xif = XMLInputFactory.newInstance();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLEventFactory xef = XMLEventFactory.newInstance();
/**
* Retrieve XMLEvent for root element
*/
public StartElement getStartElement(String source) throws XMLStreamException {
XMLEvent event;
XMLEventReader reader = xif.createXMLEventReader(new StringReader(source));
while (reader.hasNext()) {
event = reader.nextEvent();
if (event.isStartElement()) {
return event.asStartElement();
}
// alternativery you can retrieve here also QNames for first level child elements
// and return all this data in some synthetic wrapper class
}
return null; // alternatively throw an exception
}
/**
* Write root element, than some content from JAXB elements, than end element
*/
public void write(
Marshaller marshaller,
Writer writer,
StartElement root,
List<JAXBElement> elements
) throws JAXBException, XMLStreamException {
XMLEventWriter xew = xof.createXMLEventWriter(writer);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
xew.add(root);
for(JAXBElement element : elements) {
marshaller.marshal(element, xew);
}
xew.add(xef.createEndElement(root.getName(), root.getNamespaces()));
xew.close();
}
}
И используйте это так:
// create JAXB context and unmarshaller
JAXBContext ctx = JAXBContext.newInstance(RootClass.class);
Unmarshaller unmarshaller = ctx.createUnmarshaller();
// unmarshall XML
JAXBElement<RootClass> element = unmarshaller.unmarshal(source, RootClass.class);
RootClass rootValue = element.getValue();
// extract root element data from XML
StartElement root = handler.getStartElement(data);
// perform some business logic
// create marshaller
Marshaller marshaller = ctx.createMarshaller();
// create list of JAXBElements for root children
List<JAXBElement> elements = new ArrayList<>();
QName qname = ... // construct qualified name or retrieve it from saved structure
// very schematic, names of the child elements depend on your implementation
elements.add(new JAXBElement(qname , rootValue.getChild().getClass(), rootValue.getChild()));
handler.write(marshaller, writer, root, elements);
При сохранении данных корневого элемента вы также можете сохранить QNames для его дочерних элементов в некотором классе-оболочке. Пока я вижу, что структура REQ-IF содержит заголовок, основной контент и расширения инструментов. Вы можете сохранить QNames для всех из них, а затем использовать их для построения элементов JAXB в процессе маршалинга.