16

次のシナリオ (のバリエーション) に対処する必要があります。私のモデルクラスは次のとおりです。

class Car {
    String brand;
    Engine engine;
}

abstract class Engine {
}

class V12Engine extends Engine {
    int horsePowers;
}

class V6Engine extends Engine {
    String fuelType;
}

そして、次の入力を逆シリアル化する必要があります (シリアル化サポート ATM は必要ありません)。

<list>

    <brand id="1">
        Volvo
    </brand>

    <car>
        <brand>BMW</brand>
        <v12engine horsePowers="300" />
    </car>

    <car>
        <brand refId="1" />
        <v6engine fuel="unleaded" />
    </car>

</list>

私が試したこと/問題:

XStream を使用してみましたが、次のようなタグを記述する必要があります。

<engine class="cars.V12Engine">
    <horsePowers>300</horsePowers>
</engine>

など ( <engine>-tagは必要ありません。-tagまたは<v6engine>-tagが必要です。<v12engine>

また、上記の brand-id で示されているように、識別子に基づいて「事前定義された」ブランドを参照できるようにする必要があります。(たとえばMap<Integer, String> predefinedBrands、逆シリアル化中に a を維持することによって)。XStream がそのようなシナリオに適しているかどうかはわかりません。

これは、プッシュまたはプル パーサー (SAX や StAX など) または DOM ライブラリを使用して「手動で」実行できることを認識しています。ただし、もう少し自動化することをお勧めします。理想的には、クラス ( new などEngine) を追加して、すぐに XML で使用できるようにする必要があります。(XStream は決して必須ではありません。最も洗練されたソリューションが報奨金を獲得します。)

4

3 に答える 3

11

JAXB ( javax.xml.bind) は、必要なすべてのことを実行できますが、一部のビットは他のビットよりも簡単です。簡単にするために、すべての XML ファイルに名前空間があると仮定します。名前空間がない場合はややこしくなりますが、StAX API を使用して回避できます。

<list xmlns="http://example.com/cars">

    <brand id="1">
        Volvo
    </brand>

    <car>
        <brand>BMW</brand>
        <v12engine horsePowers="300" />
    </car>

    <car>
        <brand refId="1" />
        <v6engine fuel="unleaded" />
    </car>

</list>

対応package-info.javaする

@XmlSchema(namespace = "http://example.com/cars",
           elementFormDefault = XmlNsForm.QUALIFIED)
package cars;
import javax.xml.bind.annotation.*;

要素名によるエンジンの種類

これは、次を使用して簡単です@XmlElementRef

package cars;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Car {
    String brand;
    @XmlElementRef
    Engine engine;
}

@XmlRootElement
abstract class Engine {
}

@XmlRootElement(name = "v12engine")
@XmlAccessorType(XmlAccessType.FIELD)
class V12Engine extends Engine {
    @XmlAttribute
    int horsePowers;
}

@XmlRootElement(name = "v6engine")
@XmlAccessorType(XmlAccessType.FIELD)
class V6Engine extends Engine {
    // override the default attribute name, which would be fuelType
    @XmlAttribute(name = "fuel")
    String fuelType;
}

さまざまなタイプのEngineすべてに注釈が付け@XmlRootElementられ、適切な要素名が付けられています。アンマーシャリング時に、XML で見つかった要素名を使用して、使用するEngineサブクラスを決定します。したがって、与えられた XML

<car xmlns="http://example.com/cars">
    <brand>BMW</brand>
    <v12engine horsePowers="300" />
</car>

およびアンマーシャリングコード

JAXBContext ctx = JAXBContext.newInstance(Car.class, V6Engine.class, V12Engine.class);
Unmarshaller um = ctx.createUnmarshaller();
Car c = (Car)um.unmarshal(new File("file.xml"));

assert "BMW".equals(c.brand);
assert c.engine instanceof V12Engine;
assert ((V12Engine)c.engine).horsePowers == 300;

新しいタイプの を追加するには、Engine単に新しいサブクラスを作成し、@XmlRootElement必要に応じて注釈を付けて、この新しいクラスを に渡されるリストに追加しJAXBContext.newInstance()ます。

ブランドの相互参照

@XmlIDJAXB にはandに基づく相互参照メカニズム@XmlIDREFがありますが、ID 属性が有効な XML ID、つまり XML 名である必要があり、特に完全に数字で構成されていない必要があります。しかし、「前方」参照 (つまり、まだ「宣言」されていない<car>a を参照する a) を必要としない限り、自分で相互参照を追跡することはそれほど難しくありません。<brand>

最初のステップは、JAXB クラスを定義して、<brand>

package cars;

import javax.xml.bind.annotation.*;

@XmlRootElement
public class Brand {
  @XmlValue // i.e. the simple content of the <brand> element
  String name;

  // optional id and refId attributes (optional because they're
  // Integer rather than int)
  @XmlAttribute
  Integer id;

  @XmlAttribute
  Integer refId;
}

Brandここで、オブジェクトとString必要なとの間で変換Carし、id/ref マッピングを維持するための「型アダプター」が必要です。

package cars;

import javax.xml.bind.annotation.adapters.*;
import java.util.*;

public class BrandAdapter extends XmlAdapter<Brand, String> {
  private Map<Integer, Brand> brandCache = new HashMap<Integer, Brand>();

  public Brand marshal(String s) {
    return null;
  }


  public String unmarshal(Brand b) {
    if(b.id != null) {
      // this is a <brand id="..."> - cache it
      brandCache.put(b.id, b);
    }
    if(b.refId != null) {
      // this is a <brand refId="..."> - pull it from the cache
      b = brandCache.get(b.refId);
    }

    // and extract the name
    return (b.name == null) ? null : b.name.trim();
  }
}

別の注釈を使用して、アダプターをbrandフィールドにリンクします。Car

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Car {
    @XmlJavaTypeAdapter(BrandAdapter.class)
    String brand;
    @XmlElementRef
    Engine engine;
}

パズルの最後の部分は<brand>、最上位で見つかった要素がキャッシュに保存されるようにすることです。ここに完全な例があります

package cars;

import javax.xml.bind.*;
import java.io.File;
import java.util.*;

import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;

public class Main {
  public static void main(String[] argv) throws Exception {
    List<Car> cars = new ArayList<Car>();

    JAXBContext ctx = JAXBContext.newInstance(Car.class, V12Engine.class, V6Engine.class, Brand.class);
    Unmarshaller um = ctx.createUnmarshaller();

    // create an adapter, and register it with the unmarshaller
    BrandAdapter ba = new BrandAdapter();
    um.setAdapter(BrandAdapter.class, ba);

    // create a StAX XMLStreamReader to read the XML file
    XMLInputFactory xif = XMLInputFactory.newFactory();
    XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource(new File("file.xml")));

    xsr.nextTag(); // root <list> element
    xsr.nextTag(); // first <brand> or <car> child

    // read each <brand>/<car> in turn
    while(xsr.getEventType() == XMLStreamConstants.START_ELEMENT) {
      Object obj = um.unmarshal(xsr);

      // unmarshal from an XMLStreamReader leaves the reader pointing at
      // the event *after* the closing tag of the element we read.  If there
      // was a text node between the closing tag of this element and the opening
      // tag of the next then we will need to skip it.
      if(xsr.getEventType() != XMLStreamConstants.START_ELEMENT && xsr.getEventType() != XMLStreamConstants.END_ELEMENT) xsr.nextTag();

      if(obj instanceof Brand) {
        // top-level <brand> - hand it to the BrandAdapter so it can be
        // cached if necessary
        ba.unmarshal((Brand)obj);
      }
      if(obj instanceof Car) {
        cars.add((Car)obj);
      }
    }
    xsr.close();

    // at this point, cars contains all the Car objects we found, with
    // any <brand> refIds resolved.
  }
}
于 2012-12-29T17:44:29.320 に答える
4

XStream を使用したソリューションを次に示します。これは、XStream に既に慣れているようで、信じられないほど柔軟な XML ツールであるためです。Java よりもはるかに優れているため、Groovy で行われます。Java への移植はかなり簡単です。XStream にすべての作業を任せようとするのではなく、結果を後処理することを選択したことに注意してください。具体的には、「ブランド参照」は事後に処理されます。マーシャリング内で行うこともできますが、このアプローチはよりクリーンであり、将来の変更に対するオプションがよりオープンになっていると思います。さらに、このアプローチでは、「ブランド」要素をドキュメント全体のどこにでも出現させることができます。これには、それらを参照する車の後も含まれます。

注釈付きのソリューション

import com.thoughtworks.xstream.XStream
import com.thoughtworks.xstream.annotations.*
import com.thoughtworks.xstream.converters.*
import com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter
import com.thoughtworks.xstream.io.*
import com.thoughtworks.xstream.mapper.Mapper

// The classes as given, plus toString()'s for readable output and XStream
// annotations to support unmarshalling. Note that with XStream's flexibility,
// all of this is possible with no annotations, so no code modifications are
// actually required.

@XStreamAlias("car")
// A custom converter for handling the oddities of parsing a Car, defined
// below.
@XStreamConverter(CarConverter)
class Car {
    String brand
    Engine engine
    String toString() { "Car{brand='$brand', engine=$engine}" }
}

abstract class Engine {
}

@XStreamAlias("v12engine")
class V12Engine extends Engine {
    @XStreamAsAttribute int horsePowers
    String toString() { "V12Engine{horsePowers=$horsePowers}" }
}

@XStreamAlias("v6engine")
class V6Engine extends Engine {
    @XStreamAsAttribute @XStreamAlias("fuel") String fuelType
    String toString() { "V6Engine{fuelType='$fuelType'}" }
}

// The given input:
String xml = """\
    <list>
        <brand id="1">
            Volvo
        </brand>
        <car>
            <brand>BMW</brand>
            <v12engine horsePowers="300" />
        </car>
        <car>
            <brand refId="1" />
            <v6engine fuel="unleaded" />
        </car>
    </list>"""

// The solution:

// A temporary Brand class to hold the relevant information needed for parsing
@XStreamAlias("brand")
// An out-of-the-box converter that uses a single field as the value of an
// element and makes everything else attributes: a perfect match for the given
// "brand" XML.
@XStreamConverter(value=ToAttributedValueConverter, strings="name")
class Brand {
    Integer id
    Integer refId
    String name
    String toString() { "Brand{id=$id, refId=$refId, name='$name'}" }
}

// Reads Car instances, figuring out the engine type and storing appropriate
// brand info along the way.
class CarConverter implements Converter {
    Mapper mapper

    // A Mapper can be injected auto-magically by XStream when converters are
    // configured via annotation.
    CarConverter(Mapper mapper) {
        this.mapper = mapper
    }

    Object unmarshal(HierarchicalStreamReader reader,
                     UnmarshallingContext context) {
        Car car = new Car()
        reader.moveDown()
        Brand brand = context.convertAnother(car, Brand)
        reader.moveUp()
        reader.moveDown()
        // The mapper knows about registered aliases and can tell us which
        // engine type it is.
        Class engineClass = mapper.realClass(reader.getNodeName())
        def engine = context.convertAnother(car, engineClass)
        reader.moveUp()
        // Set the brand name if available or a placeholder for later 
        // reference if not.
        if (brand.name) {
            car.brand = brand.name
        } else {
            car.brand = "#{$brand.refId}"
        }
        car.engine = engine
        return car
    }

    boolean canConvert(Class type) { type == Car }

    void marshal(Object source, HierarchicalStreamWriter writer,
                 MarshallingContext context) {
        throw new UnsupportedOperationException("Don't need this right now")
    }
}

// Now exercise it:

def x = new XStream()
// As written, this line would have to be modified to add new engine types,
// but if this isn't desirable, classpath scanning or some other kind of
// auto-registration could be set up, but not through XStream that I know of.
x.processAnnotations([Car, Brand, V12Engine, V6Engine] as Class[])
// Parsing will create a List containing Brands and Cars
def brandsAndCars = x.fromXML(xml)
List<Brand> brands = brandsAndCars.findAll { it instanceof Brand }
// XStream doesn't trim whitespace as occurs in the sample XML. Maybe it can
// be made to?
brands.each { it.name = it.name.trim() }
Map<Integer, Brand> brandsById = brands.collectEntries{ [it.id, it] }
List<Car> cars = brandsAndCars.findAll{ it instanceof Car }
// Regex match brand references and replace them with brand names.
cars.each {
    def brandReference = it.brand =~ /#\{(.*)\}/
    if (brandReference) {
        int brandId = brandReference[0][1].toInteger()
        it.brand = brandsById.get(brandId).name
    }
}
println "Brands:"
brands.each{ println "  $it" }
println "Cars:"
cars.each{ println "  $it" }

出力

Brands:
  Brand{id=1, refId=null, name='Volvo'}
Cars:
  Car{brand='BMW', engine=V12Engine{horsePowers=300}}
  Car{brand='Volvo', engine=V6Engine{fuelType='unleaded'}}

注釈なしのソリューション

PSニヤリと笑ってください。これは、注釈のない同じものです。new XStream()クラスに注釈を付ける代わりに、注釈が以前に行っていたすべてのことを行う追加の行がいくつかあることを除いて、すべて同じです。出力は同じです。

import com.thoughtworks.xstream.XStream
import com.thoughtworks.xstream.converters.*
import com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter
import com.thoughtworks.xstream.io.*
import com.thoughtworks.xstream.mapper.Mapper

class Car {
    String brand
    Engine engine
    String toString() { "Car{brand='$brand', engine=$engine}" }
}

abstract class Engine {
}

class V12Engine extends Engine {
    int horsePowers
    String toString() { "V12Engine{horsePowers=$horsePowers}" }
}

class V6Engine extends Engine {
    String fuelType
    String toString() { "V6Engine{fuelType='$fuelType'}" }
}

String xml = """\
    <list>
        <brand id="1">
            Volvo
        </brand>
        <car>
            <brand>BMW</brand>
            <v12engine horsePowers="300" />
        </car>
        <car>
            <brand refId="1" />
            <v6engine fuel="unleaded" />
        </car>
    </list>"""

class Brand {
    Integer id
    Integer refId
    String name
    String toString() { "Brand{id=$id, refId=$refId, name='$name'}" }
}

class CarConverter implements Converter {
    Mapper mapper

    CarConverter(Mapper mapper) {
        this.mapper = mapper
    }

    Object unmarshal(HierarchicalStreamReader reader,
                     UnmarshallingContext context) {
        Car car = new Car()
        reader.moveDown()
        Brand brand = context.convertAnother(car, Brand)
        reader.moveUp()
        reader.moveDown()
        Class engineClass = mapper.realClass(reader.getNodeName())
        def engine = context.convertAnother(car, engineClass)
        reader.moveUp()
        if (brand.name) {
            car.brand = brand.name
        } else {
            car.brand = "#{$brand.refId}"
        }
        car.engine = engine
        return car
    }

    boolean canConvert(Class type) { type == Car }

    void marshal(Object source, HierarchicalStreamWriter writer,
                 MarshallingContext context) {
        throw new UnsupportedOperationException("Don't need this right now")
    }
}

def x = new XStream()
x.alias('car', Car)
x.alias('brand', Brand)
x.alias('v6engine', V6Engine)
x.alias('v12engine', V12Engine)
x.registerConverter(new CarConverter(x.mapper))
x.registerConverter(new ToAttributedValueConverter(Brand, x.mapper, x.reflectionProvider, x.converterLookup, 'name'))
x.useAttributeFor(V12Engine, 'horsePowers')
x.aliasAttribute(V6Engine, 'fuelType', 'fuel')
x.useAttributeFor(V6Engine, 'fuelType')
def brandsAndCars = x.fromXML(xml)
List<Brand> brands = brandsAndCars.findAll { it instanceof Brand }
brands.each { it.name = it.name.trim() }
Map<Integer, Brand> brandsById = brands.collectEntries{ [it.id, it] }
List<Car> cars = brandsAndCars.findAll{ it instanceof Car }
cars.each {
    def brandReference = it.brand =~ /#\{(.*)\}/
    if (brandReference) {
        int brandId = brandReference[0][1].toInteger()
        it.brand = brandsById.get(brandId).name
    }
}
println "Brands:"
brands.each{ println "  $it" }
println "Cars:"
cars.each{ println "  $it" }

PPS Gradle がインストールされている場合は、これを にドロップしbuild.gradle、上記のスクリプトの 1 つを にドロップしてsrc/main/groovy/XStreamExample.groovyから、それだけgradle runで結果を確認できます。

apply plugin: 'groovy'
apply plugin: 'application'

mainClassName = 'XStreamExample'

dependencies {
    groovy 'org.codehaus.groovy:groovy:2.0.5'
    compile 'com.thoughtworks.xstream:xstream:1.4.3'
}

repositories {
    mavenCentral()
}
于 2012-12-30T04:37:34.557 に答える
1

ここを参照して、いくつかのアイデアを得ることができます。

個人的には、DOM パーサーを使用して XML ファイルの内容を取得します。

例:

import java.io.*;
import javax.xml.parsers.*;

import org.w3c.dom.*;

public class DOMExample {

  public static void main(String[] args) throws Exception {

    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

    File file = new File("filename.xml");
    Document doc = builder.parse(file);

    NodeList carList = doc.getElementsByTagName("car");
    for (int i = 0; i < carList.getLength(); ++i) {

        Element carElem = (Element)carList.item(i);

        Element brandElem = (Element)carElem.getElementsByTagName("brand").item(0);
        Element engineElem = (Element)carElem.getElementsByTagName("v12engine").item(0);

        String brand= brandElem.getTextContent();
        String engine= engineElem.getTextContent();

        System.out.println(brand+ ", " + engine);

        // TODO Do something with the desired information.
    }       
  }
}

タグ名の可能な内容を知っていれば、これはかなりうまくいくでしょう。XML ファイルを解析するには、さまざまな方法があります。うまくいけば、あなたのために働く何かを思いつくことができます. 幸運を!

于 2013-01-03T18:25:16.457 に答える