代码之家  ›  专栏  ›  技术社区  ›  Gandalf StormCrow

解析xml的更好方法

  •  8
  • Gandalf StormCrow  · 技术社区  · 12 年前

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

    9 回复  |  直到 12 年前
        1
  •  6
  •   Marcelo    12 年前

    如果您控制XML的定义,您可以使用XML绑定工具,例如 JAXB公司 (用于XML绑定的Java体系结构。)在JAXB中,您可以为XML结构定义模式(支持XSD和其他类型),或者为Java类添加注释,以便定义序列化规则。一旦您在XML和Java之间有了明确的声明性映射,那么到XML的编组和从XML的解编组就变得微不足道了。

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

    JAXB page from Oracle

        2
  •  5
  •   Sami Korhonen    12 年前

    下面是一个将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());
        }
    }
    
        3
  •  0
  •   Thorn    12 年前

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

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

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

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

        4
  •  0
  •   Ian Roberts    12 年前

    在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的组合来实现类似的事情——定义表示重复元素的JAXB注释类(本例中为Item),然后创建StAX解析器,导航到第一个 Item 开始标记,然后可以对一个完整的进行解组 项目 XMLStreamReader .

        5
  •  0
  •   dinukadev    12 年前

    正如其他人所建议的那样,Stax模型是一种更好的方法,可以最大限度地减少内存足迹,因为它是一种基于推送的模型。我个人使用了Axio(它在Apache Axis中使用),并使用XPath表达式解析元素,这比您在提供的代码片段中所做的遍历节点元素更不冗长。

        6
  •  0
  •   Trade-Ideas Philip    11 年前

    我一直在使用这个图书馆。它位于标准Java库的顶部,让我更轻松。特别是,你可以通过名称询问特定的元素或属性,而不是使用你描述的大“if”语句。

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

        7
  •  0
  •   Pythagoras    8 年前

    还有另一个库支持更紧凑的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代表return Null,E代表throw Exception,D代表use Default。

        8
  •  0
  •   mike rodent    8 年前

    不使用外部包,甚至不使用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) 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<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> 其与 堆栈<部分模式> : Objects 然后可以是你想要的任何东西,使你能够“回到”你正在处理的那个方兴未艾的“XML节点”。不要使用 Map 不过: 堆栈 可能包含相同的 PARSE_MODE 对象不止一次。事实上,这说明了所有树状结构的一个基本特征: 没有单独的节点 (此处:解析模式) 孤立存在:它的身份总是由通向它的整个路径定义 .

        9
  •  -1
  •   Twinscode    12 年前
        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]);
        }
    }
    

    }

    推荐文章
    Praxder  ·  SAX XMLParser不工作
    12 年前