/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.core.util; import static org.xmind.core.internal.dom.DOMConstants.ATTR_ID; import static org.xmind.core.internal.dom.DOMConstants.TAG_TOPIC; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xmind.core.Core; import org.xmind.core.CoreException; import org.xmind.core.IAdaptable; import org.xmind.core.ITopic; import org.xmind.core.internal.dom.INodeAdaptableProvider; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; public class DOMUtils { private static class ElementIterator implements Iterator<Element> { private String tagName; private Node child; // private NodeList children; // // private int index; private Element next; public ElementIterator(Node parent) { this(parent, null); } public ElementIterator(Node parent, String tagName) { this.tagName = tagName; this.child = parent.getFirstChild(); // this.children = parent.getChildNodes(); // this.index = 0; this.next = findNextElement(); } private Element findNextElement() { if (child == null) { next = null; } else { while (child != null && !isElementByTag(child, tagName)) { child = child.getNextSibling(); } if (child != null) { next = (Element) child; child = child.getNextSibling(); } else { next = null; } } // for (int i = index; i < children.getLength(); i++) { // Node n = children.item(i); // if (isElementByTag(n, tagName)) { // next = (Element) n; // index = i + 1; // return next; // } // } return next; } public boolean hasNext() { return next != null; } public Element next() { Element result = next; next = findNextElement(); return result; } public void remove() { } } public static class AdaptableIterator<T extends IAdaptable> implements Iterator<T> { private Node node; private String tagName; private INodeAdaptableProvider provider; private boolean reversed; private T next; public AdaptableIterator(Node parent, String tagName, INodeAdaptableProvider provider, boolean reversed) { this.tagName = tagName; this.provider = provider; this.reversed = reversed; this.node = reversed ? parent.getLastChild() : parent.getFirstChild(); this.next = findNext(); } @SuppressWarnings("unchecked") private T findNext() { while (node != null) { IAdaptable obj; if (isElementByTag(node, tagName)) { obj = provider.getAdaptable(node); } else { obj = null; } node = reversed ? node.getPreviousSibling() : node.getNextSibling(); if (obj != null) return (T) obj; } return null; } public boolean hasNext() { return next != null; } public T next() { T n = next; next = findNext(); return n; } public void remove() { } } private static class DelegateSet<T> extends AbstractSet<T> { private Collection<T> c; public DelegateSet(Collection<T> c) { this.c = c; } public Iterator<T> iterator() { return c.iterator(); } public int size() { return c.size(); } } private static final ErrorHandler NULL_ERROR_HANDLER = new ErrorHandler() { public void warning(SAXParseException exception) throws SAXException { } public void fatalError(SAXParseException exception) throws SAXException { } public void error(SAXParseException exception) throws SAXException { } }; // private static Transformer transformer = null; // private static DocumentBuilder documentBuilder = null; private DOMUtils() { } public static Transformer getDefaultTransformer() throws CoreException { try { return TransformerFactory.newInstance().newTransformer(); } catch (TransformerException e) { throw new CoreException(Core.ERROR_FAIL_ACCESS_XML_TRANSFORMER, e); } } public static DocumentBuilder getDefaultDocumentBuilder() throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { factory.setAttribute( "http://apache.org/xml/features/continue-after-fatal-error", //$NON-NLS-1$ true); } catch (Exception e) { // ignore } try { factory.setFeature( "http://apache.org/xml/features/disallow-doctype-decl", //$NON-NLS-1$ true); } catch (Exception e) { // ignore } try { factory.setFeature( "http://xml.org/sax/features/external-parameter-entities", //$NON-NLS-1$ false); } catch (Exception e) { // ignore } try { factory.setFeature( "http://xml.org/sax/features/external-general-entities", //$NON-NLS-1$ false); } catch (Exception e) { // ignore } factory.setXIncludeAware(false); factory.setExpandEntityReferences(false); factory.setNamespaceAware(true); factory.setIgnoringElementContentWhitespace(true); DocumentBuilder documentBuilder = factory.newDocumentBuilder(); documentBuilder.setErrorHandler(NULL_ERROR_HANDLER); return documentBuilder; } public static String toString(Node node) { if (node == null) return "null"; //$NON-NLS-1$ StringBuilder sb = new StringBuilder(); sb.append('['); sb.append(node.getNodeName()); NamedNodeMap attributes = node.getAttributes(); if (attributes != null && attributes.getLength() > 0) { for (int i = 0; i < attributes.getLength(); i++) { sb.append(' '); Node item = attributes.item(i); sb.append(item.getNodeName()); sb.append('='); sb.append('"'); sb.append(item.getNodeValue()); sb.append('"'); } } sb.append(']'); return sb.toString(); } public static Document doCreateDocument() throws ParserConfigurationException { return getDefaultDocumentBuilder().newDocument(); } /** * @return */ public static Document createDocument() { try { return getDefaultDocumentBuilder().newDocument(); } catch (ParserConfigurationException e) { return null; } } /** * @param docTag * =manifest * @return */ public static Document createDocument(String docTag) { Document ret = createDocument(); createElement(ret, docTag); return ret; } /** * @param is * @return * @throws IOException */ public static Document loadDocument(InputStream is) throws IOException { if (is == null) throw new IllegalArgumentException(); try { return getDefaultDocumentBuilder().parse(is); } catch (IOException e) { throw e; } catch (Exception e) { throw new IOException(e.getMessage()); } } public static Document loadDocument(byte[] bytes) throws IOException { if (bytes == null) throw new IllegalArgumentException(); ByteArrayInputStream in = new ByteArrayInputStream(bytes); try { return loadDocument(in); } finally { in.close(); } } public static void save(Node dom, OutputStream out, boolean closeOnFinish) throws IOException, CoreException { save(getDefaultTransformer(), dom, out, closeOnFinish); } //manifest, zob, false public static void save(IAdaptable adaptable, OutputStream out, boolean closeOnFinish) throws IOException, CoreException { save(getDefaultTransformer(), adaptable, out, closeOnFinish); } public static void save(Transformer t, IAdaptable adaptable, OutputStream out, boolean closeOnFinish) throws IOException { Node dom = (Node) adaptable.getAdapter(Node.class); if (dom != null) { save(t, dom, out, closeOnFinish); } } public static void save(Transformer t, Node dom, OutputStream out, boolean closeOnFinish) throws IOException { try { t.transform(new DOMSource(dom), new StreamResult(out)); } catch (TransformerException e) { throw new IOException(e.getMessage()); } finally { if (closeOnFinish) { out.close(); } } } /** * @param parent * @param tag * =manifest * @return */ public static Element createElement(Node parent, String tag) { Document doc = parent.getNodeType() == Node.DOCUMENT_NODE ? (Document) parent : parent.getOwnerDocument(); Element e = doc.createElement(tag); parent.appendChild(e); return e; } public static String getPrefix(String qualifiedName) { int index = qualifiedName.indexOf(':'); if (index >= 0) return qualifiedName.substring(0, index); return null; } public static String getLocalName(String qualifiedName) { int index = qualifiedName.indexOf(':'); if (index >= 0) return qualifiedName.substring(index + 1); return qualifiedName; } public static String getQualifiedName(String prefix, String localName) { return prefix + ":" + localName; //$NON-NLS-1$ } /** * @param parent * @param tag * @param text * @return */ public static Element createText(Node parent, String tag, String text) { Element e = createElement(parent, tag); Node t = parent.getOwnerDocument().createTextNode(text); e.appendChild(t); return e; } /** * @param parent * @param name * @param value * @return */ public static Attr createAttr(Element parent, String name, Object value) { if (value == null) return null; Attr a = parent.getOwnerDocument().createAttribute(name); a.setNodeValue(value.toString()); parent.getAttributes().setNamedItem(a); return a; } /** * @param element * @param attrName * @param value */ public static void setAttribute(Element element, String attrName, Object value) { if (value != null) { element.setAttribute(attrName, value.toString()); } else if (element.hasAttribute(attrName)) { element.removeAttribute(attrName); } } public static String getAttribute(Element element, String attrName) { if (!element.hasAttribute(attrName)) { String localName = getLocalName(attrName); if (!attrName.equals(localName)) return getAttribute(element, localName); return null; } return element.getAttribute(attrName); } public static boolean isElementByTag(Node node, String tagName) { if (!(node instanceof Element)) return false; if (tagName == null) return true; Element element = (Element) node; String tag = element.getTagName(); return tag.equals(tagName) || getLocalName(tag).equals(getLocalName(tagName)); } public static Iterator<Element> childElementIter(Node parent) { return new ElementIterator(parent); } public static Iterator<Element> childElementIterByTag(Node parent, String tagName) { return new ElementIterator(parent, tagName); } public static boolean hasChildElement(Node parent) { return childElementIter(parent).hasNext(); } public static boolean hasChildElementByTag(Node parent, String tagName) { return childElementIterByTag(parent, tagName).hasNext(); } public static int getElementIndex(Node parent, String tagName, Element child) { Iterator<Element> it = childElementIterByTag(parent, tagName); for (int i = 0; it.hasNext(); i++) { if (it.next() == child) return i; } return -1; } public static int getNodeIndex(Node parent, Node child) { NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { if (child == children.item(i)) return i; } return -1; } public static Element getFirstChildElement(Node parent) { return childElementIter(parent).next(); } public static Element getFirstChildElementByTag(Node parent, String tag) { return childElementIterByTag(parent, tag).next(); } public static int getNumChildElementsByTag(Node parent, String tag) { int num = 0; Iterator<Element> it = childElementIterByTag(parent, tag); while (it.hasNext()) { it.next(); num++; } return num; } /** * @param parent * @param tag * @return */ public static Element[] getChildElementsByTag(Node parent, String tag) { List<Element> list = new ArrayList<Element>( parent.getChildNodes().getLength()); Iterator<Element> it = childElementIterByTag(parent, tag); while (it.hasNext()) { list.add(it.next()); } return list.toArray(new Element[list.size()]); } public static Element[] getChildElements(Node parent) { List<Element> list = new ArrayList<Element>( parent.getChildNodes().getLength()); Iterator<Element> it = childElementIter(parent); while (it.hasNext()) { list.add(it.next()); } return list.toArray(new Element[list.size()]); } public static Element ensureChildElement(Node parent, String tagName) { Element ele; if (parent.getNodeType() == Node.DOCUMENT_NODE) { ele = ((Document) parent).getDocumentElement(); } else { ele = getFirstChildElementByTag(parent, tagName); } if (ele == null) { ele = createElement(parent, tagName); } return ele; } public static void createCentalTopicElement(Node parent, ITopic topic) { if (topic == null) createElement(parent, TAG_TOPIC); else createElement(parent, topic.getTitleText()); } public static <T extends IAdaptable> List<T> getChildList(Element element, String childTag, INodeAdaptableProvider finder) { List<T> list = getChildren(element, childTag, finder); return list; } public static <T extends IAdaptable> Set<T> getChildSet(Element element, String childTag, INodeAdaptableProvider finder) { List<T> list = getChildren(element, childTag, finder); return unmodifiableSet(list); } public static <T extends IAdaptable> Iterator<T> getChildIterator( Element element, String childTag, final INodeAdaptableProvider finder, final Class<T> childClass) { final Iterator<Element> it = childElementIterByTag(element, childTag); return new Iterator<T>() { T next = findNext(); private T findNext() { while (it.hasNext()) { Element e = it.next(); IAdaptable a = finder.getAdaptable(e); if (a != null && childClass.isInstance(a)) { return childClass.cast(a); } } return null; } public boolean hasNext() { return next != null; } public T next() { T n = next; next = findNext(); return n; } public void remove() { throw new UnsupportedOperationException(); } }; } public static <T> Iterator<T> emptyIter() { return new Iterator<T>() { public boolean hasNext() { return false; } public T next() { return null; } public void remove() { } }; } @SuppressWarnings("unchecked") public static <T extends IAdaptable> List<T> getChildren(Element element, String childTag, INodeAdaptableProvider finder) { ArrayList<T> list = new ArrayList<T>( element.getChildNodes().getLength()); Iterator<Element> it = childElementIterByTag(element, childTag); while (it.hasNext()) { Element child = it.next(); IAdaptable a = finder.getAdaptable(child); if (a != null) { list.add((T) a); } } return list; } @SuppressWarnings("unchecked") public static <T extends IAdaptable> List<T> getChildren(Node parent, INodeAdaptableProvider finder) { NodeList childNodes = parent.getChildNodes(); int num = childNodes.getLength(); ArrayList<T> list = new ArrayList<T>(num); for (int i = 0; i < num; i++) { Node n = childNodes.item(i); IAdaptable a = finder.getAdaptable(n); if (a != null) { list.add((T) a); } } return list; } public static <T> Set<T> unmodifiableSet(Collection<T> c) { return new DelegateSet<T>(c); } /** * @param parent * @param tag * @return */ public static String getTextContentByTag(Node parent, String tag) { Element ele = getFirstChildElementByTag(parent, tag); if (ele == null) return null; Node firstChild = ele.getFirstChild(); return firstChild == null ? null : firstChild.getTextContent(); } /** * @param titleNode * @param textContent */ public static void setText(Node titleNode, String textContent) { Node textNode = findTextNode(titleNode); if (textNode != null) { if (textContent == null) { titleNode.removeChild(textNode); } else { textNode.setTextContent(textContent); } } else { titleNode.setTextContent(textContent); } } public static Node findTextNode(Node node) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node c = children.item(i); if (c.getNodeType() == Node.TEXT_NODE) return c; } return null; } /** * @param parent * @param tag * @param text */ public static void setText(Node parent, String tag, String text) { Element titleElement = getFirstChildElementByTag(parent, tag); if (titleElement == null) { if (text != null) createText(parent, tag, text); } else { setText(titleElement, text); if (!titleElement.hasChildNodes() && !titleElement.hasAttributes()) { parent.removeChild(titleElement); } } } /** * @param element * @return */ public static Element addIdAttribute(Element element) { if (!element.hasAttribute(ATTR_ID)) { element.setAttribute(ATTR_ID, Core.getIdFactory().createId()); element.setIdAttribute(ATTR_ID, true); } return element; } public static String replaceId(Element element) { String newId = Core.getIdFactory().createId(); replaceId(element, newId); return newId; } public static Element replaceId(Element element, String newId) { if (newId == null) return element; element.setAttribute(ATTR_ID, newId); element.setIdAttribute(ATTR_ID, true); return element; } public static boolean isOrphanNode(Node node) { if (node == null) return true; if (node.getNodeType() == Node.DOCUMENT_NODE) return false; return isOrphanNode(node.getParentNode()); } public static Document getOwnerDocument(Node node) { return node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node : node.getOwnerDocument(); } }