org.opensingular.internal.lib.commons.xml.MElement.java Source code

Java tutorial

Introduction

Here is the source code for org.opensingular.internal.lib.commons.xml.MElement.java

Source

/*
 * Copyright (C) 2016 Singular Studios (a.k.a Atom Tecnologia) - www.opensingular.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.opensingular.internal.lib.commons.xml;

import org.apache.commons.lang3.StringUtils;
import org.opensingular.internal.lib.commons.json.JSONToolkit;
import org.opensingular.lib.commons.base.SingularException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;

/**
 * Representa um Element com diversos mtodos utilitrios para
 * leitura e montagem do XML. Essa classe substitui a classe XMLToolkit.
 * O MElement  um Element (implementa essa interface) adicionado dos mtodos
 * do XMLToolkit.
 * <p/>
 *  possvel montar uma rvore XML com objeto org.w3c.dom.Element
 * usando os mtodos desta classe, no entanto, este procedimento no
 *  prtico, pois exige mais de um passo para adicionar uma
 * nica informao.
 * <p/>
 * A montagem de um estrutura de objetos Element em vez do arquivo
 * XML tambm  bem mais simples e de melhor performance Evita-se fazer um
 * parse do arquivo.
 * <p/>
 * <p/>
 * <b>Exemplo de uso</b>:<br>
 * <p/>
 * Passo 1: <i>Cria o elemento raiz:</i>
 * <xmp>
 * MElement raiz = MElement.newInstance("pedido");
 * raiz.printTabulado(System.out);
 * // XML resultado:
 * // <pedido/>
 * </xmp>
 * <p/>
 * Passo 2: <i>Adicionar sub-elementos:</i>
 * <xmp>
 * MElement item1 = raiz.addElement("item");
 * MElement item2 = raiz.addElement("item");
 * raiz.printTabulado(System.out);
 * // XML resultado em raiz:
 * // <pedido>
 * //   <item/>
 * //   <item/>
 * // </pedido>
 * </xmp>
 * <p/>
 * Passo 3: <i>Adicionar elementos com valores:</i>
 * <xmp>
 * item1.addElement("@cod",310);
 * item1.addElement("nome","arroz");
 * item1.addElement("qtd",10);
 * item2.addElement("@cod",410);
 * item2.addElement("nome","milho");
 * item2.addElement("qtd",21);
 * item2.addElement("unidade","kg");
 * raiz.addElement("responsavel","Paulo Santos");
 * raiz.printTabulado(System.out);
 * // XML resultado em raiz:
 * // <pedido>
 * //   <item cod="310">
 * //      <nome>arroz</nome>
 * //      <qtd>10</qtd>
 * //   </item>
 * //   <item cod="410">
 * //      <nome>milho</nome>
 * //      <qtd>21</qtd>
 * //      <unidade>kg</unidade>
 * //   </item>
 * //   <responsavel>Paulo Santos</responsavel>
 * // </pedido>
 * </xmp>
 * <p/>
 * Passo 4: <i>Percorrendo todos os elementos filhos:</i>
 * <xmp>
 * //getPrimeiroFilho() e getProximoIrmao j retornam MElement
 * MElement filho = raiz.getPrimeiroFilho();
 * while (filhos != null) {
 * System.out.println(filho.getNodeName());
 * filhos = filhos.getProximoIrmao()
 * }
 * // XML resultado:
 * //   item
 * //   item
 * //   responsavel
 * </xmp>
 * <p/>
 * Passo 5: <i>Percorrendo todos os elementos "item":</i>
 * <xmp>
 * //getPrimeiroFilho(String) e getProximoGemeo j retornam MElement
 * MElement item = raiz.getPrimeiroFilho("item");
 * while (item != null) {
 * System.out.println(
 * item.getValor("@cod") + " - " +   //Le um atributo
 * item.getValor("nome") + " - " +   //Le o valor de um Element
 * item.formatNumber("qtd",1) + " " +//Le formatando a saida
 * item.getValor("unidade","n/d"));  //Le valor (usando default)
 * <p/>
 * item = item.getProximoGemeo()
 * }
 * // XML resultado:
 * //   310 - arroz - 10.0 - kg
 * //   410 - milho - 21.0 - n/d
 * </xmp>
 * <p/>
 * Passo 6: <i>Percorrendo todos os elementos "item" com qtd=21:</i>
 * <xmp>
 * // selectElements aceita consultas xPath
 * MElementResult item = raiz.selectElements("item[qtd=21]");
 * while (item.next()) {
 * System.out.println(
 * item.getValor("@cod") + " - " +   //Le um atributo
 * item.getValor("nome") + " - ");   //Le o valor de um Element
 * }
 * // XML resultado:
 * //   410 - milho
 * </xmp>
 * <p/>
 * <b>Criando um MElement.</b> Existe 3 formas de se obter um MElement:
 * <xmp>
 * //Novo element
 * MElement raiz1 = MElement.newIntance("pedido");
 * <p/>
 * //Novo element com namespace
 * MElement raiz2 = MElement.newIntance("http://www.com/ordem", "pedido");
 * <p/>
 * //Convertendo um Element
 * //Toda alterao em wrapper reflete-se no Element original.
 * Element original = ....
 * MElement wraper = MElement.toElement(original);
 * </xmp>
 * <p/>
 * <b>MElement e SQL.</b> Um problema muito comum  o tratamento de null
 * tanto ao ler quanto ao gravar no banco de dados. O MElement possui alguns
 * facilitadores nesse sentido.<p>
 * <p/>
 * <i>Montando XML a partir do Banco</i>
 * <xmp>
 * <pre>
 *    MElement raiz = MElement.newInstance("pedido");
 *    java.sql.Date agora = new java.sql.Date(System.currentTimeMillis());
 *
 *    Resultset rs = ....
 *
 *    while(rs.next()) {
 *        MElement item = raiz.addElement("item");
 *
 *        // Se o nome for null, o addElement(String,String) dispara erro
 *        // Nesse caso no  problema, pois no banco o campo  not null
 *        item.addElement("nome", rs.getString("nome"));
 *        item.addElement("qtd" , rs.getInt("qtd");
 *
 *        // Por ser possvel que o campo und seja null,  utilizado
 *        // o mtodo addElement(String, String, String) onde o ltimo valor
 *        //  o default (se o segundo=null). Por ser o terceiro null, caso o
 *        // rs.getString("und") seja null, simplesmente no adicionada a tag
 *        item.addElement("und" , rs.getString("und"), null);
 *
 *        // Nesse caso adiciona o terceiro valor se dt for null
 *        item.addElement("dt"  , rs.getDate("dt"), agora);
 *    }
 * </pre>
 * </xmp>
 * <p/>
 * <i>Preenchendo um PreparedStatement a partir do MElement</i>
 * <pre>
 * <xmp>
 *    MElement raiz =....
 *    PreparedStatement ps = ....
 *
 *    //Caso qualquer um dos 4 campos sejam null, j chama ps.setNull(int)
 *
 *    //Mtodos especficos para os tipos principais
 *    raiz.setSQLInt(ps, 1, "id");
 *    raiz.setSQLString(ps, 2, "nome");
 *    raiz.setSQLDouble(ps, 3, "salario");
 *
 *    //Recebe como parmetro o tipo SQL a ser utilizado no parmetro
 *    raiz.setSQL(ps, 4, TYPES.VARCHAR, "descr");
 * </xmp>
 * </pre>
 *
 * @author Daniel C. Bordin
 */
public abstract class MElement implements Element, Serializable {

    /**
     * Pega em tempo de compilao situaes onde tenta-se converte MElement
     * para MElement. Como tal passo  totalmente desnecessrio, retorna void.
     * Na prtica gera um erro em tempo de compilao em tal caso.
     *
     * @param no elemento no precisa ser convertido
     */
    public static void toMElement(MElement no) {
        throw SingularException.rethrow("No deveria ser chamadado esse metodo com um parmetro MElement");
    }

    public static MElement toMElement(Element no) {
        if (no == null) {
            return null;
        } else if (no instanceof MElement) {
            return (MElement) no;
        }
        return new MElementWrapper(no);
    }

    /**
     * Gerar um wrapper MElement baseado no Node informado.
     *
     * @param no Element original.
     * @return Null se no for null. O prprio se esse j for MElement.
     */
    public static MElement toMElement(Node no) {
        if (no == null) {
            return null;
        } else if (no instanceof MElement) {
            return (MElement) no;
        } else if (!XmlUtil.isNodeTypeElement(no)) {
            throw new SingularException("no " + XPathToolkit.getFullPath(no) + " no  Element");
        }
        return new MElementWrapper((Element) no);
    }

    /**
     * Cria um novo MElement com tag raiz com o nome da classe informada. O
     * MElement contm internamente um Element embutido.
     *
     * @param toCall Classe cujo nome sera o nome da tag
     * @return MElement wrapper.
     */
    public static MElement newInstance(Class<?> toCall) {
        return newInstance(toCall.getName().replace('.', '-'));
    }

    /**
     * Cria um novo MElement com tag raiz no nome informado. O MElement contm
     * internamente um Element embutido.
     *
     * @param nomeRaiz nome da tag raiz
     * @return MElement wrapper.
     */
    public static MElement newInstance(String nomeRaiz) {
        return new MElementWrapper(nomeRaiz);
    }

    /**
     * Cria um novo MElement com tag raiz no nome e namespace especificados. O
     * MElement contm internamente um Element embutido. MElement retornado.
     *
     * @param nameSpaceURI Nome do namespace. Tipicamente o name space possui o
     *                     formato de uma URL (no  obrigatrio) no formato, por exemplo,
     *                     http://www.miranteinfo.com/sisfinanceiro/cobranca/registraPagamento.
     * @param nomeRaiz     o nome do elemento que ser criado. Pode conter prefixo
     *                     (ex.: "fi:ContaPagamento").
     * @return -
     */
    public static MElement newInstance(String nameSpaceURI, String nomeRaiz) {
        return new MElementWrapper(nameSpaceURI, nomeRaiz);
    }

    /**
     * Retorna o valor do no passado como parmetro. Se for um Element retorna o
     * texto imediatamente abaixo.
     *
     * @param no do qual ser extraido o texto
     * @return pdoe ser null
     */
    static String getValorTexto(Node no) {
        //No  private, pois a classe XMLToolkit tambm utiliza
        if (no == null) {
            return null;
        }
        switch (no.getNodeType()) {
        case Node.ELEMENT_NODE:
            Node n = no.getFirstChild();
            if (XmlUtil.isNodeTypeText(n)) {
                return n.getNodeValue();
            }
            break;
        case Node.ATTRIBUTE_NODE:
        case Node.TEXT_NODE:
            String valor = no.getNodeValue();
            if (!StringUtils.isEmpty(valor)) {
                return valor;
            }
            break;
        default:
            throw new SingularException("getValorTexto(Node) no trata n " + XPathToolkit.getNomeTipo(no));
        }
        return null;
    }

    public final MDocument getMDocument() {
        return MDocument.toMDocument(getOwnerDocument());
    }

    public final void addElement(MElement e) {
        appendChild(e.getOriginal());
    }

    abstract Element getOriginal();

    /**
     * Adiciona um element com o nome informado e devolve a referncia.
     *
     * @param nome do novo element filho
     * @return Elemento criado
     */
    public final MElement addElement(String nome) {
        return addElementNS(null, nome);
    }

    /**
     * Adiciona um element com o nome informado no namespace especificado.
     *
     * @param namespaceURI  -
     * @param qualifiedName Nome do novo element filho
     * @return Elemento criado
     */
    public final MElement addElementNS(String namespaceURI, String qualifiedName) {
        return toMElement(MElementWrapper.addElementNS(this, namespaceURI, qualifiedName));
    }

    /**
     * Adiciona um element como o nome informado como filho do atual.
     *
     * @param nome  do MElement a ser criado
     * @param valor Se for null, um exception  disparada.
     * @return O MElement resultado.
     */
    public final MElement addElement(String nome, String valor) {
        return toMElement(MElementWrapper.addElement(this, nome, valor));
    }

    /**
     * Adiciona um no como o nome informado e com o valor informado ou com o
     * default na ausencia do primeiro. Se o default tambm for null, ento o no
     * no  adicionado
     *
     * @param nome     do MElement a ser criado
     * @param valor    -
     * @param defaultV a ser utilizado se valor==null
     * @return O MElement resultado.
     */
    public final MElement addElement(String nome, String valor, String defaultV) {
        if (valor != null) {
            return addElement(nome, valor);
        } else if (defaultV != null) {
            return addElement(nome, defaultV);
        }
        return null;
    }

    /**
     * Adiciona o elemento como o valor informado como objeto fazendo as devidas
     * converes se necessrio. Trata os seguintes objetos de forma especial:
     * Integer, Long, Double, java.util.Date.
     *
     * @param nome do elemento a ser criado
     * @param o    Objeto a ser convertido para texto
     * @return MElement criado
     */
    public final MElement addElement(String nome, Object o) {
        if (o == null) {
            return addElement(nome, (String) null);
        } else if (o instanceof String) {
            //Apenas para no ter que sempre passar por todos os if
            //em geral ser String
            return addElement(nome, (String) o);
        } else if (o instanceof Integer) {
            return addElement(nome, ((Integer) o).intValue());
        } else if (o instanceof Long) {
            return addElement(nome, ((Long) o).longValue());
        } else if (o instanceof Double) {
            return addElement(nome, ((Double) o).doubleValue());
        } else if (o instanceof java.util.Date) {
            return addElement(nome, (java.util.Date) o);
        } else if (o instanceof Calendar) {
            return addElement(nome, (Calendar) o);
        } else if (o instanceof InputStream) {
            return addElement(nome, (InputStream) o);
        } else if (o instanceof byte[]) {
            return addElement(nome, (byte[]) o);
        } else {
            return addElement(nome, o.toString());
        }
    }

    /**
     * Adiciona o elemento como o valor informado como objeto fazendo as devidas
     * converes se necessrio. Se o valor null, utiliza o valor default. Se o
     * default tambm for null, ento o no no  adicionado.
     *
     * @param nome     do elemento a ser criado
     * @param valor    Objeto a ser convertido para texto
     * @param defaultV a ser utilizado se valor==null
     * @return MElement criado
     */
    public final MElement addElement(String nome, Object valor, Object defaultV) {
        if (valor != null) {
            return addElement(nome, valor);
        } else if (defaultV != null) {
            return addElement(nome, defaultV);
        }
        return null;
    }

    /**
     * Cria um no indicado pelo nome com o texto resultado da convero do
     * double.
     *
     * @param nome  do element ou atributo a ser criado
     * @param valor a ser atribuito
     * @return MElement criado ou dono do atributo criado
     */
    public final MElement addElement(String nome, double valor) {
        return addElement(nome, Double.toString(valor));
    }

    /**
     * Cria um no indicado pelo nome com o texto resultado da convero do
     * double segundo a preciso desejada.
     *
     * @param nome     do element ou atributo a ser criado
     * @param valor    a ser atribuito
     * @param precisao Informa quantas casas depois d virgula deseja-se manter.
     *                 Se for negativo arredonta os digitos antes da virgula.
     * @return MElement criado ou dono do atributo criado
     */
    public final MElement addElement(String nome, double valor, int precisao) {
        double m = Math.pow(10, precisao);
        String sValor = Double.toString(Math.rint(Math.round(valor * m)) / m);
        return addElement(nome, sValor);
    }

    /**
     * Cria um no indicado pelo nome com o texto resultado da convero do int.
     *
     * @param nome  do element ou atributo a ser criado
     * @param valor a ser atribuito
     * @return MElement criado ou dono do atributo criado
     */
    public final MElement addElement(String nome, int valor) {
        return addElement(nome, Integer.toString(valor));
    }

    /**
     * Cria um no indicado pelo nome com o texto resultado da convero do long.
     *
     * @param nome  do element ou atributo a ser criado
     * @param valor a ser atribuito
     * @return MElement criado ou dono do atributo criado
     */
    public final MElement addElement(String nome, long valor) {
        return addElement(nome, Long.toString(valor));
    }

    /**
     * Adiciona um elemento binario no formato BASE64 dentro do elemento pai. O
     * formato BASE64  definido pelo RFC1521 do RFC1521. Ele transforma um
     * binrio em uma string, um codificao de 6 bits. Deste modo, um array
     * binrio ocupa 33% mais espao no formato BASE, contudo passa a ser uma
     * string simples.  necessrio levar em considerao questes de gasto de
     * memria e de custo de converso de binrio para string e string para
     * binrio ao se decidir pelo uso deste formato.
     *
     * @param nome  o nome do elemento que ser inserido
     * @param valor o array binrio do elemento adicionado (a ser convertido p/
     *              BASE64)
     * @return o elemento que foi adicionado
     */
    public final MElement addElement(String nome, byte[] valor) {
        return addElement(nome, MElementWrapper.toBASE64(valor));
    }

    /**
     * Adiciona um elemento binario no formato BASE64 dentro do elemento pai at
     * esgotar a InputStream. O formato BASE64  definido pelo RFC1521 do
     * RFC1521. Ele transforma um binrio em uma string, um codificao de 6
     * bits. Deste modo, um array binrio ocupa 33% mais espao no formato BASE,
     * contudo passa a ser uma string simples.  necessrio levar em
     * considerao questes de gasto de memria e de custo de converso de
     * binrio para string e string para binrio ao se decidir pelo uso deste
     * formato.
     *
     * @param nome o nome do elemento que ser inserido
     * @param in   Stream com os dados a serem convertidos p/ BASE64.
     * @return o elemento que foi adicionado
     * @throws IOException se erro na leitura dos bytes
     */
    public final MElement addElement(String nome, InputStream in) {
        return addElement(nome, MElementWrapper.toBASE64(in));
    }

    /**
     * Adiciona o no com o nome indicado com o valor boolean informado.
     *
     * @param nome  do MElement a ser criado
     * @param valor -
     * @return O MElement criado (a menos que nome aponte para um atributo).
     */
    public final MElement addBoolean(String nome, boolean valor) {
        if (valor) {
            return addElement(nome, Boolean.TRUE.toString());
        } else {
            return addElement(nome, Boolean.FALSE.toString());
        }
    }

    /**
     * Adiciona o no com o nome indicado considerando-o como um inteiro.
     *
     * @param nome  do MELement a ser criado
     * @param valor Se for null, uma exception  disparada.
     * @return O MElement criado (a menos que nome aponte para um atributo).
     */
    public final MElement addInt(String nome, String valor) {
        if (valor == null) {
            return addElement(nome, (String) null);
        } else {
            String s = valor.trim();
            if (s.length() == 0) {
                return addElement(nome, (String) null);
            }
            Integer.parseInt(s); //NOSONAR Testa se  um inteiro
            return addElement(nome, s);
        }
    }

    /**
     * Adiciona o no com o nome indicado considerando o valor como sendo uma
     * inteiro a qual ser convertida para o formato ISO8601. Caso o valor seja
     * null ou string em branco, ento considera o segundo valor para a
     * converso. Caso o valorDefault tambm seja null ou em braco, ento no 
     * adiciona o n.
     *
     * @param nome         do MELement a ser criado
     * @param valor        -
     * @param valorDefault a ser utilizado se valor for null ou vazio
     * @return O MElement criado (a menos que nome aponte para um atributo).
     */
    public final MElement addInt(String nome, String valor, Object valorDefault) {
        if (valor != null) {
            String v = valor.trim();
            if (v.length() > 0) {
                Integer.parseInt(v); //NOSONAR Testa se  um inteiro
                return addElement(nome, v);
            }
        }
        if (valorDefault != null) {
            if (valorDefault instanceof String) {
                String v = ((String) valorDefault).trim();
                if (v.length() > 0) {
                    Integer.parseInt(v); //NOSONAR Testa se  um inteiro
                    return addElement(nome, v);
                }
            } else if (valorDefault instanceof Integer) {
                return addElement(nome, valorDefault);
            } else {
                throw new SingularException(
                        "Tipo default invlido (" + valorDefault.getClass().getName() + ") para um inteiro");
            }
        }
        return null;
    }

    /**
     * Adiciona o no com o nome indicado considerando o valor como sendo uma
     * inteiro a qual ser convertida para o formato ISO8601. Caso o valor seja
     * null ou string em branco, ento considera o segundo valor para a
     * converso. Caso o valorDefault tambm seja null ou em braco, ento no 
     * adiciona o n.
     *
     * @param nome         do MELement a ser criado
     * @param valor        -
     * @param valorDefault a ser utilizado se valor for null ou vazio
     * @return O MElement criado (a menos que nome aponte para um atributo).
     */
    public final MElement addInt(String nome, String valor, int valorDefault) {
        if (valor != null) {
            String v = valor.trim();
            if (v.length() > 0) {
                Integer.parseInt(v); //NOSONAR Testa se  um inteiro
                return addElement(nome, v);
            }
        }
        return addElement(nome, valorDefault);
    }

    /**
     * Adiciona o no com o nome indicado considerando o valor como sendo uma
     * data a qual ser convertida para o formato ISO8601.
     *
     * @param nome  do MELement a ser criado
     * @param valor Se for null, uma exception  disparada.
     * @return O MElement criado (a menos que nome aponte para um atributo).
     */
    public final MElement addDate(String nome, String valor) {
        if (valor == null) {
            return addElement(nome, (String) null);
        } else {
            return addElement(nome, ConversorToolkit.getDateFromData(valor));
        }
    }

    /**
     * Adiciona o no com o nome indicado considerando o valor como sendo uma
     * data a qual ser convertida para o formato ISO8601. Caso o valor seja
     * null ou string em branco, ento considera o segundo valor para a
     * converso. Caso o valorDefault tambm seja null ou em braco, ento no 
     * adiciona o n.
     *
     * @param nome         do MELement a ser criado
     * @param valor        -
     * @param valorDefault a ser utilizado se valor for null ou vazio
     * @return O MElement criado (a menos que nome aponte para um atributo).
     */
    public final MElement addDate(String nome, String valor, String valorDefault) {
        String trim = StringUtils.trimToNull(valor);
        if (trim == null) {
            trim = StringUtils.trimToNull(valorDefault);
        }
        if (trim != null) {
            return addElement(nome, ConversorToolkit.getDateFromData(trim));
        }
        return null;
    }

    /**
     * Adiciona um element como o nome e a data informada no formato ISO 8601.
     *
     * @param nome  do MElement a ser criado
     * @param valor Se for null, um exception  disparada.
     * @return O MElement resultado.
     */
    public final MElement addElement(String nome, java.util.Date valor) {
        if (valor == null) {
            return addElement(nome, (String) null);
        } else {
            return addElement(nome, ConversorDataISO8601.format(valor));
        }
    }

    /**
     * Adiciona um no como o nome informado e com o valor informado ou com o
     * default na ausencia do primeiro. Se o default tambm for null, ento o no
     * no  adicionado.
     *
     * @param nome         do MElement a ser criado
     * @param valor        -
     * @param valorDefault a ser utilizado se valor==null
     * @return O MElement resultado.
     */
    public final MElement addElement(String nome, java.util.Date valor, java.util.Date valorDefault) {
        if (valor != null) {
            return addElement(nome, ConversorDataISO8601.format(valor));
        } else if (valorDefault != null) {
            return addElement(nome, ConversorDataISO8601.format(valorDefault));
        }
        return null;
    }

    /**
     * Adiciona um element como o nome e o Calendar informada no formato ISO
     * 8601.
     *
     * @param nome  do MElement a ser criado
     * @param valor Se for null, um exception  disparada.
     * @return O MElement resultado.
     */
    public final MElement addElement(String nome, Calendar valor) {
        if (valor == null) {
            return addElement(nome, (String) null);
        } else {
            return addElement(nome, ConversorDataISO8601.format(valor));
        }
    }

    /**
     * Atualiza o Node (Element ou atributo) j exisitente. Se o Node no for
     * localizado, ento adiciona se o valor for diferente de null.
     *
     * @param xPath Caminho do Node a ser atualizado ou criado
     * @param value Novo valor do Node. Se for null, ento o valor  limpo. Se o
     *              element j existir e valor for null, ento transforma a tag em
     *              empty, mas a mantm no XML.
     * @return O Node alterado ou criado, ou null se no for possvel atualizar
     * o valor do mesmo.
     */
    public final Node updateNode(String xPath, String value) {
        Node n = getNode(xPath);
        if ((n == null) && !StringUtils.isEmpty(value)) {
            return addElement(xPath, value);
        } else if (n instanceof Element) {
            Node filho = n.getFirstChild();
            if (filho == null) {
                if (!StringUtils.isEmpty(value)) {
                    Document d = n.getOwnerDocument();
                    Text txt = d.createTextNode(value);
                    n.appendChild(txt);
                }
            } else if (XmlUtil.isNodeTypeText(filho)) {
                if (!StringUtils.isEmpty(value)) {
                    filho.setNodeValue(value);
                } else {
                    n.removeChild(filho);
                }
            } else {
                return null;
            }
        } else if (n instanceof Attr) {
            //No h como saber quem  o pai (n.getParentNode() retorna null)
            if (value == null) {
                return addElement(xPath, ""); //Fora a remoo do atributo
            }
            return addElement(xPath, value);
        } else {
            return null;
        }
        return n;
    }

    /**
     * Verifica se existe pelo menos um Element apontado pelo xPath. <br>
     * Dispara erro se existir um Node no endereo, mas esse no Element.
     *
     * @param xPath Endereo do Element ou consulta xpath
     * @return se getElement(xPath) != null
     * @see #possuiNode
     */
    public final boolean possuiElement(String xPath) {
        return getElement(xPath) != null;
    }

    /**
     * Verifica se existe pelo menos um n apontado pelo xPath.
     *
     * @param xPath Endereo do n ou consulta xpath
     * @return se getNode(xPath) != null
     */
    public final boolean possuiNode(String xPath) {
        return getNode(xPath) != null;
    }

    /**
     * Verifica se existe o n apontado pelo xPAth e se possui um texto.
     *
     * @param xPath Endereo do n ou consulta xpath
     * @return se getValor(xPath) != null
     */
    public final boolean isNull(String xPath) {
        return getValor(xPath) == null;
    }

    /**
     * Conta o nmero de ocorrncias de Elements filhos do no.
     *
     * @return o nmero de ocorrncias
     */
    public final int countFilhos() {
        return count(null);
    }

    /**
     * Conta o nmero de ocorrncias de Elements filhos do no com o nome
     * especificado.
     *
     * @param nome do elemento a ser procurado. Se for null conta todos.
     * @return o nmero de ocorrncias
     */
    public final int count(String nome) {
        int qtd = 0;
        Node node = getFirstChild();
        while (node != null) {
            if (XmlUtil.isNodeTypeElement(node, nome)) {
                qtd++;
            }
            node = node.getNextSibling();
        }
        return qtd;
    }

    /**
     * Retorna o texto referente ao elemento atual (sub-texto).
     *
     * @return null se for um tag vazia (ex: <nome/>).
     */
    public final String getValor() {
        return getValorTexto(this);
    }

    /**
     * Retorna o texto do elemento atual (sub-texto) com inteiro. Dispara
     * NullPointException se no houver um valor disponvel.
     *
     * @return -
     */
    public final int getInt() {
        String s = getValor();
        if (s == null) {
            throw new NullPointerException("Tag '" + getFullPath() + "' vazia");
        }
        return Integer.parseInt(s);
    }

    /**
     * Retorna o texto do elemento atual (sub-texto) como long. Dispara
     * NullPointException se no houver um valor disponvel.
     *
     * @return -
     */
    public final long getLong() {
        String s = getValor();
        if (s == null) {
            throw new NullPointerException("Tag '" + getFullPath() + "' vazia");
        }
        return Long.parseLong(s);
    }

    /**
     * Retorna o texto do elemento atual (sub-texto) como double. Dispara
     * NullPointException se no houver um valor disponvel.
     *
     * @return -
     */
    public final double getDouble() {
        String s = getValor();
        if (s == null) {
            throw new NullPointerException("Tag '" + getFullPath() + "' vazia");
        }
        return Double.parseDouble(s);
    }

    /**
     * Obtem o valor no endereo fornecido.
     *
     * @param xPath Caminho para o valor (string) desejado
     * @return Se existe o destino e apontado pelo nome e nele existe um valor,
     * esse  retornado. Caso contrrio devolve null.
     */
    public final String getValor(String xPath) {
        return getValorTexto(getNode(xPath));
    }

    /**
     * Obtem o valor no endereo fornecido, ou valor default, se a pesquisa
     * resultar em null.
     *
     * @param xPath    Caminho para o valor (string) desejado
     * @param defaultV valor a retornado se a pesquisa for null
     * @return -
     */
    public final String getValor(String xPath, String defaultV) {
        String s = getValorTexto(getNode(xPath));
        if (s == null) {
            return defaultV;
        }
        return s;
    }

    /**
     * Obtem o valor no endereo fornecido. Se o xPAth no existir ou apontar
     * para um Element ou atributo sem texto, dispara um exception.
     *
     * @param xPath Caminho para o valor (string) desejado
     * @return -
     * @throws NullPointerException Se a pesquisa resultar em null
     */
    public final String getValorNotNull(String xPath) throws NullPointerException {
        Node no = getNode(xPath);
        if (no == null) {
            throw new NullPointerException("xPath '" + xPath + "' no existe em '" + getFullPath() + "'");
        }
        String valor = getValorTexto(no);
        if (valor == null) {
            throw new NullPointerException(
                    "No '" + xPath + "' est vazio (fullPath=" + XPathToolkit.getFullPath(no) + ")");
        }
        return valor;
    }

    /**
     * Busca os valores dos elementos filhos com o nome informado. Se o nome for
     * null, retorna o valor de todos os filhos.
     *
     * @param xPath dos elementos a terem os valores retornados
     * @return sempre diferente de null
     */
    public final List<String> getValores(String xPath) {
        return XPathToolkit.getValores(this, xPath);
    }

    /**
     * Equivalente a getBoolean, serve para escrever cdigo mais legveis.
     *
     * @param xPath caminho xpath ou nome do elemento desejado
     * @return O boolean convertido de string
     */
    public final boolean is(String xPath) {
        return getBoolean(xPath);
    }

    /**
     * Equivalente a getBoolean, serve para escrever cdigo mais legveis.
     *
     * @param xPath        caminho xpath ou nome do elemento desejado
     * @param valorDefault Valor a ser utilziado se no encontrar nenhum valor
     *                     no caminho indicado no xPath
     * @return O boolean convertido de string
     */
    public final boolean is(String xPath, boolean valorDefault) {
        return getBoolean(xPath, valorDefault);
    }

    /**
     * Retorna o valor em boolean do sub-elemento indicado pelo caminho xpath.
     *
     * @param xPath caminho xpath ou nome do elemento desejado
     * @return O boolean convertido de string
     */
    public final boolean getBoolean(String xPath) {
        String s = getValorNotNull(xPath);
        if (Boolean.TRUE.toString().equals(s)) {
            return true;
        } else if (Boolean.FALSE.toString().equals(s)) {
            return false;
        }
        throw new SingularException("O valor em " + xPath + " no  boolean = " + s);
    }

    /**
     * Retorna o valor em boolean do sub-elemento indicado pelo caminho xpath.
     *
     * @param xPath        caminho xpath ou nome do elemento desejado
     * @param valorDefault Valor a ser utilziado se no encontrar nenhum valor
     *                     no caminho indicado no xPath
     * @return O boolean convertido de string
     */
    public final boolean getBoolean(String xPath, boolean valorDefault) {
        String s = getValor(xPath);
        if (s == null) {
            return valorDefault;
        } else if (Boolean.TRUE.toString().equals(s)) {
            return true;
        } else if (Boolean.FALSE.toString().equals(s)) {
            return false;
        }
        throw new SingularException("O valor em " + xPath + " no  boolean = " + s);
    }

    /**
     * Retorna o valor em int do sub-elemento indicado pelo caminho xpath.
     *
     * @param xPath caminho xpath ou nome do elemento desejado
     * @return O inteiro convertido de string
     */
    public final int getInt(String xPath) {
        return Integer.parseInt(getValorNotNull(xPath));
    }

    /**
     * Retorna o valor do no indicado pelo caminho xpath comvertido para
     * Integer.
     *
     * @param xPath caminho xpath ou nome do elemento desejado
     * @return O inteiro convertido de string ou null se getValor(xPath)==null.
     */
    public final Integer getInteger(String xPath) {
        String s = getValor(xPath);
        if (s == null) {
            return null;
        }
        return Integer.valueOf(s);
    }

    /**
     * Retorna o valor em int do sub-elemento indicado pelo caminho xpath.
     *
     * @param xPath    caminho xpath ou nome do elemento desejado
     * @param defaultV valor a se retornado se o xPath resultar em null
     * @return O long convertido de string
     */
    public final int getInt(String xPath, int defaultV) {
        String s = getValor(xPath);
        if (s == null) {
            return defaultV;
        }
        return Integer.parseInt(s);
    }

    /**
     * Retorna o valor em long do sub-elemento indicado pelo caminho xpath.
     *
     * @param xPath caminho xpath ou nome do elemento desejado
     * @return O long convertido de string
     */
    public final long getLong(String xPath) {
        return Long.parseLong(getValorNotNull(xPath));
    }

    /**
     * Retorna o valor em long do sub-elemento indicado pelo caminho xpath.
     *
     * @param xPath    caminho xpath ou nome do elemento desejado
     * @param defaultV valor a se retornado se o xPath resultar em null
     * @return O long convertido de string
     */
    public final long getLong(String xPath, long defaultV) {
        String s = getValor(xPath);
        if (s == null) {
            return defaultV;
        }
        return Long.parseLong(s);
    }

    /**
     * Retorna o valor em double do sub-elemento indicado pelo caminho xpath.
     * Utiliza Double.parseDouble(), ou seja, o formato deve ser com ponto como
     * separador de decimal.
     *
     * @param xPath caminho xpath ou nome do elemento desejado
     * @return O double convertido de string
     */
    public final double getDouble(String xPath) {
        return Double.parseDouble(getValorNotNull(xPath));
    }

    /**
     * Retorna o valor em double do sub-elemento apontando pelo nome ou caminho
     * xpath apontado. Utiliza Double.parseDouble(), ou seja, o formato deve ser
     * com ponto como separador de decimal.
     *
     * @param xPath    caminho xpath ou nome do elemento desejado
     * @param defaultV valor a se retornado se o xPath resultar em null
     * @return O double convertido de string
     */
    public final double getDouble(String xPath, double defaultV) {
        String s = getValor(xPath);
        if (s == null) {
            return defaultV;
        }
        return Double.parseDouble(s);
    }

    /**
     * Retorna o valor do no indicado pelo caminho xpath comvertido para um
     * objeto Double.
     *
     * @param xPath caminho xpath ou nome do elemento desejado
     * @return O Double convertido de string ou null se getValor(xPath)==null.
     */
    public final Double getDoubleObject(String xPath) {
        String s = getValor(xPath);
        if (s == null) {
            return null;
        }
        return new Double(s);
    }

    /**
     * Converte a String no endereo xPath com codificao BASE64 de volta para
     * um array de bytes.
     *
     * @param xPath endereo do valor (atributo, tag, etc.) a ser convertido
     * @return null se no encontrado
     */
    public final byte[] getByteBASE64(String xPath) {
        return MElementWrapper.fromBASE64(getValor(xPath));
    }

    /**
     * Converte a String no endereo xPath com codificao BASE64 de volta para
     * bytes escrevendo para a saida informada.
     *
     * @param xPath endereo do valor (atributo, tag, etc.) a ser convertido
     * @param out   Destino do bytes convertidos
     * @throws IOException Se houver problemas de converso ou de escrita para a
     *                     saida.
     */
    public final void getByteBASE64(String xPath, OutputStream out) {
        MElementWrapper.fromBASE64(getValorNotNull(xPath), out);
    }

    /**
     * Transforma o valor do campo para java.util.Date. Espera um campo no
     * formato "yyyy-mm-dd hh:mm:ss.fffffffff".
     *
     * @param xPath endereo do valor (atributo, tag, etc.) a ser convertido
     * @return -
     */
    public final java.util.Date getDate(String xPath) {
        String valor = getValor(xPath);
        if (valor == null) {
            return null;
        }
        return ConversorDataISO8601.getDate(valor);
    }

    /**
     * Transforma o valor do campo para Calendar. Espera um campo no formato
     * "yyyy-mm-dd hh:mm:ss.fffffffff".
     *
     * @param xPath endereo do valor (atributo, tag, etc.) a ser convertido
     * @return -
     */
    public final GregorianCalendar getCalendar(String xPath) {
        String valor = getValor(xPath);
        if (valor == null) {
            return null;
        }
        return ConversorDataISO8601.getCalendar(valor);
    }

    /**
     * Transforma o valor do campo em um nmero formato com separado de decimal
     * e milhar. Deixa livre a quantidade de casa decimais, ou seja, vai
     * colocar quanta forem necessrias.
     *
     * @param xPath endereo do valor (atributo, tag, etc.) a ser convertido
     * @return string vazia se o elemento no existir ou no tiver valor
     */
    public final String formatNumber(String xPath) {
        return ConversorToolkit.printNumber(getDouble(xPath, 0), -1, false);
    }

    /**
     * Transforma o valor do campo em um nmero formato com separado de decimal
     * e milhar. Deixa livre a quantidade de casa decimais, ou seja, vai
     * colocar quanta forem necessrias.
     *
     * @param xPath     endereo do valor (atributo, tag, etc.) a ser convertido
     * @param printZero Se falso e o valor zero, ento retorna string vazia
     * @return string vazia se o elemento no existir ou no tiver valor
     */
    public final String formatNumber(String xPath, boolean printZero) {
        return ConversorToolkit.printNumber(getDouble(xPath, 0), -1, printZero);
    }

    /**
     * Transforma o valor do campo em um nmero formato com separado de decimal
     * e milhar.
     *
     * @param xPath   endereo do valor (atributo, tag, etc.) a ser convertido
     * @param digitos qtd. de casas decimais a serem exibidas (-1 deixa livre).
     * @return zero se o elemento no existir ou no tiver valor
     */
    public final String formatNumber(String xPath, int digitos) {
        return ConversorToolkit.printNumber(getDouble(xPath, 0), digitos);
    }

    /**
     * Transforma o valor do campo em um nmero formato com separado de decimal
     * e milhar com opo de no exibir zeros.
     *
     * @param xPath     endereo do valor (atributo, tag, etc.) a ser convertido
     * @param digitos   qtd. de casas decimais a serem exibidas (-1 deixa livre).
     * @param printZero Se falso e o valor zero, ento retorna string vazia
     * @return string vazia se o elemento no existir ou no tiver valor
     */
    public final String formatNumber(String xPath, int digitos, boolean printZero) {
        return ConversorToolkit.printNumber(getDouble(xPath, 0), digitos, printZero);
    }

    /**
     * Transforma o valor do campo em uma string no formato de exibio de data
     * (dd/MM/yyyy).
     *
     * @param xPath endereo do valor (atributo, tag, etc.) a ser convertido
     * @return String vazia se o xPath no existir
     */
    public final String formatDate(String xPath) {
        java.util.Date d = getDate(xPath);
        return (d == null) ? "" : ConversorToolkit.printDate(d);
    }

    /**
     * Transforma o valor do campo em uma string no formato de exibio de data
     * segundo o formato solicitado.
     *
     * @param xPath   endereo do valor (atributo, tag, etc.) a ser convertido
     * @param formato a ser convertido pode ser "short", "medium", "long",
     *                "full" ou ento customizado (ver java.text.SimpleDateFormat).
     * @return String vazia se o xPath no existir
     */
    public final String formatDate(String xPath, String formato) {
        java.util.Date d = getDate(xPath);
        return (d == null) ? "" : ConversorToolkit.printDate(d, formato);
    }

    /**
     * Transforma o valor do campo em uma string no formato de exibio de hora
     * (hh:mm:ss).
     *
     * @param xPath endereo do valor (atributo, tag, etc.) a ser convertido
     * @return String vazia se o xPath no existir
     */
    public final String formatHour(String xPath) {
        java.util.Date d = getDate(xPath);
        return (d == null) ? "" : ConversorToolkit.printHora(d);
    }

    /**
     * A partir do atual procura o Node no xPath (Elemento, atributo, etc.).
     *
     * @param xPath caminho do elemento desejado
     * @return O Node no destino ou null se o xPath no existir
     * @see XPathToolkit
     */
    public final Node getNode(String xPath) {
        return XPathToolkit.selectNode(this, xPath);
    }

    /**
     * A partir do atual retorna um percorredor de todos os Element que
     * satisfazem a consulta xPath. Se a consulta for null, retorna todos os
     * filhos. Exemplos:
     * <p/>
     * <xmp>raiz.selectElements(null); //Retorna todos os filhos imediatos
     * raiz.selectElements("aluno"); //todos as tags "aluno" imediatas
     * raiz.selectElements("aluno/nota"); //todos as tags nota de baixo de
     * //todos os filhos aluno raiz.selectElements("aluno[@id=20]");//Todos as
     * tags aluno que possuem //um atributo id igual a 20 </xmp>
     *
     * @param xPath caminho do elemento desejado
     * @return Sempre diferente de null
     * @see XPathToolkit XPathToolkit para entender mais sobre xPath
     */
    public final MElementResult selectElements(String xPath) {
        return new MElementResult(this, xPath);
    }

    /**
     * Segue a mesma lgia de selectElements(String), mas retorna como iterator.
     *
     * @param xPath caminho dos elementos desejados
     * @return Sempre diferente de null
     */
    public final Iterator<MElement> iterator(String xPath) {
        MElementResult rs = new MElementResult(this, xPath);
        return rs.iterator();
    }

    /**
     * A partir do atual procura o Element no xPath.
     *
     * @param xPath caminho do elemento desejado
     * @return O Node no destino ou null se o xPath no existir
     * @throws SingularException Se o node no xPath no for um Element
     * @see XPathToolkit
     */
    public final MElement getElement(String xPath) {
        return toMElement(XPathToolkit.selectElement(this, xPath));
    }

    /**
     * Retornas todos os elementos com um nome especfico. Uma forma mais rpida
     * (e talvez mais prtica de pegar todos os filhos) :<br>
     * <xmp>MElementResult e = raiz.selectElements(xPAth); while( e.next) {
     * //faz algo com e..... } </xmp>
     *
     * @param xPath dos elementos a serem retornados. Se for null retorna todos
     *              os MElement imediantamente filhos.
     * @return a lista com os elementos ou um array de tamanho zero
     */
    public final MElement[] getElements(String xPath) {
        return selectElements(xPath).getTodos();
    }

    /**
     * Mtodo utilizado para colocar apenas o contedo de um elemento dentro de
     * outro elemento. O elemento, portanto, no  copiado, apenas todos os
     * elementos dentro dele.
     *
     * @param no a ter seus filhos copiados
     */
    public final void copyConteudo(Element no) {
        MElementWrapper.copyElement(this, no);
    }

    /**
     * Mtodo utilizado para colocar um elemento e todo o seu contedo dentro de
     * outro elemento, podendo ser usado um outro nome ao invs do nome do
     * elemento sendo copiado. Para manter o nome do elemento original, passar
     * <code>novoNome</code> igual a <code>null</code>.
     *
     * @param no       Element a ser inserido
     * @param novoNome se diferente de null
     * @return Elemento copia crido debaixo do atual.
     */
    public final MElement copy(Element no, String novoNome) {
        return toMElement(MElementWrapper.copyElement(this, no, novoNome));
    }

    /**
     * Gera o path completo (em xpath) do elemento a partir de seu raiz. Pode
     * ser utilizado para recuperar o elemento via gerElemento no raiz. Se for
     * um elemento repetido, ento adiciona um ndice (formato [N]).
     *
     * @return string xPath do elemento.
     */
    public final String getFullPath() {
        return XPathToolkit.getFullPath(this);
    }

    /**
     * Escreve o XML organizado as sub-tags em nveis (acresenta espao). Um
     * parse do string gerada por esse mtodo pode no gera o mesmo xml devido a
     * incluso de formatao (utilize o mtodo print()).
     *
     * @param out sada destino.
     */
    public final void printTabulado(PrintStream out) {
        PrintWriter out2 = new PrintWriter(out);
        XMLToolkitWriter.printDocumentIndentado(out2, this, true);
        out2.flush();
    }

    /**
     * Escreve o XML organizado as sub-tags em nveis (acresenta espao). Um
     * parse do string gerada por esse mtodo pode no gera o mesmo xml devido a
     * incluso de formatao (utilize o mtodo print()).
     *
     * @param out sada destino.
     */
    public final void printTabulado(PrintWriter out) {
        XMLToolkitWriter.printDocumentIndentado(out, this, true);
    }

    /**
     * Escreve para a saida padro (System.out) o XML organizado as sub-tags em
     * nveis (acresenta espao).
     */
    public final void printTabulado() {
        printTabulado(System.out);
    }

    /**
     * Escreve o XML de forma que um eventual parse gere o mesmo XML. Para
     * impresses mais legveis utilize printTabulado().
     *
     * @param out sada destino
     */
    public final void print(PrintStream out) {
        PrintWriter out2 = new PrintWriter(out);
        XMLToolkitWriter.printDocument(out2, this, true);
        out2.flush();
    }

    /**
     * Escreve o XML de forma que um eventual parse gere o mesmo XML. Para
     * impresses mais legveis utilize printTabulado().
     *
     * @param out sada destino
     */
    public final void print(PrintWriter out) {
        XMLToolkitWriter.printDocument(out, this, true);
    }

    /**
     * Escreve o XML de forma que um eventual parse gere o mesmo XML. Para
     * impresses mais legveis utilize printTabulado().
     *
     * @param out         sada destino
     * @param printHeader Se true, adiciona string de indentificao de arquivo
     *                    XML. Se false, depois no ser possvel fazer parse do resultado
     *                    sem informaoes complementares (header).
     */
    public final void print(PrintWriter out, boolean printHeader) {
        XMLToolkitWriter.printDocument(out, this, printHeader);
    }

    /**
     * Escreve o XML de forma que um eventual parse gere o mesmo XML. Para
     * impresses mais legveis utilize printTabulado().
     *
     * @param out               sada destino
     * @param printHeader       Se true, adiciona string de indentificao de arquivo
     *                          XML. Se false, depois no ser possvel fazer parse do resultado
     *                          sem informaoes complementares (header).
     * @param converteEspeciais se verdadeiro converte os caracteres '<' '>' e '&' para
     *                          seus respectivos escapes.
     */
    public final void print(PrintWriter out, boolean printHeader, boolean converteEspeciais) {
        XMLToolkitWriter.printDocument(out, this, printHeader, converteEspeciais);
    }

    /**
     * Retorna o Element que est antes do atual mas no mesmo nvel.
     *
     * @return null se o atual j for o primeiro Element da lista.
     */
    public final MElement getIrmaoAnterior() {
        return procurarElementAnterior(getPreviousSibling(), null);
    }

    /**
     * Retorna o Element que est antes do atual mas no mesmo nvel e que possui
     * o mesmo nome do elemento atual.
     *
     * @return null se o atual j for o primeiro Element da lista com o nome.
     */
    public final MElement getGemeoAnterior() {
        return procurarElementAnterior(getPreviousSibling(), getNodeName());
    }

    /**
     * Retorna o prximo Element que est no mesmo nvel do elemento atual.
     *
     * @return null se esse for o ltimo elemento.
     */
    public final MElement getProximoIrmao() {
        return procurarProximoElement(getNextSibling(), null);
    }

    /**
     * Retorna o prximo Element que est no mesmo nvel do elemento atual e que
     * possui o mesmo nome do Element atual.
     *
     * @return null se esse for o ltimo elemento com o nome.
     */
    public final MElement getProximoGemeo() {
        return procurarProximoElement(getNextSibling(), getNodeName());
    }

    /**
     * Retorna o primeiro Element filho do atual.
     *
     * @return null se no houve nenhum n filho do tipo Element
     */
    public final MElement getPrimeiroFilho() {
        return procurarProximoElement(getFirstChild(), null);
    }

    /**
     * Retorna o primeiro Element filho do atual com um nome especfico.
     *
     * @param nome do Element filho a ser encontrado.
     * @return null se no houve nenhum n filho do tipo Element
     */
    public final MElement getPrimeiroFilho(String nome) {
        if (nome == null) {
            throw new IllegalArgumentException("O nome no pode ser null");
        }
        return procurarProximoElement(getFirstChild(), nome);
    }

    /**
     * Retorna o ultimo Element filho do atual.
     *
     * @return null se no houve nenhum n filho do tipo Element
     */
    public final MElement getUltimoFilho() {
        return procurarElementAnterior(getLastChild(), null);
    }

    /**
     * Procura pelo node do tipo Element anterior (incluindo o n informado).
     *
     * @param no   Ponto de partida da pesquisa
     * @param nome Nome do Element a ser retornado. Se for null retorna o
     *             primeiro a ser encontrado.
     * @return Um Element ou null se no encontrar.
     */
    private MElement procurarElementAnterior(Node no2, String nome) {
        for (Node current = no2; current != null; current = current.getPreviousSibling()) {
            if (XmlUtil.isNodeTypeElement(current, nome)) {
                return toMElement(current);
            }
        }
        return null;
    }

    /**
     * Procura pelo proximo node do tipo Element (incluindo o n informado).
     *
     * @param no   Ponto de partida da pesquisa
     * @param nome Nome do Element a ser retornado. Se for null retorna o
     *             primeiro a ser encontrado.
     * @return Um Element ou null se no encontrar.
     */
    private MElement procurarProximoElement(Node no, String nome) {
        return toMElement(XmlUtil.nextSiblingOfTypeElement(no, nome));
    }

    /**
     * Gera o XML to elemento conforme o funcionamento do mtodo printTabulado
     * (utilizar preferencialment printTabulado). Existe como convenincia
     * quando no houver um PrintWriter ou PrintStream disponvel.
     *
     * @return o XML com um tag por linha e alinhado conforme o nvel
     */
    @Override
    public String toString() {
        CharArrayWriter writer = new CharArrayWriter();
        PrintWriter out = new PrintWriter(writer);
        printTabulado(out);
        out.flush();
        return writer.toString();
    }

    /**
     * Gera o XML do elemento conforme o funcionamento do mtodo print (utilizar
     * preferencialment printTabulado). Existe como convenincia quando no
     * houver um PrintWriter ou PrintStream disponvel.
     *
     * @return a String que feito parse, retorna o mesmo conteudo
     */
    public final String toStringExato() {
        CharArrayWriter writer = new CharArrayWriter();
        PrintWriter out = new PrintWriter(writer);
        print(out, true, true);
        out.flush();
        return writer.toString();
    }

    /**
     * Gera o XML do elemento conforme o funcionamento do mtodo print (utilizar
     * preferencialment printTabulado). Existe como convenincia quando no
     * houver um PrintWriter ou PrintStream disponvel.
     *
     * @param printHeader Indica se ser adiciona o identificado inicial de
     *                    arquivo XML. Se for false, no ser possvel fazer parse do
     *                    resultado sem a adio de informaes complementares.
     * @return a String que feito parse, retorna o mesmo conteudo
     */
    public final String toStringExato(boolean printHeader) {
        CharArrayWriter writer = new CharArrayWriter();
        PrintWriter out = new PrintWriter(writer);
        print(out, printHeader, true);
        out.flush();
        return writer.toString();
    }

    /**
     * Gera o XML do elemento conforme o funcionamento do mtodo print.
     *
     * @return a byte array que feito parse, retorna o mesmo conteudo
     */
    public final byte[] toByteArray() {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintWriter pw = new PrintWriter(out);
        print(pw);
        pw.flush();
        return out.toByteArray();
    }

    public String toJSONString() {
        final StringWriter sw = new StringWriter();
        JSONToolkit.printJSON(new PrintWriter(sw), this);
        return sw.toString();
    }
}