2013-03-25 4 views
8

Я разбирался в XML как это в течение многих лет, и я должен признать, что количество разных элементов становится больше. Мне кажется, что это немного скучно и утомительно, вот что я имею в виду, образец фиктивная 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> 

Это относится часть (с использованием саксофона):

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

Вы можете попробовать StAX –

+0

Если у вас есть модель данных концерта, которая генерирует XML, я бы взглянул на XStream (http://xstream.codehaus.org/). Это действительно хорошая работа по сериализации данных в xml и обратно. –

+1

По теме мне нравится начинать с XSD и использовать XmlBeans. Немного OT, теги XML должны быть чувствительны к регистру, и этот код нарушает это. –

ответ

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> 

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

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()); 
    } 
} 
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 - определить JAXB аннотированные классы, представляющие повторяющийся элемент (деталь в этом примере), а затем создать парсер StAX, перейдите к первому Item открывающего тегу , а затем вы можете размонтировать один полный Item за один раз с XMLStreamReader.

-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]); 
    } 
} 

}

+0

OP специально сказал, что они _didn't_ хотят использовать DOM (или любую другую модель, которая включает в себя анализ всего документа в древовидной структуре в памяти) –

6

Если вы контролируете определение XML, вы можете использовать XML-инструмент связывания, например JAXB (Java Architecture для XML Binding.) В JAXB вы можете определить схему для структура XML (XSD и другие поддерживаются) или аннотировать ваши классы Java, чтобы определить правила сериализации. Когда у вас есть четкое декларативное сопоставление между XML и Java, маршаллинг и unmarshalling в/из XML становится тривиальным.

Использование JAXB требует большего объема памяти, чем обработчики SAX, но существуют методы обработки XML-документов по частям: Dealing with large documents.

JAXB page from Oracle

0

Я использую xsteam сериализации свои объекты в XML, а затем загрузить их обратно в качестве объектов Java. Если вы можете представлять все как POJO, и вы должным образом аннотируете POJO для соответствия типам вашего xml-файла, вам может показаться, что это намного проще в использовании.

Когда строка представляет объект в XML, вы можете просто написать:

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

Я всегда использовал его, чтобы загрузить объект в памяти в одной строке, но если вам нужно, чтобы поток его и процесс, как вы идете, вы должны иметь возможность использовать HierarchicalStreamReader для перебора документа. Это может быть очень похоже на Simple, предложенное @Dave.

0

Как было предложено другими, модель Stax была бы лучшим подходом к минимизации печати стопы памяти, поскольку это модель на основе push. Я лично использовал Axio (который используется в Apache Axis) и анализирует элементы, используя выражения XPath, которые являются менее подробными, чем элементы узла, как это было сделано в предоставленном фрагменте кода.

0

Я использую эту библиотеку. Он сидит поверх стандартной библиотеки Java и облегчает мне работу. В частности, вы можете запросить определенный элемент или атрибут по имени, а не использовать большой оператор «если», который вы описали.

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

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 означает throw Exception и D обозначает использование по умолчанию.

0

Solution без использования вне пакета, или даже XPath: использовать enum "PARSE_MODE", возможно, в сочетании с Stack<PARSE_MODE>:

1) Основной раствор:

а) поля

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>(); 

б) сделать свой List<String>, может быть, в строи тор:

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

с) 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 и т.д. NB это рассматривает 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» того, с которым вы имеете дело. Не используйте Map, хотя: Stack может потенциально содержать один и тот же объект PARSE_MODE более одного раза. Это на самом деле иллюстрирует фундаментальную характеристику всех древовидных структур: нет отдельного узла(здесь: синтаксический режим)существует в изоляции: его идентичность всегда определяется весь путь, ведущий к ней.

Смежные вопросы