/******************************************************************************* * Copyright (c) 2004, 2008 John Krasnay and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * John Krasnay - initial API and implementation *******************************************************************************/ package net.sf.vex.dom; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import net.sf.vex.undo.CannotRedoException; import net.sf.vex.undo.CannotUndoException; import net.sf.vex.undo.IUndoableEdit; /** * <code>Element</code> represents a tag in an XML document. Methods * are available for managing the element's attributes and children. */ public class Element extends Node implements Cloneable { private String name; private Element parent = null; private List children = new ArrayList(); private Map attributes = new HashMap(); /** * Class constructor. * @param name element name */ public Element(String name) { this.name = name; } /** * Adds the given child to the end of the child list. * Sets the parent attribute of the given element to this element. */ public void addChild(Element child) { this.children.add(child); child.parent = this; } /** * Clones the element and its attributes. The returned element has * no parent or children. */ public Object clone() { try { Element element = new Element(this.getName()); for (Iterator it = this.attributes.keySet().iterator(); it .hasNext();) { String attrName = (String) it.next(); element.setAttribute(attrName, (String) this.attributes .get(attrName)); } return element; } catch (DocumentValidationException ex) { ex.printStackTrace(); return null; } } /** * Returns the value of an attribute given its name. If no such * attribute exists, returns null. * * @param name Name of the attribute. */ public String getAttribute(String name) { return (String) attributes.get(name); } /** * Returns an array of names of the attributes in the element. */ public String[] getAttributeNames() { Collection names = this.attributes.keySet(); return (String[]) names.toArray(new String[names.size()]); } /** * Returns an iterator over the children. Used by * <code>Document.delete</code> to safely delete children. */ public Iterator getChildIterator() { return this.children.iterator(); } /** * Returns an array of the elements children. */ public Element[] getChildElements() { int size = this.children.size(); return (Element[]) this.children.toArray(new Element[size]); } /** * Returns an array of nodes representing the content of this element. * The array includes child elements and runs of text returned as * <code>Text</code> objects. */ public Node[] getChildNodes() { return Document.createNodeArray( this.getContent(), this.getStartOffset() + 1, this.getEndOffset(), this.getChildElements()); } /** * @return The document to which this element belongs. * Returns null if this element is part of a document * fragment. */ public Document getDocument() { Element root = this; while (root.getParent() != null) { root = root.getParent(); } if (root instanceof RootElement) { return ((RootElement) root).getDocument(); } else { return null; } } /** * Returns the name of the element. */ public String getName() { return this.name; } /** * Returns the parent of this element, or null if this is the root element. */ public Element getParent() { return this.parent; } public String getText() { String s = super.getText(); StringBuffer sb = new StringBuffer(s.length()); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c != 0) { sb.append(c); } } return sb.toString(); } /** * Inserts the given element as a child at the given child index. * Sets the parent attribute of the given element to this element. */ void insertChild(int index, Element child) { this.children.add(index, child); child.parent = this; } /** * Returns true if the element has no content. */ public boolean isEmpty() { return this.getStartOffset() + 1 == this.getEndOffset(); } /** * Removes the given attribute from the array. * * @param name name of the attribute to remove. */ public void removeAttribute(String name) throws DocumentValidationException { String oldValue = this.getAttribute(name); String newValue = null; if (oldValue != null) { this.attributes.remove(name); } Document doc = this.getDocument(); if (doc != null) { // doc may be null, e.g. when we're cloning an element // to produce a document fragment IUndoableEdit edit = doc.isUndoEnabled() ? new AttributeChangeEdit(name, oldValue, newValue) : null; doc.fireAttributeChanged(new DocumentEvent( doc, this, name, oldValue, newValue, edit)); } } /** * Sets the value of an attribute for this element. * * @param name Name of the attribute to be set. * @param value New value for the attribute. If null, this call * has the same effect as removeAttribute(name). */ public void setAttribute(String name, String value) throws DocumentValidationException { String oldValue = this.getAttribute(name); if (value == null && oldValue == null) { return; } else if (value == null) { this.removeAttribute(name); } else if (value.equals(oldValue)) { return; } else { this.attributes.put(name, value); Document doc = this.getDocument(); if (doc != null) { // doc may be null, e.g. when we're cloning an element // to produce a document fragment IUndoableEdit edit = doc.isUndoEnabled() ? new AttributeChangeEdit(name, oldValue, value) : null; doc.fireAttributeChanged(new DocumentEvent( doc, this, name, oldValue, value, edit)); } } } /** * Sets the parent of this element. * * @param parent Parent element. */ public void setParent(Element parent) { this.parent = parent; } public String toString() { StringBuffer sb = new StringBuffer(); sb.append("<"); sb.append(this.getName()); String[] attrs = this.getAttributeNames(); for (int i = 0; i < attrs.length; i++) { if (i > 0) { sb.append(","); } sb.append(" "); sb.append(attrs[i]); sb.append("=\""); sb.append(this.getAttribute(attrs[i])); sb.append("\""); } sb.append("> ("); sb.append(this.getStartPosition()); sb.append(","); sb.append(this.getEndPosition()); sb.append(")"); return sb.toString(); } //========================================================= PRIVATE private class AttributeChangeEdit implements IUndoableEdit { private String name; private String oldValue; private String newValue; public AttributeChangeEdit(String name, String oldValue, String newValue) { this.name = name; this.oldValue = oldValue; this.newValue = newValue; } public boolean combine(IUndoableEdit edit) { return false; } public void undo() throws CannotUndoException { Document doc = getDocument(); try { doc.setUndoEnabled(false); setAttribute(name, oldValue); } catch (DocumentValidationException ex) { throw new CannotUndoException(); } finally { doc.setUndoEnabled(true); } } public void redo() throws CannotRedoException { Document doc = getDocument(); try { doc.setUndoEnabled(false); setAttribute(name, newValue); } catch (DocumentValidationException ex) { throw new CannotUndoException(); } finally { doc.setUndoEnabled(true); } } } }