【问题标题】:Better way to parse xml解析xml的更好方法
【发布时间】:2013-03-25 23:24:29
【问题描述】:

多年来我一直在解析这样的 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 文件)。

【问题讨论】:

  • 如果您有一个生成 XML 的音乐会数据模型,我会看看 XStream (xstream.codehaus.org)。它在将数据序列化为 xml 并返回时做得非常好。
  • 关于主题,我喜欢从 XSD 开始并使用 XmlBeans。有点 OT,XML 标签应该区分大小写,而这段代码打破了这一点。

标签: java xml sax


【解决方案1】:

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

使用 JAXB 确实需要比 SAX 处理程序更多的内存,但存在按部分处理 XML 文档的方法:Dealing with large documents。

JAXB page from Oracle

【讨论】:

    【解决方案2】:

    这是一个将 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>
    

    Person.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;
        }
    }
    

    PersonlistProcessor.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());
        }
    }
    

    【讨论】:

      【解决方案3】:

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

      当一个 String 表示 XML 中的一个对象时,你可以这样写:

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

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

      【讨论】:

        【解决方案4】:

        在 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 开始标记,然后然后您可以一次从XMLStreamReader 解组一个完整的Item。

        【讨论】:

          【解决方案5】:

          正如其他人所建议的,Stax 模型将是一种更好的方法来最大限度地减少内存占用,因为它是基于推送的模型。我个人使用过 Axio(在 Apache Axis 中使用)并使用 XPath 表达式解析元素,这比您在提供的代码 sn-p 中所做的通过节点元素更简洁。

          【讨论】:

            【解决方案6】:

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

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

            【讨论】:

              【解决方案7】:

              还有另一个支持更紧凑的 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代表使用默认。

              【讨论】:

                【解决方案8】:

                不使用外部包甚至 XPath 的解决方案: 使用 enum "PARSE_MODE",可能与 Stack&lt;PARSE_MODE&gt; 结合使用:

                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&lt;String&gt;,也许在构造函数中:

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

                c)startElement 和 endElement:

                @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&lt;PARSE_MODE&gt; 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 这是更复杂的东西的起点:例如,您可以设置一个与Stack&lt;PARSE_MODE&gt; 保持同步的List&lt;Object&gt;:Objects 可以是您想要的任何东西,使您能够“回溯”到您正在处理的那个方兴未艾的“XML 节点”。但是不要使用Map:Stack 可能会多次包含相同的PARSE_MODE 对象。这实际上说明了所有树状结构的基本特征:没有单个节点 (这里:解析模式) 孤立存在:它的身份始终由通向它的整个路径定义。

                【讨论】:

                  【解决方案9】:
                      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]);
                      }
                  }
                  

                  }

                  【讨论】:

                  • OP 明确表示他们不想想要使用 DOM(或任何其他涉及将整个文档解析为内存中的树结构的模型)
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-07-10
                  • 2021-12-21
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-05-22
                  • 2013-07-24
                  • 1970-01-01
                  相关资源
                  最近更新 更多