Предисловие


Как-то давно, я столкнулся с проблемой под названием «Document Object Model». При всей простоте и интуитивной понятности самого по себе XML, большинство предлагаемых API парсеров являются чем-то громоздким и труднопонимаемым для человека, который только что начал вникать в особенности работы с XML. Да, я не спорю, всё это комплексные решения, охватывающие все возможные аспекты, но от этого не легче.

Для меня до си�� пор остаётся загадкой, почему никто (или почти никто) из разработчиков парсеров не предоставляет какие-нибудь лёгкие решения для «повседневного пользования», так сказать. Пусть даже это граничит с профанацией, но суть от этого не меняется — вникая в проблему и идеологию целиком, мы приобретаем умение пользоваться большим и мощным инструментом, жертвуя при этом большим количеством времени. Возможно, кому-то наличие упрощённого варианта этого инструмента позволило бы вникнуть в суть намного быстрее.

Тем не менее, пока что приходится изобретать велосипед для себя.


Сегодня я хочу выкатить вам свой «велосипед» — небольшой класс для работы с XML под Android. Я уверен, многим он будет полезен хотя бы в качестве ознакомительного курса.

Сразу скажу, я не буду детально объяснять, почему и как я сделал то или это — исходник класса предельно простой, две самые большие его функции занимают едва ли по 40 строк, а общее количество строк менее 250-и. Вы можете скопировать его в конце этого текста.

Описание


Как я уже говорил, этот класс предназначен для работы с данными в формате XML. Если сказать более развёрнуто, он позволяет разбирать и формировать XML в виде строки или потока, предоставляет доступ к содержимому и аттрибутам узлов, но не работает с пространствами имён. Возможно, последнее является недостатком, но я не встретил для себя необходимости работать с пространствами имён, мне достаточно аттрибутов и содержимого узлов.

Рассмотрим пример:

SimpleXML xml = new SimpleXML("Home");
 
SimpleXML child = xml.createChild("Child");
child.setAttr("name""Vasya");
child.setAttr("sex""male");
child.setAttr("age""5");
 
SimpleXML dog = xml.getNodeByPath("Pets\\Dog"true);
dog.setAttr("name""Druzshok");
dog.setAttr("age""3");
 
String xmlOut = SimpleXML.saveXml(xml);
 


С помощью этого примера мы сформировали строку со следующим содержимым:

<?xml version='1.0' encoding='Utf-8' standalone='yes' ?>
<Home>
 <Child sex="male" age="5" name="Vasya"></Child>
 <pets>
  <Dog age="3" name="Druzshok"></Dog>
 </pets>
</Home>


А теперь загрузим полученное содержимое в объект:

SimpleXML xml2 = SimpleXML.loadXml(xmlOut);
if (xml2 != null) {
  // Узнаем, сколько у нас узлов
  int nodeCount = xml2.getChildCount();
 
  Vector<SimpleXML> children = xml2.getChildren("Child");
    if (children != null && children.size() > 0) {
      for(SimpleXML child : children) {
      // Производим действия с набором узлов Child
    }
  }
 
SimpleXML dog = xml2.getNodeByPath("Pets\\Dog"false); 
if (dog != null) {
  // Собаку нашли. Узнаем, как её звать.
  String dogName = dog.getAttr("name");
  if (!dogName.equals("")) {
    // Узнали, что собаку звать dogName
  }
 
  // Посмотрим, сколько аттрибутов у собаки
  int dogAttrCount = dog.getAttrCount();
 
  // О��дадим собаку бабушке
  SimpleXML xmlGrandma = new SimpleXML("Grandma");
 
  // Даже если у бабули не было животных, то она теперь будет знать о том, что они бывают
  SimpleXML xmlGrandmaPets = xmlGrandma.getNodeByPath("Pets"true);
  dog.setParent(xmlGrandmaPets);
 
  // И заберем у бабушки кота, если он есть
  SimpleXML cat = xmlGrandma.getNodeByPath("Pets\\Cat"false);
  if (cat != null) {
    cat.setParent(xml2);
  }
}


Мы смогли получить список детей, найти собаку, а так же узнать её кличку, и отдать эту собаку бабушке взамен на кота, наличие которого, правда, сомнительно. И это замечательно!

А теперь сам класс:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import java.util.Map.Entry;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlSerializer;
import android.util.Log;
import android.util.Xml;
 
public class SimpleXML {
 
  private SimpleXML fparent;
  private String ftext;
  private String fname;
  private Vector<SimpleXML> fchild;
  private HashMap<String, String> fattrs;
 
  public String getText() {
    return ftext;
  }
 
  public void setText(String newText) {
    ftext = newText;
  }
 
  public String getAttr(String attrName) {
    if (fattrs.containsKey(attrName)) return fattrs.get(attrName);
    return "";
  }
 
  public void setAttr(String attrName, String attrValue) {
    fattrs.put(attrName, attrValue);
  }
 
  public int getAttrCount() {
    return fattrs.size();
  }
 
  public int getChildCount() {
    return fchild.size();
  }
 
  public Vector<SimpleXML> getChildren() {
    return fchild;
  }
 
  public SimpleXML getParent() {
    return fparent;
  }
 
  public void setParent(SimpleXML newParent) {
    if (fparent != null) {
      try {
        fparent.fchild.remove(this);
      } catch (Exception e) { }
    }
    if (newParent instanceof SimpleXML) {
      fparent = newParent;
      try {
        fparent.fchild.add(this);
      } catch (Exception e) { }
    } else {
      fparent = null;
    }
  }
 
  public SimpleXML(String nodeName) {
    fname = nodeName;
    ftext = "";
    fattrs = new HashMap<String, String>();
    fchild = new Vector<SimpleXML>();
    fparent = null;
  }
 
  public static SimpleXML fromNode(Node node) {
    SimpleXML ret = null;
    if (node != null) {
      try {
        ret = new SimpleXML(node.getNodeName());
 
        if (node.hasAttributes()) {
          NamedNodeMap nattr = node.getAttributes();
          for(int f = 0; f < nattr.getLength(); ++f) {
            ret.setAttr(nattr.item(f).getNodeName(), nattr.item(f).getNodeValue());
          }
        }
 
        if (node.hasChildNodes()) {
          NodeList nlc = node.getChildNodes();
 
          for(int f = 0; f < nlc.getLength(); ++f) {
            if (nlc.item(f).getNodeType() == Node.TEXT_NODE) {
              ret.ftext += nlc.item(f).getNodeValue();
            } else if (nlc.item(f).getNodeType() == Node.ENTITY_REFERENCE_NODE) {
              String nv = nlc.item(f).getNodeName();
              if (nv != null && nv.length() > 1 && nv.startsWith("#")) {
                nv = nv.substring(1);
                try {
                  int[] z =  { Integer.parseInt(nv) };
                  String s = new String(z, 0, z.length);
                  ret.ftext += s; 
                } catch (Exception e) { }
              }
            } else {
              SimpleXML rchild = SimpleXML.fromNode(nlc.item(f));
              if (rchild != null) ret.getChildren().add(rchild);
            }
          }
        }
 
      } catch (Exception e) { }
    }
    return ret;
  }
 
  public SimpleXML createChild(String nodeName) {
    SimpleXML child = new SimpleXML(nodeName);
    child.setParent(this);
    return child;
  }
 
  public Vector<SimpleXML> getChildren(String nodeName) {
    Vector<SimpleXML> ret = new Vector<SimpleXML>();
    try {
      Iterator<SimpleXML> i = fchild.iterator();
      while(i.hasNext()) {
        SimpleXML xml = i.next();
        if (xml.fname.equalsIgnoreCase(nodeName)) {
          ret.add(xml);
        }
      }
    } catch (Exception e) { }
 
    return ret;
  }
 
  public SimpleXML getNodeByPath(String nodePath, boolean createIfNotExists) {
 
    if (nodePath == null || nodePath.trim().equalsIgnoreCase("")) return null;
 
    SimpleXML ret = null;
 
    try {
      String[] bpath = nodePath.split("\\\\");
      if (bpath != null && bpath.length > 0) {
        int scnt = 0;
        for(int f = 0; f < bpath.length; ++f) {
          String c = bpath[f].trim();
          if (!= null && c.length() > 0) scnt++;
        }
 
        if (scnt > 0) {
          ret = this;
          for(int f = 0; f < bpath.length; ++f) {
            String c = bpath[f].trim();
            if (!= null && c.length() > 0) {
              Vector<SimpleXML> curnodes = ret.getChildren(c);
              if (curnodes != null && curnodes.size() > 0) {
                ret = curnodes.firstElement();
              } else {
                if (createIfNotExists) {
                  ret = ret.createChild(c);
                } else {
                  ret = null;
                  break;
                }
              }
            }
          }
        }
      }
    } catch (Exception e) {
    }
 
    return ret;
  }
 
  public static SimpleXML loadXml(String txxml) {
    SimpleXML ret = null;
    try {
      ret = SimpleXML.loadXml(new ByteArrayInputStream(txxml.getBytes()));
    } catch (Exception e) { }
 
    return ret;
  }
 
  public static SimpleXML loadXml(InputStream isxml) {
    SimpleXML ret = null;
    try {
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
 
      Document doc = dbBuilder.parse(isxml);
      ret = SimpleXML.fromNode(doc.getDocumentElement());
    } catch (Exception e) { }
 
    return ret;
  }
 
  void serializeNode(XmlSerializer ser) {
    try {
      ser.startTag("", fname);
      for(Entry<String, String> ee : fattrs.entrySet()) {
        ser.attribute("", ee.getKey(), ee.getValue());
      }
 
      if (fchild.size() > 0) {
        for(SimpleXML c: fchild) {
          c.serializeNode(ser);
        }
      } else {
        ser.text(ftext);
      }
      ser.endTag("", fname);
    } catch(Exception e) { 
      Log.d("app""e: " + e.toString());
    }
  }
 
  public static String saveXml(SimpleXML document) {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      XmlSerializer xs = Xml.newSerializer();
      xs.setOutput(baos, "Utf-8");
      xs.startDocument("Utf-8"true);
      document.serializeNode(xs);
      xs.endDocument();
      return new String(baos.toByteArray());
    } catch(Exception e) { }
 
    return "";
  }
}