JAXB使用单个文本元素而不是XmlElement解析包装器

时间:2016-12-08 23:47:18

标签: java xml jaxb

今天就开始使用JAXB了,当只有一个值时,我仍然坚持使用数据元素列表的奇怪表示。请注意,对于colors的单个值,它更多地被视为元素而不是列表,并且不包含在color标记中。数据来自外部源,我无法控制格式。

JAXB如何处理colors的表示?

<?xml version="1.0" encoding="utf-8"?>
<widgets>
    <widget>
        <name>SingleValue</name>
        <colors>Blue</colors>
    </widget>
    <widget>
        <name>ListValues</name>
        <colors>
            <color>Red</color>
            <color>Blue</color>
        </colors>
   </widget>
</widgets>

我尝试过使用@XmlElementWrapper@XmlElement@XmlAnyElements@XmlElementRef(s)@XmlMixed的组合进行各种尝试。我甚至创建了一个颜色类,并尝试多次映射到数组和字符串,没有运气;它们可以单独工作,但不能同时使用。

使用上面的示例XML,这是一个简单的程序,可以解析&#34; Blue&#34;正确的,如果它包含在color标签中。目前,该程序返回一个空的颜色列表,无法拾取&#34;蓝色&#34;。

@XmlRootElement(name = "widgets")
@XmlAccessorOrder(XmlAccessOrder.UNDEFINED)
public class Widgets {
    private List<Widget> widgets = new ArrayList<Widget>();
    public static void main(String[] args ) {
        File f = new File("C:\\aersmine\\AERS_KDR_Data", "widgets.xml");
        try {
            Widgets widgets = Widgets.load( f );

            for ( Widget widget : widgets.widgets ) {
                StringBuilder sb = new StringBuilder();
                for ( String color : widget.getColors() ) {
                    if ( sb.length() > 0 )
                        sb.append( ", " );
                    sb.append(color);
                }
                System.out.println( "Widget " + widget.getName() + "   Colors: " + sb.toString());
            }
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    public static Widgets load(File file) 
            throws JAXBException, IOException {
        FileInputStream is = new FileInputStream(file);
        try {
            JAXBContext ctx = JAXBContext.newInstance(Widgets.class);
            Unmarshaller u = ctx.createUnmarshaller();
            return (Widgets) u.unmarshal(is);
        }
        finally {
            is.close();
        }
    }
    @XmlElement(name="widget")
    public List<Widget> getWidgets() {
        return widgets;
    }
    public void setWidgets( List<Widget> widgets ) {
        this.widgets = widgets;
    }
}

public class Widget {
    public String n;
    public List<String> cl = new ArrayList<String>();

    @XmlElement(name="name")
    public String getName() {
        return n;
    }
    public void setName( String name ) {
        this.n = name;
    }

    @XmlElementWrapper(name="colors")
    @XmlElement(name="color")
    public List<String> getColors() {
        return cl;
    }
    public void setColors( List<String> colors ) {
        this.cl = colors;
    }
}

非常感谢你的帮助。

2 个答案:

答案 0 :(得分:0)

首先,重要的是要说明这不是我正在寻找的答案,但它是一个临时/替代解决方案,直到找到JAXB解决方案。我现在被迫使用这个解决方案,直到找到JAXB解决方案。

我提供了这种替代解决方案,因为其他人可能会发现它很有用,因为它提供了使用正则表达式模式来操作流并纠正阻止正确解析原始XML的底层问题的能力。这是通过使用FilterReader实现的。

简单回顾一下,XML数据包含colors包裹的颜色列表。每个颜色都在列表中按预期标记为color。问题是当有单一颜色值时;该值未包含在color中,因此无法解析。

良好的颜色列表示例:

<colors>
    <color>Red</color>
    <color>Blue</color>
</colors>

单一颜色不好的例子:

<colors>Blue</colors>

此解决方案将使用正则表达式模式<colors>([^<>]+?)\s*<\/colors>来标识不正确的XML列表。然后,它将使用替换字符串值<color>|</color>,将前缀和后缀应用于在管道字符上找到的group(1)对象分割。

坏单色的校正结果将如下所示,因此JAXB解组会将其拉入:

<colors><color>Blue</color></colors>

实现:

使用原始请求中的上述代码,将public static Widgets load函数替换为此函数。请注意,除了添加新WidgetFilterReader之外,此版本加载器的另一个重要更改是使用FileReader

    public static Widgets load(File file) 
            throws JAXBException, IOException
    {
        Reader reader =
            new WidgetFilterReader( 
                     "<colors>([^<>]+?)\\s*<\\/colors>", "<color>|</color>",
                new FileReader(file));
        try
        {
            JAXBContext ctx = JAXBContext.newInstance(Widgets.class);
            Unmarshaller u = ctx.createUnmarshaller();
            return (Widgets) u.unmarshal(reader);
        }
        finally
        {
            reader.close();
        }
    }

然后添加此类,即FilterReader实现:

public class WidgetFilterReader
    extends FilterReader
{
    private StringBuilder sb = new StringBuilder();

    @SuppressWarnings( "unused" )
    private final String search;
    private final String replace;
    private Pattern pattern;
    private static final String EOF = "\uFFEE";  // half-width white circle - Used as to place holder and token

    /**
     * 
     * @param search A regular expression to build the pattern.  Example: "<colors>([^<>]+?)\\s*<\\/colors>"
     * @param replace A String value with up to two parts to prefix and suffix the found group(1) object, separated by a pipe: ie |.  
     *          Example: "<color>*</color>"
     * @param in
     */
    protected WidgetFilterReader( String search, String replace, Reader in ) {
        super( in );
        this.search = search;
        this.replace = replace;
        this.pattern = Pattern.compile(search);
    }

    @Override
    public int read()
            throws IOException {
        int read = ingest();
        return read;
    }

    private int ingest() throws IOException
    {
        if (sb.length() == 0) {
            int c = super.read();
            if ( c < 0 )
                return c;
            sb.append( (char) c );
        }

        if ( sb.length() > 0 && sb.charAt(0) == '<' ) {
            int count = 0;
            for ( int i = 0; i < sb.length(); i++ ) {
                if ( sb.charAt( i ) == '>' )
                    count++;
            }
            int c2;
            while ((c2 = super.read()) >= 0 && count < 2) {
                sb.append( (char) c2 );
                if (c2 == '>')
                    count++;
            }
            if ( c2 < 0 )
                sb.append( EOF );
            else
                sb.append( (char) c2 );

            Matcher m = pattern.matcher( sb.toString() );
            if ( m.find(0) ) {
                String grp = m.group(1);
                int i = sb.indexOf(grp);
                if ( i >= 0 ) {
                    int j = i + grp.length();
                    String[] r = replace.split( "\\|" );
                    sb.replace(i, j, (r.length > 0 ? r[0] : "") + grp + (r.length > 1 ? r[1] : ""));
                }
            }
        }

        int x = sb.charAt(0);
        sb.deleteCharAt(0);

        if ( x == EOF.charAt(0) )
            return -1;
        return x;
    }

    @Override
    public int read( char[] cbuf, int off, int len )
            throws IOException {
        int c;
        int read = 0;

        while (read < len && (c = ingest()) >= 0 ) {
            cbuf[off + read] = (char) c;
            read++;
        }
        if (read == 0)
            read = -1;
        return read;
    }
}

概述如何运作:

基本上这个类使用StringBuilder作为缓冲区,而它在前面读取搜索提供的模式。在StringBuilder缓冲区中找到模式后,将修改StringBuilder以包含更正的数据。这是有效的,因为流总是被读取并添加到内部缓冲区,然后在上游消耗时从该缓冲区中拉出。这确保了只能在上游消耗这些字符之前加载最少量的字符才能找到模式。

由于在搜索模式时可能遇到EndOfFile,因此需要有一个令牌插入缓冲区,以便在上游消费者到达该点时返回正确的EOF。因此使用了一个用于EOF令牌的相当模糊的unicode字符。如果可能恰好在您的源数据中,那么应该使用其他东西。

我还应该注意,尽管正则表达式模式正被传递到此FilterReader,但预取足够数据以执行对目标数据的有效搜索的代码依赖于正在进行的模式的特定属性。用过的。它确保在尝试find(0)之前,已将足够的数据加载到StringBuilder缓冲区中。这是通过检查<的开始字符然后确保加载另外两个>字符以满足给定模式的最小需求来实现的。那是什么意思?如果您尝试将此代码重用于其他目的,则可能必须修改预取程序以确保在内存中获取足够的数据以使模式匹配器成功使用。

答案 1 :(得分:0)

通过向 private String color; 类添加另一个字段 Widget,我找到了解决方法。有了这个,如果有一个列表,那么它会填充 private List<String> colors,如果只有 colors 有值,那么它会填充 private String color;

Widgets.class:

@Data
@NoArgsConstructor
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "widgets")
public class Widgets {
    @XmlElement(name="widget")
    private List<Widget> widgets = new ArrayList<Widget>();
}

Widget.class:

@XmlAccessorType(XmlAccessType.NONE)
@Data
public class Widget {

    @XmlElement(name = "name")
    private String name;

    @XmlElementWrapper(name = "colors")
    @XmlElement(name = "color")
    private List<String> colors = new ArrayList<>();

    @XmlElement(name = "colors")
    private String color = null;

    //If the invalid Color needs to be converted to Proper XML with Colors list then add this method
    public void afterUnmarshal(Unmarshaller m, Object parent) {
        if (color.matches(".*[a-zA-Z]+.*")) {
            colors.add(color);
        }
        color = null;
    }
}

JaxbExampleMain.class:

public class JaxbExampleMain {
    public static void main(String[] args) throws JAXBException, XMLStreamException {
        final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("colors.xml");
        final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
        final Unmarshaller unmarshaller = JAXBContext.newInstance(Widgets.class).createUnmarshaller();
        final Widgets widgets = unmarshaller.unmarshal(xmlStreamReader, Widgets.class).getValue();
        System.out.println(widgets.toString());

        Marshaller marshaller = JAXBContext.newInstance(Widgets.class).createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.marshal(widgets, System.out);
    }
}

当您尝试解组和编组问题中提供的 XML 时,这将产生以下输出:

Widgets(widgets=[Widget(name=SingleValue, colors=[], color=Blue), Widget(name=ListValues, colors=[Red, Blue], color=
        )])
<widgets>
   <widget>
      <name>SingleValue</name>
      <colors>Blue</colors>
   </widget>
   <widget>
      <name>ListValues</name>
      <colors>
         <color>Red</color>
         <color>Blue</color>
        </colors>
   </widget>
</widgets>