JAXB: как игнорировать пространство имен во время распаковки XML-документа?

В моей схеме указано пространство имен, а в документах — нет. Какой самый простой способ игнорировать пространство имен во время распаковки JAXB (XML -> объект)?

Другими словами, у меня есть

<foo><bar></bar></foo>

вместо,

<foo xmlns="http://tempuri.org/"><bar></bar></foo>

person Eugene Yokota    schedule 10.11.2008    source источник
comment
Моя проблема была на самом деле противоположной - у меня есть некоторые документы с атрибутами xmlns (для одного или нескольких элементов), а некоторые - без. Решение @lunicon позволяет мне читать оба стиля.   -  person Lambart    schedule 23.11.2016


Ответы (6)


Я считаю, что вы должны добавить пространство имен в ваш XML-документ, например, с использованием фильтр SAX.

Это означает:

  • Определите интерфейс ContentHandler с новым классом, который будет перехватывать события SAX до того, как JAXB сможет их получить.
  • Определите XMLReader, который установит обработчик содержимого

затем соедините их вместе:

public static Object unmarshallWithFilter(Unmarshaller unmarshaller,
java.io.File source) throws FileNotFoundException, JAXBException 
{
    FileReader fr = null;
    try {
        fr = new FileReader(source);
        XMLReader reader = new NamespaceFilterXMLReader();
        InputSource is = new InputSource(fr);
        SAXSource ss = new SAXSource(reader, is);
        return unmarshaller.unmarshal(ss);
    } catch (SAXException e) {
        //not technically a jaxb exception, but close enough
        throw new JAXBException(e);
    } catch (ParserConfigurationException e) {
        //not technically a jaxb exception, but close enough
        throw new JAXBException(e);
    } finally {
        FileUtil.close(fr); //replace with this some safe close method you have
    }
}
person VonC    schedule 10.11.2008
comment
Почему в этом посте спамные рекламные ссылки? - person TomWolk; 07.09.2015
comment
@TomWolk Извините, я восстановил правильную ссылку (с web.archive.org). Пожалуйста, учтите, что эта ссылка была не спам-рекламой, когда я писал ответ... 7 лет назад;) - person VonC; 07.09.2015
comment
@Macilias Нет обновлений с моей стороны. Если вы найдете какие-либо обновления, не стесняйтесь обновлять этот ответ. - person VonC; 04.01.2016
comment
хорошо, может быть, не совсем устарело, но мне не хватало NamespaceFilterXMLReader. На самом деле высоко оцененный пост Кристофера дает один - person Macilias; 04.01.2016

Вот расширение / редактирование решения VonCs на тот случай, если кто-то не хочет мучиться с внедрением собственного фильтра для этого. Он также показывает, как вывести элемент JAXB без присутствия пространства имен. Все это достигается с помощью фильтра SAX.

Реализация фильтра:

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import org.xml.sax.helpers.XMLFilterImpl;

public class NamespaceFilter extends XMLFilterImpl {

    private String usedNamespaceUri;
    private boolean addNamespace;

    //State variable
    private boolean addedNamespace = false;

    public NamespaceFilter(String namespaceUri,
            boolean addNamespace) {
        super();

        if (addNamespace)
            this.usedNamespaceUri = namespaceUri;
        else 
            this.usedNamespaceUri = "";
        this.addNamespace = addNamespace;
    }



    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        if (addNamespace) {
            startControlledPrefixMapping();
        }
    }



    @Override
    public void startElement(String arg0, String arg1, String arg2,
            Attributes arg3) throws SAXException {

        super.startElement(this.usedNamespaceUri, arg1, arg2, arg3);
    }

    @Override
    public void endElement(String arg0, String arg1, String arg2)
            throws SAXException {

        super.endElement(this.usedNamespaceUri, arg1, arg2);
    }

    @Override
    public void startPrefixMapping(String prefix, String url)
            throws SAXException {


        if (addNamespace) {
            this.startControlledPrefixMapping();
        } else {
            //Remove the namespace, i.e. don´t call startPrefixMapping for parent!
        }

    }

    private void startControlledPrefixMapping() throws SAXException {

        if (this.addNamespace && !this.addedNamespace) {
            //We should add namespace since it is set and has not yet been done.
            super.startPrefixMapping("", this.usedNamespaceUri);

            //Make sure we dont do it twice
            this.addedNamespace = true;
        }
    }

}

Этот фильтр предназначен для того, чтобы иметь возможность добавлять пространство имен, если оно отсутствует:

new NamespaceFilter("http://www.example.com/namespaceurl", true);

и удалить любое существующее пространство имен:

new NamespaceFilter(null, false);

Фильтр можно использовать во время парсинга следующим образом:

//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Unmarshaller u = jc.createUnmarshaller();

//Create an XMLReader to use with our filter
XMLReader reader = XMLReaderFactory.createXMLReader();

//Create the filter (to add namespace) and set the xmlReader as its parent.
NamespaceFilter inFilter = new NamespaceFilter("http://www.example.com/namespaceurl", true);
inFilter.setParent(reader);

//Prepare the input, in this case a java.io.File (output)
InputSource is = new InputSource(new FileInputStream(output));

//Create a SAXSource specifying the filter
SAXSource source = new SAXSource(inFilter, is);

//Do unmarshalling
Object myJaxbObject = u.unmarshal(source);

Чтобы использовать этот фильтр для вывода XML из объекта JAXB, взгляните на приведенный ниже код.

//Prepare JAXB objects
JAXBContext jc = JAXBContext.newInstance("jaxb.package");
Marshaller m = jc.createMarshaller();

//Define an output file
File output = new File("test.xml");

//Create a filter that will remove the xmlns attribute      
NamespaceFilter outFilter = new NamespaceFilter(null, false);

//Do some formatting, this is obviously optional and may effect performance
OutputFormat format = new OutputFormat();
format.setIndent(true);
format.setNewlines(true);

//Create a new org.dom4j.io.XMLWriter that will serve as the 
//ContentHandler for our filter.
XMLWriter writer = new XMLWriter(new FileOutputStream(output), format);

//Attach the writer to the filter       
outFilter.setContentHandler(writer);

//Tell JAXB to marshall to the filter which in turn will call the writer
m.marshal(myJaxbObject, outFilter);

Надеюсь, это поможет кому-то, так как я потратил на это день и дважды чуть не сдался;)

person Kristofer    schedule 27.01.2010
comment
Работает ли это решение с несколькими вложенными объектами XML, которые используют несколько пространств имен в документе? Я попытался использовать этот пример в таком сценарии и обнаружил, что, хотя он способен удалять пространства имен для первых двух уровней в XML-документе (корневой элемент и дочерние элементы корня), он не отфильтровывает пространства имен за пределами этого. . Чтобы распаковать такой XML-документ, мне пришлось использовать объявления пространства имен для внуков корневого элемента и ниже. - person Bionic_Geek; 05.12.2012
comment
Я уверен, что люди тоже захотят это увидеть, если вы захотите поделиться своим улучшенным фильтром... - person Kristofer; 07.12.2012
comment
Почему jaxb не дает вам лучшего сообщения об ошибке и вообще требует этой гимнастики, мне не понятно. Это невероятно распространенная проблема, с которой столкнется почти каждый! - person user798719; 30.05.2013
comment
Большое спасибо! Работает как шарм ... какой-то абсурд, что все это нужно только для того, чтобы игнорировать несуществующие пространства имен в файлах поставщиков :-) - person echen; 11.03.2015
comment
Это прекрасно работает, но если вы хотите удалить только пространство имен, попробуйте вариант 3) из Jaxb игнорировать пространство имен при десортировке, которое использует SAXParserFactory с setNamespaceAware(false) - person tanderson; 10.06.2019

У меня проблемы с кодировкой с решением XMLFilter, поэтому я заставил XMLStreamReader игнорировать пространства имен:

class XMLReaderWithoutNamespace extends StreamReaderDelegate {
    public XMLReaderWithoutNamespace(XMLStreamReader reader) {
      super(reader);
    }
    @Override
    public String getAttributeNamespace(int arg0) {
      return "";
    }
    @Override
    public String getNamespaceURI() {
      return "";
    }
}

InputStream is = new FileInputStream(name);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithoutNamespace xr = new XMLReaderWithoutNamespace(xsr);
Unmarshaller um = jc.createUnmarshaller();
Object res = um.unmarshal(xr);
person lunicon    schedule 24.06.2014
comment
Я собирался попробовать реализовать решение Кристофера, когда заметил ваше, которое было намного проще и помогло мне, спасибо! Это все еще слишком сложно, но зачем нам это делать? JAXB должен предложить встроенное решение, такое как настройка свойств для этой распространенной ситуации. - person Lambart; 23.11.2016
comment
Также не забудьте закрыть FileInputStream :) - person Lambart; 23.11.2016
comment
это не игнорировало пространство имен, содержащееся в package.info. поэтому вместо этого методы getNamespaceURI возвращают содержимое package.info. В этом случае XMLReaderWithoutNamespace вместо этого должен быть XMLReaderWithNamespaceInMyPackageDotInfo. - person Net Dawg; 25.07.2017

В моей ситуации у меня много пространств имен, и после некоторой отладки я нахожу другое решение, просто изменяя класс NamespaceFitler. Для моей ситуации (просто unmarshall) это работает нормально.

 import javax.xml.namespace.QName;
 import org.xml.sax.Attributes;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.XMLFilterImpl;
 import com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector;

 public class NamespaceFilter extends XMLFilterImpl {
    private SAXConnector saxConnector;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        if(saxConnector != null) {
            Collection<QName> expected = saxConnector.getContext().getCurrentExpectedElements();
            for(QName expectedQname : expected) {
                if(localName.equals(expectedQname.getLocalPart())) {
                    super.startElement(expectedQname.getNamespaceURI(), localName, qName, atts);
                    return;
                }
            }
        }
        super.startElement(uri, localName, qName, atts);
    }

    @Override
    public void setContentHandler(ContentHandler handler) {
        super.setContentHandler(handler);
        if(handler instanceof SAXConnector) {
            saxConnector = (SAXConnector) handler;
        }
    }
}
person Henrique    schedule 07.12.2012

Другой способ добавить пространство имен по умолчанию в XML-документ перед передачей его в JAXB — использовать JDom:

  1. Разобрать XML в документ
  2. Перебрать и установить пространство имен для всех элементов
  3. Unmarshall с помощью JDOMSource

Нравится:

public class XMLObjectFactory {
    private static Namespace DEFAULT_NS = Namespace.getNamespace("http://tempuri.org/");

    public static Object createObject(InputStream in) {
        try {
            SAXBuilder sb = new SAXBuilder(false);
            Document doc = sb.build(in);
            setNamespace(doc.getRootElement(), DEFAULT_NS, true);
            Source src = new JDOMSource(doc);
            JAXBContext context = JAXBContext.newInstance("org.tempuri");
            Unmarshaller unmarshaller = context.createUnmarshaller();
            JAXBElement root = unmarshaller.unmarshal(src);
            return root.getValue();
        } catch (Exception e) {
            throw new RuntimeException("Failed to create Object", e);
        }
    }

    private static void setNamespace(Element elem, Namespace ns, boolean recurse) {
        elem.setNamespace(ns);
        if (recurse) {
            for (Object o : elem.getChildren()) {
                setNamespace((Element) o, ns, recurse);
            }
        }
    }
person mafro    schedule 28.11.2008
comment
Единственная проблема с этим заключается в том, что вам нужно прочитать весь XML-файл в память, что невозможно с массивными XML-файлами. - person Brian; 24.07.2009

Это просто модификация ответа lunicon (https://stackoverflow.com/a/24387115/3519572), если вы хотите для замены одного пространства имен на другое во время синтаксического анализа. И если вы хотите увидеть, что именно происходит, просто раскомментируйте выходные строки и установите точку останова.

public class XMLReaderWithNamespaceCorrection extends StreamReaderDelegate {

    private final String wrongNamespace;
    private final String correctNamespace;

    public XMLReaderWithNamespaceCorrection(XMLStreamReader reader, String wrongNamespace, String correctNamespace) {
        super(reader);

        this.wrongNamespace = wrongNamespace;
        this.correctNamespace = correctNamespace;
    }

    @Override
    public String getAttributeNamespace(int arg0) {
//        System.out.println("--------------------------\n");
//        System.out.println("arg0: " + arg0);
//        System.out.println("getAttributeName: " + getAttributeName(arg0));
//        System.out.println("super.getAttributeNamespace: " + super.getAttributeNamespace(arg0));
//        System.out.println("getAttributeLocalName: " + getAttributeLocalName(arg0));
//        System.out.println("getAttributeType: " + getAttributeType(arg0));
//        System.out.println("getAttributeValue: " + getAttributeValue(arg0));
//        System.out.println("getAttributeValue(correctNamespace, LN):"
//                + getAttributeValue(correctNamespace, getAttributeLocalName(arg0)));
//        System.out.println("getAttributeValue(wrongNamespace, LN):"
//                + getAttributeValue(wrongNamespace, getAttributeLocalName(arg0)));

        String origNamespace = super.getAttributeNamespace(arg0);

        boolean replace = (((wrongNamespace == null) && (origNamespace == null))
                || ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
        return replace ? correctNamespace : origNamespace;
    }

    @Override
    public String getNamespaceURI() {
//        System.out.println("getNamespaceCount(): " + getNamespaceCount());
//        for (int i = 0; i < getNamespaceCount(); i++) {
//            System.out.println(i + ": " + getNamespacePrefix(i));
//        }
//
//        System.out.println("super.getNamespaceURI: " + super.getNamespaceURI());

        String origNamespace = super.getNamespaceURI();

        boolean replace = (((wrongNamespace == null) && (origNamespace == null))
                || ((wrongNamespace != null) && wrongNamespace.equals(origNamespace)));
        return replace ? correctNamespace : origNamespace;
    }
}

использование:

InputStream is = new FileInputStream(xmlFile);
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(is);
XMLReaderWithNamespaceCorrection xr =
    new XMLReaderWithNamespaceCorrection(xsr, "http://wrong.namespace.uri", "http://correct.namespace.uri");
rootJaxbElem = (JAXBElement<SqgRootType>) um.unmarshal(xr);
handleSchemaError(rootJaxbElem, pmRes);
person tomorrow    schedule 20.10.2020