Melhor maneira de processar o xml

há anos que analiso o XML desta forma, e tenho de admitir que quando o número de elementos diferentes se torna maior, acho um pouco aborrecido e cansativo fazer, eis o que quero dizer, amostra dummy 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>

Esta é a parte relevante (usando saxofone):

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



    }

}
Pergunto-me se há alguma maneira de nos livrarmos destes booleanos hediondos que continuam a crescer com o número de elementos. Deve haver uma maneira melhor de processar este xml relativamente simples. Apenas olhando as linhas de código necessário para fazer esta tarefa parece feio.

Atualmente eu estou usando o analisador SAX, mas estou aberto a quaisquer outras sugestões (além do DOM, Eu não posso pagar em analisadores de memória eu tenho arquivos XML enormes).

Author: Gandalf StormCrow, 2013-03-26

9 answers

Aqui está um exemplo de usar JAXB com StAX.

Documento de entrada:

<?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>

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

Endereço.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;
    }
}

Processador Personlist.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());
    }
}
 5
Author: Sami Korhonen, 2013-03-26 11:26:42

Se controlar a definição do XML, poderá usar uma ferramenta de ligação XML, por exemplo JAXB (Arquitectura Java para a ligação XML.) No JAXB você pode definir um esquema para a estrutura XML (XSD e outros são suportados) ou anotar suas classes Java, a fim de definir as regras de serialização. Uma vez que você tenha um mapeamento declarativo claro entre XML e Java, marshalling e unmarshalling para/de XML torna-se trivial.

Usar o JAXB requer mais memória do que o SAX. manipuladores, mas existem métodos para processar os documentos XML por partes: lidar com documentos grandes .

Página do JAXB do Oracle

 6
Author: Marcelo, 2013-03-26 00:10:56

Tenho usado xsteam para serializar os meus próprios objectos em xml e depois carregá-los de volta como objectos Java. Se você pode representar todo o significado como POJOs e você corretamente anotar o POJOs para corresponder aos tipos em seu arquivo xml você pode achar muito mais fácil de usar.

Quando uma cadeia de caracteres representa um objecto em XML, você pode simplesmente escrever:

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

Eu sempre o usei para carregar um objecto na memória numa única linha, mas se precisares de O transmitir e processar à medida que vais ... deve ser capaz de usar um Hierarquicalstreamreader para iterar através do documento. Isto pode ser muito semelhante ao Simples, sugerido por @Dave.
 0
Author: Thorn, 2013-03-26 00:06:19
Em SAX, O analisador "empurra" os eventos no seu manipulador, então você tem que fazer todas as tarefas domésticas como você está acostumado aqui. Uma alternativa seria StAX( o pacote javax.xml.stream), que ainda está em streaming, mas seu código é responsável por" puxar " eventos do analisador. Desta forma, a lógica do que os elementos são esperados em que ordem é codificada no fluxo de controle de seu programa, em vez de ter que ser explicitamente representado em booleanos.

Dependendo da estrutura precisa do XML pode haver um "caminho do meio" com um kit de ferramentas como XOM, que tem um modo de operação onde você analisar uma subárvore do documento em um DOM-como o modelo de objeto, processo que galho, em seguida, jogá-lo fora e analisar o próximo. Isto é bom para documentos repetitivos com muitos elementos semelhantes que podem ser processados isoladamente - você tem a facilidade de programação para uma API baseada em árvore dentro de cada ramo, mas ainda tem o comportamento de streaming que lhe permite processar documentos enormes eficientemente.

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

Você pode alcançar semelhante coisa com uma combinação de StAX e JAXB - definir JAXB anotada classes que representam o elemento de repetição (Item neste exemplo) e, em seguida, criar um StAX analisador, navegue para o primeiro Item tag de início e, em seguida, você pode desempacotar um completo Item em vez de XMLStreamReader.

 0
Author: Ian Roberts, 2013-03-26 00:06:53

Como outros sugeriram, um modelo de Stax seria uma melhor abordagem para minimizar a impressão do pé de memória, uma vez que é um modelo baseado em push. Eu usei pessoalmente Axio (que é usado no eixo Apache) e processar elementos usando expressões XPath que é menos descritivo do que passar por elementos de nó como você fez no excerto de código fornecido.

 0
Author: dinukadev, 2013-03-26 00:21:02
Tenho usado esta biblioteca. Ele fica em cima da biblioteca Java padrão e torna as coisas mais fáceis para mim. Em particular, você pode pedir um elemento específico ou atributo pelo nome, em vez de usar a grande declaração "se" que você descreveu.

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

 0
Author: Trade-Ideas Philip, 2014-02-18 21:27:06

Existe outra biblioteca que suporta o processamento de XML mais compacto, RTXML. A biblioteca e a sua documentação estão em rasmustorkel.com . implementei o processamento do ficheiro na pergunta original e estou a incluir o programa completo aqui:

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

Irá notar que as funções de recuperação terminam em N, e ou D. referem-se ao que fazer quando o item de dados desejado não estiver lá. N significa "return Null", E significa "throw Exception" e D significa "use" Padrao.

 0
Author: Pythagoras, 2016-05-01 11:56:08

Solução sem usar pacotes externos, ou mesmo XPath: use um enum "PARSE_ mode", provavelmente em combinação com um Stack<PARSE_MODE>:

1) a solução básica:

A) campos

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) faça o seu List<String>, talvez no construtor:

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

C) startElement e 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...
    }

}

... o que significa tudo isto? A qualquer momento você tem conhecimento do "modo de processamento" em que você está ... e você também pode olhar para o Stack<PARSE_MODE> modeBreadcrumbs Se você precisa para saber que outros modos de processamento passou para chegar aqui...

O seu método characters torna-se substancialmente mais limpo:

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) a solução mais" profissional":
abstract classe que as classes concretas têm que estender e que, em seguida, não têm capacidade para modificar o Stack, etc. NB: isto examina qName em vez de localName. Assim:

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 {

}

Então, parte saliente da subclasse de betão:

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;
}
Este é um ponto de partida para mais coisas sofisticadas: por exemplo, você pode configurar um List<Object> que é mantido sincronizado com o Stack<PARSE_MODE>: o Objects pode então ser o que quiser, permitindo-lhe "voltar" para os "nós XML" ascendentes daquele com que está a lidar. No entanto, não utilize um Map: o Stack pode potencialmente conter o mesmo objecto PARSE_MODE mais de uma vez. Isto ilustra, de facto, uma característica fundamental de todas as estruturas arbóreas: sem nó individual (aqui: modo de processamento) existe em isolamento: a sua identidade é sempre definida por todo o caminho que a leva.
 0
Author: mike rodent, 2017-03-05 15:05:45
    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]);
    }
}

}

 -1
Author: Twinscode, 2013-03-25 23:57:27