Предисловие
Как-то давно, я столкнулся с проблемой под названием «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 (c != null && c.length() > 0) scnt++;
}
if (scnt > 0) {
ret = this;
for(int f = 0; f < bpath.length; ++f) {
String c = bpath[f].trim();
if (c != 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 "";
}
}
