簡體   English   中英

使用單個文本元素而不是 XmlElement 的 JAXB 解析包裝器

[英]JAXB Parsing Wrapper With Single Text Element instead of XmlElement

今天剛開始使用 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,這里是一個簡單的程序,如果它被包裝在color標簽中,它將正確解析“藍色”。 目前該程序返回一個空的顏色列表,無法拾取“藍色”。

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

非常感謝您的幫助。

首先,重要的是我要聲明這不是我正在尋找的答案,但它是一個臨時/替代解決方案,直到找到 JAXB 解決方案。 我目前被迫使用此解決方案,直到可以找到 JAXB 解決方案。

我提供這個替代解決方案是因為其他人可能會發現它很有用,因為它提供了使用正則表達式模式來操作流並糾正阻止原始 XML 被正確解析的潛在問題的能力。 這是通過使用 FilterReader 來完成的。

簡單回顧一下,XML 數據包含由 colors 包裹的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 緩沖區中。 這是通過檢查<的開始字符,然后確保加載另外兩個>字符以滿足給定模式的最低需求來實現的。 那是什么意思? 如果您嘗試將此代碼重用於其他目的,您可能必須修改預取器以確保您在內存中獲得足夠的數據供模式匹配器成功使用。

我能夠通過添加另一個字段private String color;來找到解決方法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>();
}

小部件類:

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

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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM