/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Charles Reich */ package com.caucho.quercus.lib.simplexml; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import com.caucho.quercus.annotation.EntrySet; import com.caucho.quercus.annotation.Hide; import com.caucho.quercus.annotation.JsonEncode; import com.caucho.quercus.annotation.Name; import com.caucho.quercus.annotation.Optional; import com.caucho.quercus.annotation.ReturnNullAsFalse; import com.caucho.quercus.env.ArrayValue; import com.caucho.quercus.env.ArrayValueImpl; import com.caucho.quercus.env.BooleanValue; import com.caucho.quercus.env.Env; import com.caucho.quercus.env.JavaValue; import com.caucho.quercus.env.LongValue; import com.caucho.quercus.env.NullValue; import com.caucho.quercus.env.ObjectExtJavaValue; import com.caucho.quercus.env.QuercusClass; import com.caucho.quercus.env.StringValue; import com.caucho.quercus.env.Value; import com.caucho.util.L10N; import com.caucho.vfs.Path; import com.caucho.vfs.ReadStream; import com.caucho.vfs.WriteStream; /** * SimpleXMLElement object oriented API facade. * Also acts as the DOM document. */ public class SimpleXMLElement implements Map.Entry<String,Object> { private static final Logger log = Logger.getLogger(SimpleXMLElement.class.getName()); private static final L10N L = new L10N(SimpleXMLElement.class); protected SimpleXMLElement _parent; protected String _name; // mixed content is all combined protected StringValue _text; protected ArrayList<SimpleXMLElement> _children; protected ArrayList<SimpleXMLElement> _attributes; protected String _namespace; protected String _prefix; protected LinkedHashMap<String, String> _namespaceMap; protected Env _env; protected QuercusClass _cls; protected SimpleXMLElement(Env env, QuercusClass cls) { _env = env; _cls = cls; } protected SimpleXMLElement(Env env, QuercusClass cls, SimpleXMLElement parent, String name) { _env = env; _cls = cls; _parent = parent; _name = name; } protected SimpleXMLElement(Env env, QuercusClass cls, SimpleXMLElement parent, String name, String namespace) { _env = env; _cls = cls; _parent = parent; int p = name.indexOf(':'); if (p > 0) { _name = name.substring(p + 1); _prefix = name.substring(0, p); } else _name = name; if ("".equals(_name)) throw new IllegalArgumentException(L.l("name can't be empty")); _namespace = namespace; if (namespace != null) { if (_prefix == null) _prefix = ""; if (! hasNamespace(_prefix, namespace)) { String ns; if ("".equals(_prefix)) ns = "xmlns"; else ns = "xmlns:" + _prefix; addNamespaceAttribute(env, ns, namespace); } } } protected static Value create(Env env, QuercusClass cls, Value data, int options, boolean dataIsUrl, Value namespaceV, boolean isPrefix) { if (data.length() == 0) { env.warning(L.l("xml data must have length greater than 0")); return BooleanValue.FALSE; } try { String namespace = null; if (! namespaceV.isNull()) namespace = namespaceV.toString(); Node node = parse(env, data, options, dataIsUrl, namespace, isPrefix); if (node == null) { return BooleanValue.FALSE; } SimpleXMLElement elt = buildNode(env, cls, null, node, namespace, isPrefix); return wrapJava(env, cls, elt); } catch (IOException e) { env.warning(e); return BooleanValue.FALSE; } catch (ParserConfigurationException e) { env.warning(e); return BooleanValue.FALSE; } catch (SAXException e) { env.warning(e); return BooleanValue.FALSE; } } protected static Value wrapJava(Env env, QuercusClass cls, SimpleXMLElement element) { if (! "SimpleXMLElement".equals(cls.getName())) return new ObjectExtJavaValue(cls, element, cls.getJavaClassDef()); else return new JavaValue(env, element, cls.getJavaClassDef()); } protected QuercusClass getQuercusClass() { return _cls; } protected void setQuercusClass(QuercusClass cls) { _cls = cls; } protected void addNamespace(String prefix, String namespace) { if (prefix == null) prefix = ""; if (hasNamespace(prefix, namespace)) return; if (_namespaceMap == null) _namespaceMap = new LinkedHashMap<String,String>(); _namespaceMap.put(prefix, namespace); } protected boolean hasNamespace(String prefix, String namespace) { String uri = getNamespace(prefix); return uri != null && uri.equals(namespace); } protected String getNamespace(String prefix) { if (prefix == null) prefix = ""; if (_namespaceMap != null) { String uri = _namespaceMap.get(prefix); if (uri != null) return uri; } if (_parent != null) return _parent.getNamespace(prefix); else return null; } /** * Returns a new instance based on the xml from 'data'. * * @param env * @param data xml data * @param options * @param dataIsUrl * @param namespaceV * @param isPrefix */ public static Value __construct(Env env, Value data, @Optional int options, @Optional boolean dataIsUrl, @Optional Value namespaceV, @Optional boolean isPrefix) { QuercusClass cls = env.getCallingClass(); if (cls == null) cls = env.getClass("SimpleXMLElement"); return create(env, cls, data, options, dataIsUrl, namespaceV, isPrefix); } protected String getName() { return _name; } protected String getNamespace() { return _namespace != null ? _namespace : ""; } protected SimpleXMLElement getOwner() { return this; } protected boolean isElement() { return true; } protected boolean isNamespaceAttribute() { return false; } protected void setText(StringValue text) { _text = text.createStringBuilder().append(text); } protected void addText(StringValue text) { if (_text == null) _text = text.createStringBuilder(); _text = _text.append(text); } protected boolean isSameNamespace(String namespace) { if (namespace == null || namespace.length() == 0) return true; else if (_namespace != null && _namespace.length() > 0) return namespace.equals(_namespace); else if (_parent != null) return _parent.isSameNamespace(namespace); else return false; } protected boolean isSamePrefix(String prefix) { if (prefix == null || prefix.length() == 0) return true; return prefix.equals(_prefix); } protected SimpleXMLElement getAttribute(String name) { if (_attributes == null) return null; int size = _attributes.size(); for (int i = 0; i < size; i++) { SimpleXMLElement attr = _attributes.get(i); if (attr.getName().equals(name)) return attr; } return null; } private SimpleXMLElement getElement(String name) { if (_children == null) return null; int size = _children.size(); for (int i = 0; i < size; i++) { SimpleXMLElement elt = _children.get(i); if (elt.getName().equals(name)) return elt; } return null; } // // Map.Entry api for iterator // @Hide public String getKey() { return _name; } @Hide public Object getValue() { if (_children == null) return _text; else return wrapJava(_env, _cls, this); } @Hide public Object setValue(Object value) { return wrapJava(_env, _cls, this); } /** * Adds an attribute to this node. */ public void addAttribute(Env env, String name, StringValue value, @Optional String namespace) { if (namespace != null && namespace.length() > 0) { int colonIndex = name.indexOf(":"); // php/1x42 if (colonIndex <= 0 || colonIndex >= name.length()) { env.warning( L.l("Adding attributes with namespaces requires " + "attribute name with a prefix")); return; } } if (_attributes == null) _attributes = new ArrayList<SimpleXMLElement>(); SimpleXMLAttribute attr = new SimpleXMLAttribute(env, _cls, this, name, namespace, value); _attributes.add(attr); } /** * Adds a namespace attribute to this node. */ protected void addNamespaceAttribute(Env env, String name, String namespace) { if (namespace == null || "".equals(namespace)) return; if (_attributes == null) _attributes = new ArrayList<SimpleXMLElement>(); SimpleXMLAttribute attr = new SimpleXMLNamespaceAttribute(env, _cls, this, name, "", env.createString(namespace)); int p = name.indexOf(':'); if (p > 0) { String prefix = name.substring(p + 1); addNamespace(prefix, namespace); } else { if (_namespace == null) _namespace = namespace; addNamespace("", namespace); } for (int i = _attributes.size() - 1; i >= 0; i--) { SimpleXMLElement oldAttr = _attributes.get(i); if (oldAttr.getName().equals(name) && oldAttr.getNamespace().equals(namespace)) { _attributes.set(i, attr); return; } } _attributes.add(attr); } /** * Adds an attribute to this node. */ protected void addAttribute(SimpleXMLElement attr) { if (_attributes == null) _attributes = new ArrayList<SimpleXMLElement>(); _attributes.add(attr); } /** * Adds a child to this node. * * @param env * @param name of the child node * @param value of the text node of the child * @param namespace * @return */ public Value addChild(Env env, String name, String value, @Optional Value namespaceV) { String namespace; /* if (! namespaceV.isNull()) namespace = namespaceV.toString(); else namespace = _namespace; */ namespace = namespaceV.toString(); SimpleXMLElement child = new SimpleXMLElement(env, _cls, this, name, namespace); child.setText(env.createString(value)); addChild(child); return wrapJava(env, _cls, child); } private void addChild(SimpleXMLElement child) { if (_children == null) _children = new ArrayList<SimpleXMLElement>(); _children.add(child); } /** * Returns the attributes of this node. * * @param env * @param namespaceV * @param isPrefix */ public Value attributes(Env env, @Optional Value namespaceV, @Optional boolean isPrefix) { String namespace = null; if (! namespaceV.isNull()) namespace = namespaceV.toString(); SimpleXMLElement attrList = new SimpleXMLAttributeList(env, _cls, this, "#attrlist", namespace, null); if (_attributes != null) { for (SimpleXMLElement attr : _attributes) { if (attr.isSameNamespace(namespace) && ! attr.isNamespaceAttribute()) attrList.addAttribute(attr); } } return wrapJava(env, _cls, attrList); } /** * Returns all the children of this node, including the attributes of * this node. * * @param env * @param namespaceV * @param isPrefix */ public Value children(Env env, @Optional Value namespaceV, @Optional boolean isPrefix) { String namespace = null; if (! namespaceV.isNull()) namespace = namespaceV.toString(); SimpleXMLElement result = new SimpleXMLChildren(env, _cls, this, getName()); if (_attributes != null) { for (SimpleXMLElement attr : _attributes) { if (attr.isSameNamespace(namespace)) result.addAttribute(attr); } } if (_children != null) { for (SimpleXMLElement child : _children) { if (isPrefix) { if (child.isSamePrefix(namespace)) { result.addChild(child); } } else { if (child.isSameNamespace(namespace)) { result.addChild(child); } } } } return wrapJava(env, _cls, result); } // // XML parsing and generation // private static Node parse(Env env, Value data, int options, boolean dataIsUrl, String namespace, boolean isPrefix) throws IOException, ParserConfigurationException, SAXException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = null; if (dataIsUrl) { Path path = env.lookup(data.toStringValue()); // PHP throws an Exception instead if (path == null) { log.log(Level.FINE, L.l("Cannot read file/URL '{0}'", data)); env.warning(L.l("Cannot read file/URL '{0}'", data)); return null; } ReadStream is = path.openRead(); try { document = builder.parse(is); } finally { is.close(); } } else { StringReader reader = new java.io.StringReader(data.toString()); document = builder.parse(new InputSource(reader)); } NodeList childList = document.getChildNodes(); // php/1x70 for (int i = 0; i < childList.getLength(); i++) { if (childList.item(i).getNodeType() == Node.ELEMENT_NODE) return childList.item(i); } return childList.item(0); } private static SimpleXMLElement buildNode(Env env, QuercusClass cls, SimpleXMLElement parent, Node node, String namespace, boolean isPrefix) { if (node.getNodeType() == Node.TEXT_NODE) { String value = node.getNodeValue(); if (parent != null) { parent.addChild(new SimpleXMLText(env, cls, env.createString(value))); if (! isWhitespace(value)) parent.addText(env.createString(value)); } return parent; } /* NamedNodeMap attrMap = node.getAttributes(); Node namespaceAttr = attrMap.getNamedItem("xmlns"); if (namespaceAttr != null) namespace = namespaceAttr.getNodeValue(); */ SimpleXMLElement elt = new SimpleXMLElement(env, cls, parent, node.getNodeName(), namespace); if (parent != null) parent.addChild(elt); NamedNodeMap attrs = node.getAttributes(); if (attrs != null) { int length = attrs.getLength(); for (int i = 0; i < length; i++) { Attr attr = (Attr)attrs.item(i); if (attr.getName().startsWith("xmlns")) { elt.addNamespaceAttribute(env, attr.getName(), attr.getValue()); } else { elt.addAttribute(env, attr.getName(), env.createString(attr.getValue()), namespace); } } } for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { buildNode(env, cls, elt, child, namespace, isPrefix); } return elt; } /** * Converts node tree to a valid xml string. * * @return xml string */ @ReturnNullAsFalse public StringValue asXML(Env env) { if (_parent == null) { StringValue sb = env.createBinaryBuilder(); sb.append("<?xml version=\"1.0\"?>\n"); toXMLImpl(sb); sb.append("\n"); return sb; } else return toXML(env); } public StringValue toXML(Env env) { StringValue sb = env.createBinaryBuilder(); toXMLImpl(sb); return sb; } protected void toXMLImpl(StringValue sb) { sb.append("<"); boolean hasPrefix = false; if (_prefix != null && ! "".equals(_prefix) && getNamespace(_prefix) != null) hasPrefix = true; if (hasPrefix) { sb.append(_prefix); sb.append(":"); } sb.append(_name); /* if (_namespaceMap != null) { for (Map.Entry<String,String> entry : _namespaceMap.entrySet()) { if (! "".equals(entry.getKey())) { sb.append(" xmlns:"); sb.append(entry.getKey()); } else sb.append(" xmlns"); sb.append("=\""); sb.append(entry.getValue()); sb.append("\""); } } */ // add attributes, if any if (_attributes != null) { int size = _attributes.size(); for (int i = 0; i < size; i++) { SimpleXMLElement attr = _attributes.get(i); attr.toXMLImpl(sb); } } // add children, if any if (_children != null) { sb.append(">"); int size = _children.size(); for (int i = 0; i < size; i++) { SimpleXMLElement child = _children.get(i); child.toXMLImpl(sb); } } else if (_text == null || _text.length() == 0) { sb.append("/>"); return; } else { sb.append(">"); sb.append(_text); } // add closing tag sb.append("</"); if (hasPrefix) { sb.append(_prefix); sb.append(":"); } sb.append(_name); sb.append(">"); } /** * Returns the name of the node. * * @return name of the node */ @Name("getName") public String simplexml_getName() { return _name; } /** * Alias of getNamespaces(). */ public Value getDocNamespaces(Env env, @Optional boolean isRecursive) { return getNamespaces(env, isRecursive); } /** * Returns the namespaces used in this document. */ public Value getNamespaces(Env env, @Optional boolean isRecursive) { ArrayValue array = new ArrayValueImpl(); if (isRecursive) getNamespacesRec(env, array); else getNamespaces(env, array); return array; } private void getNamespacesRec(Env env, ArrayValue array) { getNamespaces(env, array); if (_children != null) { for (SimpleXMLElement child : _children) { child.getNamespacesRec(env, array); } } } private void getNamespaces(Env env, ArrayValue array) { if (_namespaceMap != null) { for (Map.Entry<String,String> entry : _namespaceMap.entrySet()) { StringValue name = env.createString(entry.getKey()); StringValue uri = env.createString(entry.getValue()); SimpleXMLAttribute attr = new SimpleXMLAttribute(env, _cls, this, entry.getKey()); attr.setText(uri); array.append(name, env.wrapJava(attr)); } } } /** * Runs an XPath expression on this node. * * @param env * @param expression * @return array of results * @throws XPathExpressionException */ public Value xpath(Env env, String expression) { try { XPath xpath = XPathFactory.newInstance().newXPath(); InputSource is = new InputSource(asXML(env).toInputStream()); NodeList nodes = (NodeList) xpath.evaluate(expression, is, XPathConstants.NODESET); int nodeLength = nodes.getLength(); if (nodeLength == 0) return NullValue.NULL; // There are matching nodes ArrayValue result = new ArrayValueImpl(); for (int i = 0; i < nodeLength; i++) { Node node = nodes.item(i); boolean isPrefix = node.getPrefix() != null; SimpleXMLElement xml = buildNode(env, _cls, null, nodes.item(i), node.getNamespaceURI(), isPrefix); result.put(wrapJava(env, _cls, xml)); } return result; } catch (XPathExpressionException e) { env.warning(e); log.log(Level.FINE, e.getMessage()); return NullValue.NULL; } } /** * Implementation for getting the indices of this class. * i.e. <code>$a->foo[0]</code> */ public Value __get(Env env, Value indexV) { if (indexV.isString()) { String name = indexV.toString(); SimpleXMLElement attr = getAttribute(name); if (attr == null) return NullValue.NULL; else return wrapJava(env, _cls, attr); } else if (indexV.isLongConvertible()) { int i = indexV.toInt(); if (i == 0) return wrapJava(env, _cls, getOwner()); else if (_parent == null) return NullValue.NULL; ArrayList<SimpleXMLElement> children = _parent._children; if (children != null) { int size = children.size(); for (int j = 0; j < size; j++) { SimpleXMLElement child = children.get(j); if (child.getName().equals(getName()) && i-- == 0) return wrapJava(env, _cls, child); } } return NullValue.NULL; } else return NullValue.NULL; } /** * Implementation for setting the indices of this class. * i.e. <code>$a->foo[0] = "hello"</code> */ public void __set(String name, StringValue value) { addAttribute(_env, name, value, null); } /** * Implementation for getting the indices of this class. * i.e. <code>count($a->foo[0])</code> */ public int __count(Env env) { ArrayList<SimpleXMLElement> children = _parent._children; if (children != null) { int size = children.size(); int count = 0; for (int j = 0; j < size; j++) { SimpleXMLElement child = children.get(j); if (child.getName().equals(getName())) count++; } return count; } else return 1; } /** * Implementation for getting the fields of this class. * i.e. <code>$a->foo</code> */ public Value __getField(String name) { SimpleXMLElement elt = getElement(name); if (elt != null) return wrapJava(_env, _cls, new SelectedXMLElement(_env, _cls, elt)); else return NullValue.NULL; } /** * Implementation for setting the fields of this class. * i.e. <code>$a->foo = "hello"</code> */ public void __setField(String name, Value value) { SimpleXMLElement child = getElement(name); if (child == null) { child = new SimpleXMLElement(_env, _cls, this, name); child.setText(value.toStringValue()); addChild(child); } else { child._children = null; child.setText(value.toStringValue()); } } /** * Required for 'foreach'. When only values are specified in * the loop <code>foreach($a as $b)</code>, this method * should return an iterator that contains Java objects * that will be wrapped in a Value. * * When a 'foreach' loop with name/value pairs * i.e. <code>foreach($a as $b=>$c)</code> * invokes this method, it expects an iterator that * contains objects that implement Map.Entry. */ public Iterator iterator() { // php/1x05 if (_children != null) return new ElementIterator(_children); else return null; } @EntrySet public Set<Map.Entry<Value,Value>> entrySet() { LinkedHashMap<Value,Value> map = new LinkedHashMap<Value,Value>(); if (_attributes != null) { ArrayValue array = new ArrayValueImpl(); for (SimpleXMLElement attr : _attributes) { StringValue value = attr._text; array.put(_env.createString(attr._name), value); } map.put(_env.createString("@attributes"), array); } boolean hasElement = false; if (_children != null) { for (SimpleXMLElement child : _children) { if (! child.isElement()) continue; hasElement = true; StringValue name = _env.createString(child.getName()); Value oldChild = map.get(name); Value childValue; if (child._text != null) childValue = child._text; else childValue = wrapJava(_env, _cls, child); if (oldChild == null) { map.put(name, childValue); } else if (oldChild.isArray()) { ArrayValue array = (ArrayValue) oldChild; array.append(childValue); } else { ArrayValue array = new ArrayValueImpl(); array.append(oldChild); array.append(childValue); map.put(name, array); } } } if (! hasElement && _text != null) { map.put(LongValue.ZERO, _text); } return map.entrySet(); } /** * var_dump() implementation */ public void varDumpImpl(Env env, Value obj, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet) throws IOException { String name = "SimpleXMLElement"; if (obj != null) name = obj.getClassName(); // php/1x33 if (_text != null && _children == null && _attributes == null) { if (depth > 0) { _text.varDump(env, out, depth, valueSet); return; } out.println("object(" + name + ") (1) {"); printDepth(out, 2 * (depth + 1)); out.println("[0]=>"); printDepth(out, 2 * (depth + 1)); _text.varDump(env, out, depth, valueSet); out.println(); printDepth(out, 2 * depth); out.print("}"); return; } Set<Map.Entry<Value,Value>> entrySet = entrySet(); out.println("object(" + name + ") (" + entrySet.size() + ") {"); for (Map.Entry<Value,Value> entry : entrySet) { printDepth(out, 2 * (depth + 1)); out.print("["); if (entry.getKey().isString()) out.print("\"" + entry.getKey() + "\""); else out.print(entry.getKey()); out.println("]=>"); printDepth(out, 2 * (depth + 1)); entry.getValue().varDump(env, out, depth + 1, valueSet); out.println(); } printDepth(out, 2 * depth); out.print('}'); } @JsonEncode public void jsonEncode(Env env, StringValue sb) { sb.append('{'); jsonEncodeImpl(env, sb, true); sb.append('}'); } protected void jsonEncodeImpl(Env env, StringValue sb, boolean isTop) { if (! isTop) { sb.append('"'); sb.append(getName()); sb.append('"'); sb.append(':'); } if (_attributes == null && _children != null && _children.size() == 1 && ! _children.get(0).isElement()) { _children.get(0).jsonEncodeImpl(env, sb, false); } else { int length = 0; boolean hasChildren = _attributes != null && _children != null; if (hasChildren) sb.append('{'); if (_attributes != null) { length++; sb.append("\"@attributes\""); sb.append(':'); sb.append('{'); for (SimpleXMLElement attribute : _attributes) { attribute.jsonEncodeImpl(env, sb, false); } sb.append('}'); } if (_children != null) { for (SimpleXMLElement child : _children) { if (! child.isElement()) continue; if (length++ > 0) sb.append(','); child.jsonEncodeImpl(env, sb, false); } } if (hasChildren) sb.append('}'); } } protected void printDepth(WriteStream out, int depth) throws IOException { for (int i = 0; i < depth; i++) out.print(' '); } public StringValue __toString(Env env) { if (_text != null) return _text; else return env.getEmptyString(); } private static boolean isWhitespace(String text) { for (int i = text.length() - 1; i >= 0; i--) { if (! isWhitespace(text.charAt(i))) return false; } return true; } private static boolean isWhitespace(int ch) { return ch <= 0x20 && (ch == 0x20 || ch == 0x9 || ch == 0xa || ch == 0xd); } class ElementIterator implements Iterator { private ArrayList<SimpleXMLElement> _children; private int _index; private int _size; ElementIterator(ArrayList<SimpleXMLElement> children) { _children = children; _size = children.size(); } public boolean hasNext() { for (; _index < _size; _index++) { SimpleXMLElement elt = _children.get(_index); if (elt.isElement()) return true; } return false; } public Object next() { while (_index < _size) { SimpleXMLElement elt = _children.get(_index++); if (elt.isElement()) return elt; } return null; } public void remove() { } } }