/** * Copyright (C) 2000 - 2009 Silverpeas * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * As a special exception to the terms and conditions of version 3.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * Open Source Software ("FLOSS") applications as described in Silverpeas's * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * "http://repository.silverpeas.com/legal/licensing" * * This program 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. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.silverpeas.xml; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.jdom.Attribute; import org.jdom.Element; public class XmlTreeHandler { /** * The consult mode */ public static final char MODE_SELECT = 'S'; /** * Every named node accessed is created after the existing ones */ public static final char MODE_INSERT = 'I'; /** * Every named node accessed is deleted */ public static final char MODE_DELETE = 'D'; /** * If the named node exists, it is accessed If the named node does not exist, it is created */ public static final char MODE_UPDATE = 'U'; /** * Every named node accessed becomes unique If it exists, the others ones are deleted If it does * not exist, it is created */ public static final char MODE_UNIQUE = '1'; public XmlTreeHandler() { this(null, MODE_SELECT); } public XmlTreeHandler(Element startingElement) { this(startingElement, MODE_SELECT); } public XmlTreeHandler(Element startingElement, char mode) { setStartingElement(startingElement); setMode(mode); } public void setStartingElement(Element startingElement) { removeAllStates(); _startingElement = startingElement; eraseAttribute(); eraseName(); setElement(_startingElement); syncIElementsWithElementAndName(); setCurrentElementAsCousinsAncestor(); pushState(); } public void setMode(char mode) { _mode = mode; } public char getMode() { return _mode; } public Element getStartingElement() { return _startingElement; } private Element _startingElement = null; private char _mode; // ################################### private void setName(String name) { _name = name; } private String getName() { return _name; } private boolean hasName() { return getName() != null; } private void eraseName() { if (hasName()) { setName(null); } } private String _name = null; // ################################### private void setElement(Element e) { _element = e; } private Element getElement() { return _element; } private boolean hasElement() { return getElement() != null; } private void eraseElement() { if (hasElement()) { setElement(null); } } private Element _element = null; // ################################### private void setIElements(Iterator iElements) { _iElements = iElements; } private Iterator getIElements() { return _iElements; } private boolean hasIElements() { return getIElements() != null; } private void eraseIElements() { if (hasIElements()) { _iElements = null; } } private Iterator _iElements = null; // ################################### private void setNextIElementsAsElement() { if (hasIElements()) { // all modes but MODE_SELECT do sth special only when a name is present switch (getMode()) { case MODE_UNIQUE: case MODE_DELETE: if (hasName()) { eraseAll(); break; } case MODE_UPDATE: if (hasName()) { if (getIElements().hasNext()) { setElement((Element) getIElements().next()); break; } // else MODE_INSERT } case MODE_INSERT: if (hasName()) { Element e = new Element(getName()); getElement().getParentElement().getChildren(getName()).add(e); setElement(e); syncIElementsWithElementAndName(); break; } case MODE_SELECT: if (getIElements().hasNext()) { setElement((Element) getIElements().next()); } else { eraseAll(); } break; } } else { eraseAll(); } } private void syncIElementsWithElementAndName() { if (hasElement()) { if (getElement().isRootElement()) { eraseIElements(); } else { List children = null; if (hasName()) { children = getElement().getParentElement().getChildren(getName()); } else { children = getElement().getParentElement().getChildren(); } setIElements(children.iterator()); while (!((Object) getElement()).equals(getIElements().next())) { } } } } // ################################### private void setAttribute(Attribute a) { _attribute = a; } private Attribute getAttribute() { return _attribute; } private boolean hasAttribute() { return getAttribute() != null; } private void eraseAttribute() { if (hasAttribute()) { setAttribute(null); } } private Attribute _attribute = null; // ################################### private void setCousinsAncestor(Element ancestor) { _cousinsAncestor = ancestor; } private Element getCousinsAncestor() { return _cousinsAncestor; } private void eraseCousinsAncestor() { _cousinsAncestor = null; } private boolean hasCousinsAncestor() { return _cousinsAncestor != null; } private Element _cousinsAncestor = null; // ################################### private boolean isElementCousinsAncestor() { if (!hasElement()) { return false; } if (!hasCousinsAncestor()) { return getElement().isRootElement(); } return ((Object) getCousinsAncestor()).equals(getElement()); } private boolean isParentCousinsAncestor() { if (!hasElement()) { return false; } if (!hasCousinsAncestor()) { return hasParent() && getElement().getParentElement().isRootElement(); } Element a = getCousinsAncestor(); Element p = getElement().getParentElement(); return ((Object) a).equals(p); } private void setElementCousinsAncestor() { if (hasElement()) { setCousinsAncestor(getElement()); } else { eraseCousinsAncestor(); } } private void setParentCousinsAncestor() { if (hasElement() && hasParent() && !getElement().getParentElement().isRootElement()) { setCousinsAncestor(getElement().getParentElement()); } else { eraseCousinsAncestor(); } } // ################################### private void eraseAll() { eraseAttribute(); eraseName(); eraseElement(); eraseIElements(); eraseCousinsAncestor(); } // ################################### public boolean isElement() { return hasElement(); } public boolean isAttribute() { return hasAttribute(); } public Element getCurrentElement() { if (isElement()) { return getElement(); } return null; } public Attribute getCurrentAttribute() { if (isAttribute()) { return getAttribute(); } return null; } public boolean currentNodeExists() { return hasElement() || hasAttribute(); } public Object getCurrentNode() { if (isElement()) { return getElement(); } if (isAttribute()) { return getAttribute(); } return null; } public String getCurrentNodeValue() { if (isElement()) { return getElement().getText(); } if (isAttribute()) { return getAttribute().getValue(); } return null; } public void setCurrentNodeValue(String value) { if (isElement()) { getElement().setText(value); } if (isAttribute()) { getAttribute().setValue(value); } } public boolean hasCurrentNodeValue() { String value = getCurrentNodeValue(); return value != null && !value.trim().equals(""); } // ################################### public boolean isCurrentElementRoot() { if (!isElement()) { return false; } return getCurrentElement().isRootElement(); } public void gotoRoot() { if (getStartingElement() != null) { eraseAll(); setElement(getStartingElement().getDocument().getRootElement()); syncIElementsWithElementAndName(); } } public void returnToStartingElement() { returnToStartingState(); } public void setCurrentElementAsStartingElement() { removeAllStates(); if (hasElement()) { _startingElement = getElement(); setCurrentElementAsCousinsAncestor(); } else { eraseAll(); _startingElement = null; } pushState(); } public void setNameFromCurrentElement() { if (hasElement()) { setName(getElement().getName()); syncIElementsWithElementAndName(); } } // ################################### public boolean hasParent() { return isElement() && !isCurrentElementRoot(); } public void gotoParent() { if (hasParent()) { eraseName(); if (isElementCousinsAncestor()) { setParentCousinsAncestor(); } setElement(getCurrentElement().getParentElement()); syncIElementsWithElementAndName(); } else { eraseAll(); } } // ################################### public boolean hasNextSibling() { if (hasIElements()) { return getIElements().hasNext(); } return false; } public void gotoNextSibling() { boolean changeCousinsAncestor = isElementCousinsAncestor(); setNextIElementsAsElement(); if (changeCousinsAncestor && hasElement()) { setCousinsAncestor(getElement()); } } // ################################### private List getNamedChildren() { if (!isElement()) { return null; } List children = null; if (hasName()) { children = getCurrentElement().getChildren(getName()); switch (getMode()) { case MODE_SELECT: break; case MODE_UNIQUE: // no children => MODE_UPDATE => MODE_INSERT if (!children.isEmpty()) { if (children.size() > 1) { Iterator i = children.iterator(); boolean passedFirst = false; while (i.hasNext()) { Element e = (Element) i.next(); if (passedFirst) { getCurrentElement().removeContent(e); } else { passedFirst = true; } } } break; } case MODE_UPDATE: // if no children exist => MODE_INSERT if (!children.isEmpty()) { break; } case MODE_INSERT: children.add(new Element(getName())); break; case MODE_DELETE: // actually removes the children getCurrentElement().removeChildren(getName()); // to tell that there is no children children.clear(); break; } } else { children = getCurrentElement().getChildren(); } if (children.isEmpty()) { return null; } return children; } private void setFirstNamedChild() { List children = getNamedChildren(); if (children == null) { eraseAll(); } else { setElement((Element) children.iterator().next()); syncIElementsWithElementAndName(); } } // ################################### public boolean hasChildren() { if (isElement()) { return getCurrentElement().getChildren().isEmpty(); } return false; } public boolean hasChildren(String name) { if (hasChildren()) { List children = getElement().getChildren(name); return (children != null) && (!children.isEmpty()); } return false; } public void gotoFirstChild() { eraseName(); setFirstNamedChild(); } public void gotoFirstChild(String name) { setName(name); setFirstNamedChild(); } // ################################### public void gotoAttribute(String name) { if (isElement()) { Attribute a = getCurrentElement().getAttribute(name); switch (getMode()) { case MODE_SELECT: break; case MODE_UNIQUE: case MODE_UPDATE: case MODE_INSERT: if (a == null) { a = new Attribute(name, "PLEASE GIVE ME A VALUE"); getCurrentElement().setAttribute(a); } break; case MODE_DELETE: if (a != null) { getCurrentElement().removeAttribute(name); a = null; } break; } if (a == null) { eraseAll(); } else { eraseName(); eraseIElements(); eraseElement(); setAttribute(a); } } else { eraseAll(); } } // ################################### public static final char TYPE_ATTRIBUTE = 'A'; public static final char TYPE_ELEMENT = 'E'; public void gotoFirstChildNode(char nodeType, String nodeName) { switch (nodeType) { case TYPE_ATTRIBUTE: gotoAttribute(nodeName); break; case TYPE_ELEMENT: gotoFirstChild(nodeName); break; default: eraseAll(); break; } } public void gotoNextSiblingNode() { if (isElement()) { gotoNextSibling(); } else { eraseAll(); } } public boolean hasNextSiblingNode() { boolean found; char bakMode = getMode(); pushState(); gotoNextSiblingNode(); found = currentNodeExists(); popState(); setMode(bakMode); return found; } // ##################################### public void pushState() { if (_stateStack == null) { _stateStack = new LinkedList(); } _stateStack.addFirst(new Object[] { getAttribute(), getElement(), getName(), getStartingElement(), getCousinsAncestor() }); } public void popState() { if (_stateStack != null && _stateStack.size() >= 1) { setAttribute((Attribute) ((Object[]) _stateStack.getFirst())[0]); setElement((Element) ((Object[]) _stateStack.getFirst())[1]); setName((String) ((Object[]) _stateStack.getFirst())[2]); syncIElementsWithElementAndName(); _startingElement = (Element) ((Object[]) _stateStack.getFirst())[3]; setCousinsAncestor((Element) ((Object[]) _stateStack.getFirst())[4]); _stateStack.removeFirst(); } } private void removeAllStates() { if (_stateStack != null && _stateStack.size() > 0) { while (_stateStack.size() >= 1) { _stateStack.removeFirst(); } } } private void returnToStartingState() { if (_stateStack != null && _stateStack.size() > 0) { while (_stateStack.size() > 1) { _stateStack.removeFirst(); } popState(); pushState(); } } private LinkedList _stateStack = null; // ######### Search ########## public void setCurrentElementAsCousinsAncestor() { setElementCousinsAncestor(); } /** * move to the next element with the same name and with ancestors with the same names */ public void gotoNextCousin() { if (hasElement() && hasName()) { switch (getMode()) { case MODE_UPDATE: if (hasNextCousin()) { setMode(MODE_SELECT); gotoNextCousin(); setMode(MODE_UPDATE); break; } // if no next, INSERT case MODE_INSERT: char bakMode = getMode(); // from UPDATE mode, there is no next if (getMode() == MODE_INSERT) { setMode(MODE_SELECT); while (hasNextCousin()) { gotoNextCousin(); } setMode(bakMode); } gotoNextSibling(); break; case MODE_DELETE: case MODE_UNIQUE: gotoNextSibling(); break; case MODE_SELECT: if (!isElement() || isCurrentElementRoot() || !hasName()) { eraseAll(); } else if (hasNextSibling()) { gotoNextSibling(); } else if (isParentCousinsAncestor()) { eraseAll(); } else { boolean changeCousinsAncestor = isElementCousinsAncestor(); String cousinName = getName(); setElement(getElement().getParentElement()); setName(getElement().getName()); syncIElementsWithElementAndName(); gotoNextCousin(); if (hasElement()) { gotoFirstChild(cousinName); if (hasElement() && changeCousinsAncestor) { setCousinsAncestor(getElement()); } } else { eraseAll(); } } break; } } else { eraseAll(); } } /** * @return true if another element exists with the same path (same name and ancestors with same * names) in the document */ public boolean hasNextCousin() { boolean found; if (hasElement() && hasName()) { pushState(); char bakMode = getMode(); setMode(MODE_SELECT); gotoNextCousin(); setMode(bakMode); found = currentNodeExists(); popState(); } else { found = false; } return found; } /** * @return true if the current element is parent of the subnode of the given type, name and value. */ public boolean isParentOf(char nodeType, String nodeName, String nodeValue) { boolean found = false; if (hasElement()) { pushState(); char bakMode = getMode(); setMode(MODE_SELECT); gotoFirstChildNode(nodeType, nodeName); found = currentNodeExists(); if (nodeValue != null) { if (found) { found = getCurrentNodeValue() != null; } if (found) { found = getCurrentNodeValue().trim().equals(nodeValue); } } if (!found) { while (!found && hasNextSiblingNode()) { gotoNextSiblingNode(); found = currentNodeExists(); if (nodeValue != null) { if (found) { found = getCurrentNodeValue() != null; } if (found) { found = getCurrentNodeValue().trim().equals(nodeValue); } } } } setMode(bakMode); popState(); } return found; } /** * @return true if the current element is parent of the subnode of the given type and name. */ public boolean isParentOf(char nodeType, String nodeName) { return isParentOf(nodeType, nodeName, null); } public void gotoFirstNephewNode(char nodeType, String nodeName) { if (hasElement()) { boolean found = false; char bakMode = getMode(); found = isParentOf(nodeType, nodeName); if (!found) { setMode(MODE_SELECT); while (!found && hasNextCousin()) { gotoNextCousin(); found = isParentOf(nodeType, nodeName); } setMode(bakMode); } gotoFirstChildNode(nodeType, nodeName); } else { eraseAll(); } } /** */ public void gotoFirstNephewParentOf(String nephewName, char nodeType, String nodeName, String nodeValue) { if (hasElement()) { boolean found; boolean nephewExists; char bakMode = getMode(); setMode(MODE_SELECT); pushState(); gotoFirstNephewNode(TYPE_ELEMENT, nephewName); nephewExists = currentNodeExists(); popState(); if (nephewExists) { gotoFirstNephewNode(TYPE_ELEMENT, nephewName); found = isParentOf(nodeType, nodeName, nodeValue); if (!found) { while (!found && hasNextCousin()) { gotoNextCousin(); found = isParentOf(nodeType, nodeName, nodeValue); } } } else { found = false; } setMode(bakMode); switch (getMode()) { case MODE_DELETE: if (nephewExists && found) { if (hasParent()) { Element e = getCurrentElement(); gotoParent(); getCurrentElement().removeContent(e); } } eraseAll(); break; case MODE_UNIQUE: if (!nephewExists) { gotoFirstChild(nephewName); } else { if (!found) { setMode(MODE_SELECT); while (hasNextSiblingNode()) { gotoNextSiblingNode(); } setMode(bakMode); gotoNextSiblingNode(); } } if (!found) { pushState(); gotoFirstChildNode(nodeType, nodeName); if (nodeValue != null) { setCurrentNodeValue(nodeValue); } popState(); } Element e = getCurrentElement(); Element p = e.getParentElement(); List l = p.getChildren(getName()); if (e.getParentElement().getChildren(getName()).size() > 1) { pushState(); String uniqname = getName(); gotoParent(); getCurrentElement().removeChildren(uniqname); getCurrentElement().addContent(e); popState(); } break; case MODE_SELECT: if (!found) { eraseAll(); } break; case MODE_UPDATE: if (found) { break; } // else INSERT case MODE_INSERT: if (!nephewExists) { gotoFirstChild(nephewName); } if (found) { setMode(MODE_SELECT); while (hasNextCousin()) { gotoNextCousin(); } setMode(bakMode); } if (nephewExists) { gotoNextCousin(); } pushState(); gotoFirstChildNode(nodeType, nodeName); if (nodeValue != null) { setCurrentNodeValue(nodeValue); } popState(); break; } } else { eraseAll(); } } public void gotoFirstNephewParentOf(String nephewName, char nodeType, String nodeName) { gotoFirstNephewParentOf(nephewName, nodeType, null); } /** * finds and goes to the first cousin which has a subnode of the given type, name and value. */ public void gotoNextCousinParentOf(char nodeType, String nodeName, String nodeValue) { if (hasElement() && hasName()) { char bakMode = getMode(); setMode(MODE_SELECT); boolean found = false; while (!found && hasNextCousin()) { gotoNextCousin(); found = isParentOf(nodeType, nodeName, nodeValue); } setMode(bakMode); switch (getMode()) { case MODE_DELETE: if (found) { if (hasParent()) { Element e = getCurrentElement(); gotoParent(); getCurrentElement().removeContent(e); } } eraseAll(); break; case MODE_UNIQUE: if (!found) { gotoNextSiblingNode(); pushState(); gotoFirstChildNode(nodeType, nodeName); if (nodeValue != null) { setCurrentNodeValue(nodeValue); } popState(); } Element e = getCurrentElement(); if (e.getParentElement().getChildren(getName()).size() > 1) { pushState(); String uniqname = getName(); gotoParent(); getCurrentElement().removeChildren(uniqname); getCurrentElement().addContent(e); popState(); } break; case MODE_SELECT: if (!found) { eraseAll(); } break; case MODE_UPDATE: if (found) { break; } // else INSERT case MODE_INSERT: if (found) { setMode(MODE_SELECT); while (hasNextCousin()) { gotoNextCousin(); } setMode(bakMode); } gotoNextCousin(); pushState(); gotoFirstChildNode(nodeType, nodeName); if (nodeValue != null) { setCurrentNodeValue(nodeValue); } popState(); break; } } else { eraseAll(); } } public void gotoNextCousinParentOf(char nodeType, String nodeName) { gotoNextCousinParentOf(nodeType, nodeName, null); } /** * finds and goes to the first cousin which has a subnode of the given type, name and value. */ public boolean hasNextCousinParentOf(char nodeType, String nodeName, String nodeValue) { boolean found; if (hasElement()) { pushState(); char bakMode = getMode(); setMode(MODE_SELECT); gotoNextCousinParentOf(nodeType, nodeName, nodeValue); setMode(bakMode); found = currentNodeExists(); popState(); } else { found = false; } return found; } public boolean hasNextCousinParentOf(char nodeType, String nodeName) { return hasNextCousinParentOf(nodeType, nodeName, null); } }