【问题标题】:JAXB: How to ignore namespace during unmarshalling XML document?JAXB:如何在解组 XML 文档期间忽略命名空间?
【发布时间】:2010-09-21 14:34:28
【问题描述】:

我的架构指定了一个命名空间,但文档没有。在 JAXB 解组(XML -> 对象)期间忽略命名空间的最简单方法是什么?

换句话说,我有

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

而不是,

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

【问题讨论】:

  • 我的问题实际上是相反的——我有一些带有xmlns 属性的文档(在一个或多个元素上),而有些则没有。 @lunicon 的解决方案让我可以同时阅读这两种风格。

标签: java xml xml-serialization jaxb


【解决方案1】:

如果您想在解析期间将一个命名空间替换为另一个命名空间,这只是对 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);

【讨论】:

    【解决方案2】:

    我相信您必须将add the namespace 添加到您的xml 文档中,例如使用SAX filter

    这意味着:

    • 使用新类定义 ContentHandler 接口,该类将在 JAXB 获取 SAX 事件之前拦截它们。
    • 定义一个将设置内容处理程序的 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
        }
    }
    

    【讨论】:

    • 这篇文章为什么会有垃圾广告链接?
    • @TomWolk 对不起,我已经恢复了正确的链接(使用 web.archive.org)。当我在 7 年前写答案时,请考虑到此链接 不是 垃圾邮件广告 ;)
    • @Macilias 我这边没有更新。如果您发现任何更新,请随时更新此答案。
    • 好的,也许并不完全过时,但我错过了 NamespaceFilterXMLReader。实际上 Kristofer 的高分帖子提供了一个
    【解决方案3】:

    在我的情况下,我有很多命名空间,经过一些调试后,我找到了另一个解决方案,只是更改了 NamespaceFitler 类。对于我的情况(只是解组),这项工作很好。

     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;
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      我在使用 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);
      

      【讨论】:

      • 当我注意到您的解决方案时,我正要尝试实施 Kristofer 的解决方案,它非常简单并且对我有用,谢谢!但是,它仍然太复杂了,为什么我们必须这样做? JAXB 应该为这种常见情况提供一个内置的解决方案,例如属性设置。
      • 另外别忘了关闭FileInputStream :)
      • 这并没有忽略 package.info 中包含的命名空间。因此,让 getNamespaceURI 方法返回 package.info 的内容。在这种情况下,XMLReaderWithoutNamespace 应该改为 XMLReaderWithNamespaceInMyPackageDotInfo
      【解决方案5】:

      这是 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);
      

      要使用此过滤器从 JAXB 对象输出 XML,请查看以下代码。

      //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);
      

      这有望帮助某人,因为我花了一天时间做这件事,几乎放弃了两次;)

      【讨论】:

      • 此解决方案是否适用于在整个文档中使用多个命名空间的多个嵌套 XML 对象?我尝试在这种情况下使用此示例,发现虽然它能够删除 XML 文档中前两个级别(根元素和根的子级)的命名空间,但它似乎没有过滤掉除此之外的命名空间.为了解组这样一个 XML 文档,我必须对根元素的孙子元素及以下元素使用命名空间声明。
      • 如果您愿意分享改进后的过滤器,我相信人们也希望看到这一点...
      • 为什么 jaxb 没有给你一个更好的错误信息并且需要这些体操,这超出了我的理解。这是一个非常普遍的问题,几乎每个人都会面临!
      • 非常感谢!就像一个魅力......有点荒谬,所有这些都只是为了忽略供应商文件中已失效的命名空间:-)
      • 这很好用,但如果您只想删除命名空间,请尝试使用带有 setNamespaceAware(false) 的 SAXParserFactory 的 Jaxb ignore the namespace on unmarshalling 中的选项 3)
      【解决方案6】:

      在将 XML 文档提供给 JAXB 之前向其添加默认命名空间的另一种方法是使用 JDom

      1. 将 XML 解析为文档
      2. 遍历所有元素并设置命名空间
      3. 使用 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);
                  }
              }
          }
      

      【讨论】:

      • 唯一的问题是您必须将整个 XML 文件读入内存,这对于大量 XML 文件来说是不可行的。
      猜你喜欢
      • 2015-06-19
      • 2016-06-24
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 2011-12-29
      • 2011-12-05
      • 2015-02-18
      • 1970-01-01
      相关资源
      最近更新 更多