JAXB:如何在没有命名空间的情况下解组 XML

Posted

技术标签:

【中文标题】JAXB:如何在没有命名空间的情况下解组 XML【英文标题】:JAXB: How can I unmarshal XML without namespaces 【发布时间】:2011-11-03 07:31:30 【问题描述】:

我有一个 XML 文件:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<object>
   <str>the type</str>
   <bool type="boolean">true</bool>        
</object>

我想将它解组为下面类的对象

@XmlRootElement(name="object")
public class Spec  
   public String str;
   public Object bool;


我该怎么做?除非我指定命名空间(见下文),否则它不起作用。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<object>
   <str>the type</str>
   <bool xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
       xmlns:xs="http://www.w3.org/2001/XMLSchema"  
       xsi:type="xs:boolean">true</bool>        
</object>

【问题讨论】:

【参考方案1】:

谢谢大家,这里分享了适用于我的代码的解决方案

我尝试使它成为通用的每个命名空间都包含“:”,如果任何标签有“:”,我会编写代码,它将从 xml 中删除。

这用于在使用 jaxb 解组期间跳过命名空间。

public class NamespaceFilter 

    private NamespaceFilter() 

    

    private static final String COLON = ":";

    public static XMLReader nameSpaceFilter() throws SAXException 
        XMLReader xr = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) 
            private boolean skipNamespace;

            @Override
            public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException 
                if (qName.indexOf(COLON) > -1) 
                    skipNamespace = true;
                 else 
                    skipNamespace = false;
                    super.startElement("", localName, qName, atts);
                
            

            @Override
            public void endElement(String uri, String localName, String qName) throws SAXException 
                if (qName.indexOf(COLON) > -1) 
                    skipNamespace = true;
                 else 
                    skipNamespace = false;
                    super.endElement("", localName, qName);
                
            

            @Override
            public void characters(char[] ch, int start, int length) throws SAXException 
                if (!skipNamespace) 
                    super.characters(ch, start, length);
                
            
        ;
        return xr;
    


用于解组,

XMLReader xr = NamespaceFilter.nameSpaceFilter();
        Source src = new SAXSource(xr, new InputSource("filePath"));
        StringWriter sw = new StringWriter();
        Result res = new StreamResult(sw);
        TransformerFactory.newInstance().newTransformer().transform(src, res);
        JAXBContext jc = JAXBContext.newInstance(Tab.class);
        Unmarshaller u = jc.createUnmarshaller();
        String done = sw.getBuffer().toString();
        StringReader reader = new StringReader(done);
        Tab tab = (Tab) u.unmarshal(reader);

        System.out.println(tab);

【讨论】:

【参考方案2】:

基于 Blaise 的 cmets(感谢 Blaise!)和我的研究。 这是我的问题的解决方案。你同意 Blaise 的观点,还是有更好的方法?

package forum7184526;

import java.io.FileInputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.StreamReaderDelegate;

import org.eclipse.persistence.oxm.XMLConstants;

public class Demo 

public static void main(String[] args) throws Exception 
    XMLInputFactory xif = XMLInputFactory.newFactory();
    XMLStreamReader xsr = xif.createXMLStreamReader(new FileInputStream("input.xml"));
    xsr = new XsiTypeReader(xsr);

    JAXBContext jc = JAXBContext.newInstance(Spec.class);
    Unmarshaller unmarshaller = jc.createUnmarshaller();
    Spec spec = (Spec) unmarshaller.unmarshal(xsr);


private static class XsiTypeReader extends StreamReaderDelegate 

    public XsiTypeReader(XMLStreamReader reader) 
        super(reader);
    

    @Override
    public String getAttributeNamespace(int arg0) 
        if("type".equals(getAttributeLocalName(arg0))) 
            return "http://www.w3.org/2001/XMLSchema-instance";
        
        return super.getAttributeNamespace(arg0);
    

    @Override
    public String getAttributeValue(int arg0) 
      String n = getAttributeLocalName(arg0);
      if("type".equals(n)) 
         String v = super.getAttributeValue(arg0);
          return  "xs:"+ v;
      
      return super.getAttributeValue(arg0);
    
    @Override
    public NamespaceContext getNamespaceContext() 
      return new MyNamespaceContext(super.getNamespaceContext());
    


private static class MyNamespaceContext implements NamespaceContext 
  public NamespaceContext _context;
  public MyNamespaceContext(NamespaceContext c)
     _context = c;
  

  @Override
  public Iterator<?> getPrefixes(String namespaceURI) 
     return _context.getPrefixes(namespaceURI);
  

  @Override
  public String getPrefix(String namespaceURI) 
     return _context.getPrefix(namespaceURI);
  

  @Override
  public String getNamespaceURI(String prefix) 
     if("xs".equals(prefix)) 
        return  "http://www.w3.org/2001/XMLSchema";
     
     return _context.getNamespaceURI(prefix);
  


【讨论】:

【参考方案3】:

更简单的方法可能是使用unmarshalByDeclaredType,因为您已经知道要解组的类型。

通过使用

Unmarshaller.unmarshal(rootNode, MyType.class);

您不需要在 XML 中声明命名空间,因为您传入了已设置命名空间的 JAXBElement。

这也是完全合法的,因为您不需要在 XML 实例中引用命名空间,请参阅 http://www.w3.org/TR/xmlschema-0/#PO - 许多客户端以这种方式生成 XML。

终于搞定了。请注意,您必须删除架构中的任何自定义命名空间;这是工作示例代码:

架构:

<xsd:schema
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="customer">
   <xsd:complexType>
      <xsd:sequence minOccurs="1" maxOccurs="1">
         <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1" />
         <xsd:element name="phone" type="xsd:string" minOccurs="1" maxOccurs="1" />
      </xsd:sequence>
   </xsd:complexType>
</xsd:element>

XML:

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <name>Jane Doe</name>
   <phone>08154712</phone>
</customer>

JAXB 代码:

JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller u = jc.createUnmarshaller();
u.setSchema(schemaInputStream); // load your schema from File or any streamsource
Customer = u.unmarshal(new StreamSource(inputStream), clazz);  // pass in your XML as inputStream

【讨论】:

不确定这是否适用于本地元素具有命名空间 (elementFormDefault="qualified")) 的模式。好的,根元素的命名空间(或者,最好是全名)无关紧要。但这是真的吗对于子元素?我不这么认为。 似乎对我不起作用。我有&lt;xsd:schema targetNamespace="http://www.foobar.com" elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.foobar.com"&gt;JAXBException 被抛出。【参考方案4】:

更新

您可以通过引入一个中间层在typexsi:type 之间进行转换来实现这一点。下面是使用 StAX StreamReaderDelegate 为 JAXB 解组操作执行此操作的示例:

package forum7184526;

import java.io.FileInputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.StreamReaderDelegate;

import org.eclipse.persistence.oxm.XMLConstants;

public class Demo 

    public static void main(String[] args) throws Exception 
        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileInputStream("input.xml"));
        xsr = new XsiTypeReader(xsr);

        JAXBContext jc = JAXBContext.newInstance(Spec.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Spec spec = (Spec) unmarshaller.unmarshal(xsr);
    

    private static class XsiTypeReader extends StreamReaderDelegate 

        public XsiTypeReader(XMLStreamReader reader) 
            super(reader);
        

        @Override
        public String getAttributeNamespace(int arg0) 
            if("type".equals(getAttributeLocalName(arg0))) 
                return XMLConstants.SCHEMA_INSTANCE_URL;
            
            return super.getAttributeNamespace(arg0);
        

    


xsi:type 是一种模式机制,用于指定元素的真实类型(类似于 Java 中的强制转换)。如果您删除命名空间,您将更改文档的语义。

http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html

在EclipseLink JAXB (MOXy) 中,我们允许您使用@XmlDescriminatorNode@XmlDescrimatorValue 为域对象指定自己的继承指示符。我们目前不为数据类型属性提供这种类型的自定义:

http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-moxy-extension.html

【讨论】:

感谢您的回复,布莱斯。我很满意拥有命名空间。但是是否有可能在 XML 中没有命名空间(因为它将面向开发用户)但将该信息与 Java 类注释一起保存,以便当 jaxb 解析 XML 时,它会看到“类型”并知道“啊哈,它是根据 Java 类声明命名空间‘xsi’”。与“xs:boolean”相同。你明白我在说什么吗?还是仍然不可能?我需要这个,因为我们的开发用户将创建这样的 XML 声明,我们需要将它们解组为 Java。 @Andrey - 你可以完成这项工作,下面是你可以为 unmarshal 做的一种方法。 嗨,布莱斯。删除“type”属性的命名空间效果很好,非常感谢!!!,但我仍然如何删除“type”属性值的“xs”命名空间声明 - “xs:boolean”。我尝试覆盖一些 StreamReaderDelegate 方法,但没有成功。 @Andrey - 我的回答中的代码只是解决了 unmarshal 的情况。要从编组操作中删除命名空间,您可以为 XMLStreamWriter 创建一个包装器,该包装器拦截并删除相关的命名空间信息。这是我在做类似事情时给出的答案的链接:***.com/questions/5720501/… 我实际上仍在谈论解组。 (我也更正了我最初的问题。)基本上,您的建议有助于删除“type”属性的命名空间声明,但我无法弄清楚如何删除“type”属性值本身的命名空间声明。 "xs:boolean" --> "boolean"

以上是关于JAXB:如何在没有命名空间的情况下解组 XML的主要内容,如果未能解决你的问题,请参考以下文章

使用命名空间和前缀的 JAXB 解组

如果命名空间声明在 SOAP 信封上,如何使用 JAXB 解组 SOAP 响应?

Jaxb:在同一个包中解组具有多个命名空间的 xml

JAXB使用多命名空间解组

在不知道结构的情况下解组嵌套的 json

在将 XML 文件解组为对象后,如何让 JAXB 调用方法?