8

多年来我一直在解析这样的 XML,我不得不承认,当不同元素的数量变大时,我觉得这样做有点无聊和筋疲力尽,这就是我的意思,示例虚拟 XML:

<?xml version="1.0"?>
<Order>
    <Date>2003/07/04</Date>
    <CustomerId>123</CustomerId>
    <CustomerName>Acme Alpha</CustomerName>
    <Item>
        <ItemId> 987</ItemId>
        <ItemName>Coupler</ItemName>
        <Quantity>5</Quantity>
    </Item>
    <Item>
        <ItemId>654</ItemId>
        <ItemName>Connector</ItemName>
        <Quantity unit="12">3</Quantity>
    </Item>
    <Item>
        <ItemId>579</ItemId>
        <ItemName>Clasp</ItemName>
        <Quantity>1</Quantity>
    </Item>
</Order>

这是相关部分(使用 sax):

public class SaxParser extends DefaultHandler {

    boolean isItem = false;
    boolean isOrder = false;
    boolean isDate = false;
    boolean isCustomerId = false;
    private Order order;
    private Item item;

        @Override
    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
        if (localName.equalsIgnoreCase("ORDER")) {
            order = new Order();
        }

        if (localName.equalsIgnoreCase("DATE")) {
            isDate = true;
        }

        if (localName.equalsIgnoreCase("CUSTOMERID")) {
            isCustomerId = true;
        }

        if (localName.equalsIgnoreCase("ITEM")) {
            isItem = true;
        }
    }

    public void characters(char ch[], int start, int length) throws SAXException {

        if (isDate){
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
            String value = new String(ch, start, length);
            try {
                order.setDate(formatter.parse(value));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }

        if(isCustomerId){
            order.setCustomerId(Integer.valueOf(new String(ch, start, length)));
        }

        if (isItem) {
            item = new Item();
            isItem = false;
        }



    }

}

我想知道有没有办法摆脱这些随着元素数量不断增长的可怕布尔值。一定有更好的方法来解析这个相对简单的xml。仅通过查看执行此任务所需的代码行看起来很难看。

目前我正在使用 SAX 解析器,但我愿意接受任何其他建议(除了 DOM,我在内存解析器中买不起我有巨大的 XML 文件)。

4

9 回答 9

6

如果您控制 XML 的定义,则可以使用 XML 绑定工具,例如JAXB(用于 XML 绑定的 Java 体系结构)。在 JAXB 中,您可以为 XML 结构定义模式(支持 XSD 和其他)或注释您的 Java类以定义序列化规则。一旦您在 XML 和 Java 之间建立了清晰的声明式映射,对 XML 的编组和解组就变得微不足道了。

使用 JAXB 确实需要比 SAX 处理程序更多的内存,但是存在按部分处理 XML 文档的方法:处理大型文档

Oracle 的 JAXB 页面

于 2013-03-25T23:59:20.807 回答
5

这是一个将 JAXB 与 StAX 结合使用的示例。

输入文件:

<?xml version="1.0" encoding="UTF-8"?>
<Personlist xmlns="http://example.org">
    <Person>
        <Name>Name 1</Name>
        <Address>
            <StreetAddress>Somestreet</StreetAddress>
            <PostalCode>00001</PostalCode>
            <CountryName>Finland</CountryName>
        </Address>
    </Person>
    <Person>
        <Name>Name 2</Name>
        <Address>
            <StreetAddress>Someotherstreet</StreetAddress>
            <PostalCode>43400</PostalCode>
            <CountryName>Sweden</CountryName>
        </Address>
    </Person>
</Personlist>

人.java:

@XmlRootElement(name = "Person", namespace = "http://example.org")
public class Person {
    @XmlElement(name = "Name", namespace = "http://example.org")
    private String name;
    @XmlElement(name = "Address", namespace = "http://example.org")
    private Address address;

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }
}

地址.java:

public class Address {
    @XmlElement(name = "StreetAddress", namespace = "http://example.org")
    private String streetAddress;
    @XmlElement(name = "PostalCode", namespace = "http://example.org")
    private String postalCode;
    @XmlElement(name = "CountryName", namespace = "http://example.org")
    private String countryName;

    public String getStreetAddress() {
        return streetAddress;
    }

    public String getPostalCode() {
        return postalCode;
    }

    public String getCountryName() {
        return countryName;
    }
}

个人列表处理器.java:

public class PersonlistProcessor {
    public static void main(String[] args) throws Exception {
        new PersonlistProcessor().processPersonlist(PersonlistProcessor.class
                .getResourceAsStream("personlist.xml"));
    }

    // TODO: Instead of throws Exception, all exceptions should be wrapped
    // inside runtime exception
    public void processPersonlist(InputStream inputStream) throws Exception {
        JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
        XMLStreamReader xss = XMLInputFactory.newFactory().createXMLStreamReader(inputStream);
        // Create unmarshaller
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        // Go to next tag
        xss.nextTag();
        // Require Personlist
        xss.require(XMLStreamReader.START_ELEMENT, "http://example.org", "Personlist");
        // Go to next tag
        while (xss.nextTag() == XMLStreamReader.START_ELEMENT) {
            // Require Person
            xss.require(XMLStreamReader.START_ELEMENT, "http://example.org", "Person");
            // Unmarshall person
            Person person = (Person)unmarshaller.unmarshal(xss);
            // Process person
            processPerson(person);
        }
        // Require Personlist
        xss.require(XMLStreamReader.END_ELEMENT, "http://example.org", "Personlist");
    }

    private void processPerson(Person person) {
        System.out.println(person.getName());
        System.out.println(person.getAddress().getCountryName());
    }
}
于 2013-03-26T11:26:42.290 回答
0

在 SAX 中,解析器将事件“推送”到您的处理程序,因此您必须像在这里习惯的那样做所有的内务处理。另一种选择是 StAX(javax.xml.stream包),它仍在流式传输,但您的代码负责从解析器“拉”事件。这样,在程序的控制流中编码了哪些元素的逻辑以什么顺序被编码,而不必用布尔值显式表示。

根据 XML 的精确结构,可能存在使用XOM之类的工具包的“中间方式” ,它具有一种操作模式,您可以将文档的子树解析为类似 DOM 的对象模型,处理该树枝,然后抛出它离开并解析下一个。这对于具有许多相似元素的重复文档很有用,每个元素都可以单独处理 - 您可以轻松地在每个树枝中对基于树的 API 进行编程,但仍然具有流式处理行为,可以让您有效地解析大型文档。

public class ItemProcessor extends NodeFactory {
  private Nodes emptyNodes = new Nodes();

  public Nodes finishMakingElement(Element elt) {
    if("Item".equals(elt.getLocalName())) {
      // process the Item element here
      System.out.println(elt.getFirstChildElement("ItemId").getValue()
         + ": " + elt.getFirstChildElement("ItemName").getValue());

      // then throw it away
      return emptyNodes;
    } else {
      return super.finishMakingElement(elt);
    }
  }
}

您可以使用 StAX 和 JAXB 的组合来实现类似的事情 - 定义代表您的重复元素(本例中的 Item)的 JAXB 注释类,然后创建一个 StAX 解析器,导航到第一个Item开始标记,然后您可以解组一个完整的Item一次从XMLStreamReader

于 2013-03-25T23:48:02.343 回答
0

我一直在使用这个库。它位于标准 Java 库之上,让我的工作变得更轻松。特别是,您可以按名称请求特定元素或属性,而不是使用您描述的大“if”语句。

http://marketmovers.blogspot.com/2014/02/the-easy-way-to-read-xml-in-java.html

于 2014-02-18T21:27:06.033 回答
0

我一直在使用xsteam将我自己的对象序列化为 xml,然后将它们作为 Java 对象加载回来。如果您可以将所有内容表示为 POJO,并且正确地注释 POJO 以匹配 xml 文件中的类型,您可能会发现它更易于使用。

当一个 String 表示 XML 中的一个对象时,您可以只写:

Order theOrder = (Order)xstream.fromXML(xmlString);

我一直使用它在一行中将对象加载到内存中,但是如果您需要对其进行流式传输并随时处理,您应该能够使用HierarchicalStreamReader来遍历文档。这可能与@Dave 建议的 Simple 非常相似。

于 2013-03-26T00:06:19.353 回答
0

正如其他人所建议的那样,Stax 模型将是最小化内存占用的更好方法,因为它是基于推送的模型。我个人使用过 Axio(在 Apache Axis 中使用)并使用 XPath 表达式解析元素,这比您在提供的代码片段中完成的遍历节点元素更简洁。

于 2013-03-26T00:21:02.647 回答
0

还有另一个支持更紧凑的 XML 解析的库 RTXML。该库及其文档位于rasmustorkel.com上。我在原始问题中实现了文件的解析,并在此处包含了完整的程序:

package for_so;

import java.io.File;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import rasmus_torkel.xml_basic.read.TagNode;
import rasmus_torkel.xml_basic.read.XmlReadOptions;
import rasmus_torkel.xml_basic.read.impl.XmlReader;

public class Q15626686_ReadOrder
{
    public static class Order
    {
        public final Date            _date;
        public final int             _customerId;
        public final String          _customerName;
        public final ArrayList<Item> _itemAl;

        public
        Order(TagNode node)
        {
            _date = (Date)node.nextStringMappedFieldE("Date", Date.class);
            _customerId = (int)node.nextIntFieldE("CustomerId");
            _customerName = node.nextTextFieldE("CustomerName");
            _itemAl = new ArrayList<Item>();
            boolean finished = false;
            while (!finished)
            {
                TagNode itemNode = node.nextChildN("Item");
                if (itemNode != null)
                {
                    Item item = new Item(itemNode);
                    _itemAl.add(item);
                }
                else
                {
                    finished = true;
                }
            }
            node.verifyNoMoreChildren();
        }
    }

    public static final Pattern DATE_PATTERN = Pattern.compile("^(\\d\\d\\d\\d)\\/(\\d\\d)\\/(\\d\\d)$");

    public static class Date
    {
        public final String _dateString;
        public final int    _year;
        public final int    _month;
        public final int    _day;

        public
        Date(String dateString)
        {
            _dateString = dateString;
            Matcher matcher = DATE_PATTERN.matcher(dateString);
            if (!matcher.matches())
            {
                throw new RuntimeException(dateString + " does not match pattern " + DATE_PATTERN.pattern());
            }
            _year = Integer.parseInt(matcher.group(1));
            _month = Integer.parseInt(matcher.group(2));
            _day = Integer.parseInt(matcher.group(3));
        }
    }

    public static class Item
    {
        public final int      _itemId;
        public final String   _itemName;
        public final Quantity _quantity;

        public
        Item(TagNode node)
        {
            _itemId = node.nextIntFieldE("ItemId");
            _itemName = node.nextTextFieldE("ItemName");
            _quantity = new Quantity(node.nextChildE("Quantity"));
            node.verifyNoMoreChildren();
        }
    }

    public static class Quantity
    {
        public final int _unitSize;
        public final int _unitQuantity;

        public
        Quantity(TagNode node)
        {
            _unitSize = node.attributeIntD("unit", 1);
            _unitQuantity = node.onlyInt();
        }
    }

    public static void
    main(String[] args)
    {
        File xmlFile = new File(args[0]);
        TagNode orderNode = XmlReader.xmlFileToRoot(xmlFile, "Order", XmlReadOptions.DEFAULT);
        Order order = new Order(orderNode);
        System.out.println("Read order for " + order._customerName + " which has " + order._itemAl.size() + " items");
    }
}

您会注意到检索函数以 N、E 或 D 结尾。它们指的是当所需数据项不存在时要执行的操作。N 代表返回 Null,E 代表抛出异常,D 代表使用默认值。

于 2016-04-08T12:44:20.933 回答
0

不使用外部包甚至 XPath 的解决方案:使用enum“PARSE_MODE”,可能与Stack<PARSE_MODE>

1)基本解决方案:

a) 字段

private PARSE_MODE parseMode = PARSE_MODE.__UNDEFINED__;
// NB: essential that all these enum values are upper case, but this is the convention anyway
private enum PARSE_MODE {
    __UNDEFINED__, ORDER, DATE, CUSTOMERID, ITEM };
private List<String> parseModeStrings = new ArrayList<String>();
private Stack<PARSE_MODE> modeBreadcrumbs = new Stack<PARSE_MODE>();

b)让你的List<String>,也许在构造函数中:

    for( PARSE_MODE pm : PARSE_MODE.values() ){
        // might want to check here that these are indeed upper case
        parseModeStrings.add( pm.name() );
    }

c)startElementendElement

@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
    String localNameUC = localName.toUpperCase();
    // pushing "__UNDEFINED__" would mess things up! But unlikely name for an XML element
    assert ! localNameUC.equals( "__UNDEFINED__" );

    if( parseModeStrings.contains( localNameUC )){
        parseMode = PARSE_MODE.valueOf( localNameUC );
        // any "policing" to do with which modes are allowed to switch into 
        // other modes could be put here... 
        // in your case, go `new Order()` here when parseMode == ORDER
        modeBreadcrumbs.push( parseMode );
    } 
    else {
       // typically ignore the start of this element...
    }
}   

@Override
private void endElement(String uri, String localName, String qName) throws Exception {
    String localNameUC = localName.toUpperCase();
    if( parseModeStrings.contains( localNameUC )){
        // will not fail unless XML structure which is malformed in some way
        // or coding error in use of the Stack, etc.:
        assert modeBreadcrumbs.pop() == parseMode;
        if( modeBreadcrumbs.empty() ){
            parseMode = PARSE_MODE.__UNDEFINED__;
        }
        else {
            parseMode = modeBreadcrumbs.peek();
        }
    } 
    else {
       // typically ignore the end of this element...
    }

}

... 那么,这意味着什么?在任何时候,您都知道您所处的“解析模式”......Stack<PARSE_MODE> modeBreadcrumbs如果您需要找出您通过哪些其他解析模式到达这里,您还可以查看......

然后,您的characters方法变得更加清洁:

public void characters(char[] ch, int start, int length) throws SAXException {
    switch( parseMode ){
    case DATE:
        // PS - this SimpleDateFormat object can be a field: it doesn't need to be created hundreds of times
        SimpleDateFormat formatter. ...
        String value = ...
        ...
        break;

    case CUSTOMERID:
        order.setCustomerId( ...
        break;

    case ITEM:
        item = new Item();
        // this next line probably won't be needed: when you get to endElement, if 
        // parseMode is ITEM, the previous mode will be restored automatically
        // isItem = false ;
    }

}

2)更“专业”的解决方案:
abstract具体类必须扩展的类,然后无法修改Stack等。注意,这检查qName而不是localName. 因此:

public abstract class AbstractSAXHandler extends DefaultHandler {
    protected enum PARSE_MODE implements SAXHandlerParseMode {
        __UNDEFINED__
    };
    // abstract: the concrete subclasses must populate...
    abstract protected Collection<Enum<?>> getPossibleModes();
    // 
    private Stack<SAXHandlerParseMode> modeBreadcrumbs = new Stack<SAXHandlerParseMode>();
    private Collection<Enum<?>> possibleModes;
    private Map<String, Enum<?>> nameToEnumMap;
    private Map<String, Enum<?>> getNameToEnumMap(){
        // lazy creation and population of map
        if( nameToEnumMap == null ){
            if( possibleModes == null ){
                possibleModes = getPossibleModes();
            }
            nameToEnumMap = new HashMap<String, Enum<?>>();
            for( Enum<?> possibleMode : possibleModes ){
                nameToEnumMap.put( possibleMode.name(), possibleMode ); 
            }
        }
        return nameToEnumMap;
    }

    protected boolean isLegitimateModeName( String name ){
        return getNameToEnumMap().containsKey( name );
    }

    protected SAXHandlerParseMode getParseMode() {
        return modeBreadcrumbs.isEmpty()? PARSE_MODE.__UNDEFINED__ : modeBreadcrumbs.peek();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes)
            throws SAXException {
        try {
            _startElement(uri, localName, qName, attributes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // override in subclasses (NB I think caught Exceptions are not a brilliant design choice in Java)
    protected void _startElement(String uri, String localName, String qName, Attributes attributes)
            throws Exception {
        String qNameUC = qName.toUpperCase();
        // very undesirable ever to push "UNDEFINED"! But unlikely name for an XML element
        assert !qNameUC.equals("__UNDEFINED__") : "Encountered XML element with qName \"__UNDEFINED__\"!";
        if( getNameToEnumMap().containsKey( qNameUC )){
            Enum<?> newMode = getNameToEnumMap().get( qNameUC );
            modeBreadcrumbs.push( (SAXHandlerParseMode)newMode );
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        try {
            _endElement(uri, localName, qName);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // override in subclasses
    protected void _endElement(String uri, String localName, String qName) throws Exception {
        String qNameUC = qName.toUpperCase();
        if( getNameToEnumMap().containsKey( qNameUC )){
            modeBreadcrumbs.pop(); 
        }
    }

    public List<?> showModeBreadcrumbs(){
        return org.apache.commons.collections4.ListUtils.unmodifiableList( modeBreadcrumbs );
    }

}

interface SAXHandlerParseMode {

}

然后,具体子类的显着部分:

private enum PARSE_MODE implements SAXHandlerParseMode {
    ORDER, DATE, CUSTOMERID, ITEM
};

private Collection<Enum<?>> possibleModes;

@Override
protected Collection<Enum<?>> getPossibleModes() {
    // lazy initiation
    if (possibleModes == null) {
        List<SAXHandlerParseMode> parseModes = new ArrayList<SAXHandlerParseMode>( Arrays.asList(PARSE_MODE.values()) );
        possibleModes = new ArrayList<Enum<?>>();
        for( SAXHandlerParseMode parseMode : parseModes ){
            possibleModes.add( PARSE_MODE.valueOf( parseMode.toString() ));
        }
        // __UNDEFINED__ mode (from abstract superclass) must be added afterwards
        possibleModes.add( AbstractSAXHandler.PARSE_MODE.__UNDEFINED__ );
    }
    return possibleModes;
}

PS 这是更复杂的东西的起点:例如,您可以设置一个List<Object>Stack<PARSE_MODE>: 保持同步的,Objects然后可以是您想要的任何东西,使您能够“返回”到上升的“XML 节点”你正在处理的一个。但是,不要使用 a Map: theStack可能会PARSE_MODE多次包含同一个对象。这实际上说明了所有树状结构的一个基本特征:没有单独的节点 (这里:解析模式) 是孤立存在的:它的身份始终由通向它的整个路径定义

于 2017-03-04T13:11:26.583 回答
-1
    import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

public class JXML {
private DocumentBuilder builder;
private Document doc = null;
private DocumentBuilderFactory factory ;
private XPathExpression expr = null;
private XPathFactory xFactory;
private XPath xpath;
private String xmlFile;
public static ArrayList<String> XMLVALUE ;  


public JXML(String xmlFile){
    this.xmlFile = xmlFile;
}


private void xmlFileSettings(){     
    try {
        factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        xFactory = XPathFactory.newInstance();
        xpath = xFactory.newXPath();
        builder = factory.newDocumentBuilder();
        doc = builder.parse(xmlFile);
    }
    catch (Exception e){
        System.out.println(e);
    }       
}



public String[] selectQuery(String query){
    xmlFileSettings();
    ArrayList<String> records = new ArrayList<String>();
    try {
        expr = xpath.compile(query);
        Object result = expr.evaluate(doc, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result;
        for (int i=0; i<nodes.getLength();i++){             
            records.add(nodes.item(i).getNodeValue());
        }
        return records.toArray(new String[records.size()]);
    } 
    catch (Exception e) {
        System.out.println("There is error in query string");
        return records.toArray(new String[records.size()]);
    }       
}

public boolean updateQuery(String query,String value){
    xmlFileSettings();
    try{
        NodeList nodes = (NodeList) xpath.evaluate(query, doc, XPathConstants.NODESET);
        for (int idx = 0; idx < nodes.getLength(); idx++) {
          nodes.item(idx).setTextContent(value);
        }
        Transformer xformer = TransformerFactory.newInstance().newTransformer();
        xformer.transform(new DOMSource(doc), new StreamResult(new File(this.xmlFile)));
        return true;
    }catch(Exception e){
        System.out.println(e);
        return false;
    }
}




public static void main(String args[]){
    JXML jxml = new JXML("c://user.xml");
    jxml.updateQuery("//Order/CustomerId/text()","222");
    String result[]=jxml.selectQuery("//Order/Item/*/text()");
    for(int i=0;i<result.length;i++){
        System.out.println(result[i]);
    }
}

}

于 2013-03-25T23:57:27.747 回答