Pull to refresh

Простой класс для работы с XML

Reading time14 min
Views22K
Предисловие


Как-то давно, я столкнулся с проблемой под названием «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 "";
  }
}
 
Tags:
Hubs:
Total votes 8: ↑6 and ↓2+4
Comments27

Articles