All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.magicwerk.brownies.jdom.XmlTransformer Maven / Gradle / Ivy

/*
 * Copyright 2015 by Thomas Mauch
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id$
 */
package org.magicwerk.brownies.jdom;

import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;

import org.jdom2.Attribute;
import org.jdom2.Comment;
import org.jdom2.Content;
import org.jdom2.DocType;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.ProcessingInstruction;
import org.jdom2.Text;
import org.magicwerk.brownies.collections.GapList;
import org.magicwerk.brownies.collections.IList;
import org.magicwerk.brownies.core.CheckTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Transform XML content according to registered handlers.
 *
 * @author Thomas Mauch
 * @version $Id$
 */
public class XmlTransformer {

	/** Logger */
	static final Logger LOG = LoggerFactory.getLogger(XmlTransformer.class);

	static class NodeContext {
		Element parent;
		Object node;

		NodeContext(Element parent, Object node) {
			this.parent = parent;
			this.node = node;
		}
	}

	List> handlers = GapList.create();
	boolean copyHandler = true;
	Object currentNode;
	IList currentNodeChildren;
	Document dstDoc;
	Element dstRootElem;
	Element dstElem;

	public List> getHandlers() {
		return handlers;
	}

	public void setHandlers(List> handlers) {
		this.handlers = handlers;
	}

	/**
	 * Transform document according to registered handlers.
	 *
	 * @param doc	document to transform
	 * @return		transformed document
	 */
	public Document transform(Document doc) {
		doTransform(doc);
		return dstDoc;
	}

	/**
	 * Transform element according to registered handlers.
	 *
	 * @param elem	element to transform
	 * @return		transformed element
	 */
	public Element transform(Element elem) {
		doTransform(elem);
		return dstRootElem;
	}

	void doTransform(Object node) {
		dstDoc = null;
		dstRootElem = null;

		IList open = GapList.create(new NodeContext(null, node));
		while (true) {
			NodeContext nc = open.pollFirst();
			if (nc == null) {
				break;
			}
			List newProcessItems = handle(nc.parent, nc.node);
			//LOG.debug("doTransform {}/{} -> {}", nc.parent, nc.node, dstElem);

			for (int i = 0; i < newProcessItems.size(); i++) {
				open.add(i, new NodeContext(dstElem, newProcessItems.get(i)));
			}
		}
	}

	List handle(Element parent, Object node) {
		dstElem = parent;
		currentNode = node;
		currentNodeChildren = GapList.create();

		// Apply handlers
		doHandle(node);

		// Handle attributes before children
		currentNodeChildren.sort(new Comparator() {
			@Override
			public int compare(Object o1, Object o2) {
				int n1 = (o1 instanceof Attribute) ? 0 : 1;
				int n2 = (o2 instanceof Attribute) ? 0 : 1;
				return n1 - n2;
			}
		});
		return currentNodeChildren;
	}

	void doHandle(Object obj) {
		CheckTools.checkTypeOf(obj, Document.class, Content.class, Attribute.class);

		boolean done = false;
		for (Handler h : handlers) {
			@SuppressWarnings("unchecked")

			Handler handler = (Handler) h;
			if (!testHandler(handler, obj)) {
				continue;
			}

			done = handler.apply(obj, this);
			if (done) {
				break;
			}
		}

		if (!done) {
			if (copyHandler) {
				applyAttributes();
				applyContent();
				add(copyNode(obj));
			}
		}
	}

	boolean testHandler(Handler handler, Object obj) {
		try {
			return handler.test(obj);
		} catch (ClassCastException e) {
			return false;
		}
	}

	//

	/**
	 * Copy node non-recursively and add it to the destination tree.
	 *
	 * @param node node to copy
	 */
	public void copy(Object node) {
		add(copyNode(node));
	}

	/**
	 * Copy node recursively and add it to the destination tree.
	 *
	 * @param node node to copy
	 */
	public void copyOf(Object node) {
		Object copy;
		if (node instanceof Element) {
			copy = copyElementRecursively((Element) node);
		} else {
			copy = copyNode(node);
		}
		add(copy);
	}

	/**
	 * Nest the element inside a new element with name newName.
	 */
	public void nestElement(Element elem, String newName) {
		Element newElem = new Element(newName);
		newElem.addContent(elem);
		add(newElem);
	}

	/**
	 * Rename element to newName.
	 */
	public void renameElement(Element elem, String newName) {
		Element newElem = XmlTransformer.copyElement(elem);
		newElem.setName(newName);
		add(newElem);
	}

	/**
	 * Rename attribute to newName.
	 */
	public void renameAttribute(Attribute attr, String newName) {
		Attribute newAttr = XmlTransformer.copyAttribute(attr);
		newAttr.setName(newName);
		add(newAttr);
	}

	public void convertAttributeToElement(Attribute attr) {
		convertAttributeToElement(attr, attr.getName());
	}

	public void convertAttributeToElement(Attribute attr, String newName) {
		Element newElem = new Element(newName);
		newElem.setText(attr.getValue());
		add(newElem);
	}

	void convertElementToAttribute(Element elem) {
		convertElementToAttribute(elem, elem.getName());
	}

	void convertElementToAttribute(Element elem, String newName) {
		Attribute newAttr = new Attribute(newName, elem.getText());
		add(newAttr);
	}

	//

	public void applyNodes(Object... nodes) {
		currentNodeChildren.addArray(nodes);
	}

	public void applyNodes(List nodes) {
		currentNodeChildren.addAll(nodes);
	}

	/**
	 * Apply both attributes and content.
	 */
	public void applyAll() {
		applyAttributes();
		applyContent();
	}

	public void applyAttributes() {
		Element elem = (currentNode instanceof Element) ? (Element) currentNode : null;
		if (elem != null) {
			currentNodeChildren.addAll(elem.getAttributes());
		}
	}

	public void applyContent() {
		if (currentNode instanceof Element) {
			Element elem = (Element) currentNode;
			currentNodeChildren.addAll(elem.getContent());
		} else if (currentNode instanceof Document) {
			Document doc = (Document) currentNode;
			currentNodeChildren.addAll(doc.getContent());
		}
	}

	public void applyElements() {
		if (currentNode instanceof Element) {
			Element elem = (Element) currentNode;
			currentNodeChildren.addAll(elem.getChildren());
		} else if (currentNode instanceof Document) {
			Document doc = (Document) currentNode;
			currentNodeChildren.add(doc.getRootElement());
		}
	}

	public Element getDestinationElement() {
		return dstElem;
	}

	/**
	 * Add result to destination tree.
	 *
	 * @param result	result to add
	 */
	public void add(Object result) {
		if (result == null) {
			//
		} else if (result instanceof Boolean && ((Boolean) result)) {
			//
		} else if (result instanceof Document) {
			Document doc = (Document) result;
			CheckTools.check(dstDoc == null, "Destination document already set");
			dstDoc = doc;
		} else if (result instanceof Element) {
			Element elem = (Element) result;
			if (dstElem != null) {
				dstElem.addContent(elem);
			} else {
				if (dstDoc != null) {
					dstDoc.setRootElement(elem);
				} else {
					dstDoc = new Document(elem);
				}
			}
			dstElem = elem;
			if (dstRootElem == null) {
				dstRootElem = dstElem;
			}
		} else if (result instanceof Attribute) {
			Attribute attr = (Attribute) result;
			dstElem.setAttribute(attr);
		} else if (result instanceof Content) {
			Content cont = (Content) result;
			if (dstElem == null) {
				dstDoc.addContent(cont);
			} else {
				dstElem.addContent(cont);
			}
		} else {
			throw new AssertionError("Invalid content: " + result.getClass());
		}
	}

	/**
	 * Copy specified node (non recursively).
	 *
	 * @param node	node to copy
	 * @return		copy of node
	 */
	public static Object copyNode(Object node) {
		if (node instanceof Document) {
			return copyDocument((Document) node);
		} else if (node instanceof Element) {
			return copyElement((Element) node);
		} else if (node instanceof Attribute) {
			return copyAttribute((Attribute) node);
		} else if (node instanceof Text) {
			return copyText((Text) node);
		} else if (node instanceof Comment) {
			return copyComment((Comment) node);
		} else if (node instanceof ProcessingInstruction) {
			return copyProcessingInstruction((ProcessingInstruction) node);
		} else if (node instanceof DocType) {
			return copyDocType((DocType) node);
		}
		throw new AssertionError("Invalid content: " + node.getClass());
	}

	public static Document copyDocument(Document doc) {
		Document copy = new Document();
		return copy;
	}

	/**
	 * Copy element recursively (with attributes and nested content)
	 *
	 * @param elem	element to copy
	 * @return		created element
	 */
	public static Element copyElementRecursively(Element elem) {
		Element copy = elem.clone();
		return copy;
	}

	/**
	 * Copy element only (without attributes and nested content).
	 *
	 * @param elem	element to copy
	 * @return		created element
	 */
	public static Element copyElement(Element elem) {
		Element copy = new Element(elem.getName(), elem.getNamespace());
		return copy;
	}

	public static Attribute copyAttribute(Attribute attr) {
		Attribute copy = new Attribute(attr.getName(), attr.getValue(), attr.getNamespace());
		return copy;
	}

	public static Text copyText(Text text) {
		Text copy = new Text(text.getText());
		return copy;
	}

	public static Comment copyComment(Comment comm) {
		Comment copy = new Comment(comm.getText());
		return copy;
	}

	public static ProcessingInstruction copyProcessingInstruction(ProcessingInstruction pi) {
		ProcessingInstruction copy = new ProcessingInstruction(pi.getTarget(), pi.getData());
		return copy;
	}

	public static DocType copyDocType(DocType dt) {
		DocType copy = dt.clone();
		return copy;
	}

	//

	/**
	 * Interface for handling nodes in a XML tree.
	 */
	public interface Handler {
		/**
		 * Determines whether handler should be executed for the node.
		 *
		 * @param node	node
		 * @return		true if handler should be executed for the node
		 */
		boolean test(T node);

		/**
		 * Execute handler for node.
		 * The handler is only executed if test() has returned true.
		 *
		 * @param node	node
		 * @param xt	XML transformer to use
		 * @return		true if node has been processed and no more handlers should be executed, false to continue processing
		 */
		boolean apply(T node, XmlTransformer xt);
	}

	/**
	 * Abstract handler for elements.
	 */
	public static abstract class ElementHandler implements Handler {
		Predicate predicate;

		public ElementHandler(Predicate predicate) {
			this.predicate = predicate;
		}

		@Override
		public boolean test(Element node) {
			return predicate == null || predicate.test(node);
		}
	}

	/**
	 * Abstract handler for attributes.
	 */
	public static abstract class AttributeHandler implements Handler {
		Predicate predicate;

		public AttributeHandler(Predicate predicate) {
			this.predicate = predicate;
		}

		@Override
		public boolean test(Attribute node) {
			return predicate == null || predicate.test(node);
		}
	}

}