/****************************************************************************** * Copyright (c) 2008-2013, Linagora * * 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: * Linagora - initial API and implementation *******************************************************************************/ package com.ebmwebsourcing.petals.common.internal.provisional.utils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IStatus; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.SAXException; import com.ebmwebsourcing.petals.common.internal.PetalsCommonPlugin; /** * @author Vincent Zurczak - EBM WebSourcing */ public final class DomUtils { /** * Private constructor for utility class. */ private DomUtils() { // nothing } /** * @param prefix * @param node * @return the name space URI associated with prefix and visible from node. */ public static String lookupNamespaceURI( String prefix, Node node ) { return getNamespaceMappings( node ).get( prefix ); } /** * Get all the name spaces visible from this node. * <p> * This method was added because the DOM implementation of the Eclipse * XML editor does not handle this correctly. * </p> * @param node * @return */ public static Map<String, String> getNamespaceMappings( Node node ) { Map<String, String> namespaces = new HashMap<String, String> (); boolean firstLoop = true; Node n = node; while( n != null ) { // Get default name space if( firstLoop && n.getNamespaceURI() != null ) { namespaces.put( "", n.getNamespaceURI()); firstLoop = false; } // Get other name spaces if( n.getAttributes() != null ) { for( int i=0; i<n.getAttributes().getLength(); i++ ) { String attrName = n.getAttributes().item( i ).getNodeName(); if( attrName.startsWith( "xmlns" )) { //$NON-NLS-1$ String prefix = ""; int index = attrName.indexOf( ':' ); if( index != -1 && ++ index < attrName.length()) prefix = attrName.substring( index ); String uri = n.getAttributes().item( i ).getNodeValue(); namespaces.put( prefix, uri ); } } } // Go on with the parent n = n.getParentNode(); } return namespaces; } /** * @param node * @return the name of the XML node (with no name space element) */ public static String getNodeName( Node node ) { String name = node.getNamespaceURI() != null ? node.getLocalName() : node.getNodeName(); if( name.contains( ":" )) { //$NON-NLS-1$ String[] parts = name.split( ":" ); //$NON-NLS-1$ name = parts[ parts.length - 1 ]; } return name; } /** * @param namespaceURI * @param node * @return the prefix associated with the given namespace URI, null if not found */ public static String lookupNamespacePrefix( String namespaceURI, Node node ) { for( Map.Entry<String, String> entry : getNamespaceMappings( node ).entrySet()) { if( entry.getValue().equals( namespaceURI )) return entry.getKey(); } return null; } /** * Moves an element among its <i>brothers</i> (other elements, not nodes). * @param element the element to move * @param moveBefore true to move the element before (decrease its position), false to move it after (increase its position) */ public static void moveElementOrder( Element element, boolean moveBefore ) { Node parentNode = element.getParentNode(); if( moveBefore ) { Element elementBefore = getElement( element, true ); if( elementBefore != null ) { parentNode.removeChild( element ); parentNode.insertBefore( element, elementBefore ); } } else { Element elementAfter = getElement( element, false ); if( elementAfter != null ) { Element elementAfterAfter = getElement( elementAfter, false ); parentNode.removeChild( element ); parentNode.insertBefore( element, elementAfterAfter ); } } } /** * @param element * @param before true to search the element before, false for the element after * @return the element before <i>element</i> */ public static Element getElement( Element element, boolean before ) { NodeList bas = element.getParentNode().getChildNodes(); Element searchedElement = null; boolean found = false; for( int i=0; i<bas.getLength() && (searchedElement == null || ! found); i++ ) { if( element.equals( bas.item( i ))) found = true; else if( bas.item( i ) instanceof Element ) { // We look for the element before - we didn't find the right element yet if( before && ! found ) searchedElement = (Element) bas.item( i ); // We look for the element after - we have to find the right element first else if( ! before && found ) searchedElement = (Element) bas.item( i ); } } return searchedElement; } /** * @param element * @param attributeName * @param attributeValue * @return */ public static Attr addOrSetAttribute( Element element, String attributeName, String attributeValue ) { try { Attr attribute = element.getAttributeNode( attributeName ); if( attribute != null && attributeValue.equals( attribute.getValue())) return attribute; element.setAttribute( attributeName, attributeValue ); return element.getAttributeNode( attributeName ); } catch( Exception e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } return null; } /** * @param element */ public static void removeElement( Element element ) { try { element.getParentNode().removeChild( element ); } catch( Exception e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } } /** * @param parentElement * @param newNodeName * @param newNodeNamespaceUri * @return */ public static Element addElement( Element parentElement, String newNodeName, String newNodeNamespaceUri ) { try { String prefix = DomUtils.lookupNamespacePrefix( newNodeNamespaceUri, parentElement ); if( prefix != null && prefix.length() > 0 ) prefix += ":"; else prefix = ""; Document doc = parentElement.getOwnerDocument(); Text textBefore = doc.createTextNode( "\n\t\t" ); parentElement.appendChild( textBefore ); Element newChild = doc.createElementNS( newNodeNamespaceUri, prefix + newNodeName ); parentElement.appendChild( newChild ); Text textAfter = doc.createTextNode( "\n\t\t" ); parentElement.appendChild( textAfter ); return newChild; } catch( Exception e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } return null; } /** * @param parentElement * @param childNode * @param insertionIndex * @return */ public static boolean insertChildElement( Element parentElement, Node childNode, int insertionIndex ) { try { Node nodeAfter = parentElement.getChildNodes().item( insertionIndex ); parentElement.insertBefore( childNode, nodeAfter ); return true; } catch( Exception e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } return false; } /** * @param parentElement * @param childElementName * @return the element if it exists, null otherwise */ public static Element getChildElement( Element parentElement, String childElementName ) { NodeList children = parentElement.getChildNodes(); for( int i=0; i<children.getLength(); i++ ) { Node child = children.item( i ); if( child.getNodeType() == Node.ELEMENT_NODE && childElementName.equals( getNodeName( child ))) return (Element) child; } return null; } /** * Builds a document for the given file. * <p> * If the document is open in the Eclipse XML editor, then the DOM is reused. * Otherwise, the DOM is created from the file... * </p> * * @param file the file to load * @return the document or null if it could not be loaded */ public static Document buildDocument( File file, boolean tryWithEclipse ) { Document doc = null; // Try to load the file from the XML editor IFile resFile = ResourceUtils.getIFile( file ); if( resFile != null ) { IStructuredModel model = StructuredModelManager.getModelManager().getExistingModelForRead( resFile ); if( model != null ) doc = ((IDOMModel) model).getDocument(); } // Otherwise, load the file if( doc == null ) { DocumentBuilderFactory db = DocumentBuilderFactory.newInstance(); db.setNamespaceAware( true ); try { doc = db.newDocumentBuilder().parse( file ); } catch( SAXException e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } catch( IOException e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } catch( ParserConfigurationException e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } } return doc; } /** * Builds a document for the given file. * <p> * Equivalent to <code><buildDocument( file,false );</code>. * </p> * * @param file the file to load * @return the document or null if it could not be loaded */ public static Document buildDocument( File file ) { return buildDocument( file, false ); } /** * Builds a document from a string. * @param text the text to parse as a XML document * @return the document or null if it could not be loaded * @throws ParserConfigurationException * @throws IOException * @throws SAXException */ public static Document buildDocument( String text ) throws SAXException, IOException, ParserConfigurationException { DocumentBuilderFactory db = DocumentBuilderFactory.newInstance(); db.setNamespaceAware( true ); return db.newDocumentBuilder().parse( new ByteArrayInputStream( text.getBytes())); } /** * Builds a new document. * @return the document or null if it could not be loaded * @throws ParserConfigurationException * @throws IOException * @throws SAXException */ public static Document buildNewDocument() throws SAXException, IOException, ParserConfigurationException { DocumentBuilderFactory db = DocumentBuilderFactory.newInstance(); db.setNamespaceAware( true ); return db.newDocumentBuilder().newDocument(); } /** * Validates if a text represents a valid XML document. * <p> * If the XML text does not start with <code><?xml </code>, then a default instruction is added. * </p> * * @param text the text to parse * @return true if it could be parsed as a XML document, false otherwise */ public static boolean isValidXmlDocument( String text ) { boolean result = false; StringBuilder sb = new StringBuilder(); sb.append( text ); if( ! text.startsWith( "<?xml " )) sb.insert( 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ); try { result = buildDocument( sb.toString()) != null; } catch( SAXException e ) { // nothing } catch( IOException e ) { // nothing } catch( ParserConfigurationException e ) { // nothing } return result; } /** * Writes a document as a string. * @param doc the document * @return the written document, or null if the conversion failed */ public static String writeDocument( Document doc ) { return writeDocument( doc, false ); } /** * Writes a document as a string. * @param doc the document * @param omitXmlDeclaration true to omit the XML declaration * @return the written document, or null if the conversion failed */ public static String writeDocument( Document doc, boolean omitXmlDeclaration ) { String result = null; DOMSource domSource = new DOMSource( doc ); StringWriter writer = new StringWriter(); StreamResult streamResult = new StreamResult( writer ); try { TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformerFactory.setAttribute( "indent-number", 4 ); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); transformer.setOutputProperty( OutputKeys.STANDALONE, "yes" ); if( omitXmlDeclaration ) transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" ); transformer.transform( domSource, streamResult ); result = writer.toString(); } catch( TransformerConfigurationException e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } catch( TransformerFactoryConfigurationError e ) { PetalsCommonPlugin.log( new Exception( e ), IStatus.ERROR ); } catch( TransformerException e ) { PetalsCommonPlugin.log( e, IStatus.ERROR ); } return result; } }