/******************************************************************************
* Copyright (c) 2009-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.sse;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.text.IDocument;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilder;
import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl;
import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
import org.eclipse.wst.xsd.contentmodel.internal.XSDImpl;
import org.eclipse.wst.xsd.contentmodel.internal.XSDImpl.XSDElementDeclarationAdapter;
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 com.ebmwebsourcing.petals.common.internal.PetalsCommonPlugin;
import com.ebmwebsourcing.petals.common.internal.provisional.utils.DomUtils;
/**
* @author Vincent Zurczak - EBM WebSourcing
*
* FIXME: this class relies on non-API code.
* Changing the WTP version may result in troubles and not-working features.
*
* FIXME: create a new DOMContentBuilder that creates right elements?
*/
@SuppressWarnings( "restriction" )
public class StructuredModelHelper {
public static final int INSERTION_APPEND = -1;
public static final int INSERTION_ANYWHERE_VALID = -2;
/**
* In WTP, element values are text nodes that are child of the element.
* <p>
* And {@link Element#setTextContent(String)} is not implemented.
* </p>
*
* @param element
* @param simpleValue
*/
public static void setElementSimpleValue( Element element, String simpleValue ) {
Text textNode = element.getOwnerDocument().createTextNode( simpleValue );
NodeList children = element.getChildNodes();
for( int i=0; i<children.getLength(); i++ )
element.removeChild( children.item( i ));
element.appendChild( textNode );
}
/**
* Gets the value of an element.
* <p>
* If the element value is not simple (i.e. this element has children
* that are not text nodes), then null is returned.
* </p>
*
* @param element the element whose simple value must be found
* @return the element simple value, or null if the element has elements as children
*/
public static String getElementSimpleValue( Element element ) {
boolean notSimple = false;
StringBuilder result = new StringBuilder();
NodeList children = element.getChildNodes();
for( int i=0; i<children.getLength(); i++ ) {
Node node = children.item( i );
if( node instanceof Text ) {
result.append(((Text) node).getNodeValue());
}
else {
notSimple = true;
break;
}
}
return notSimple ? null : result.toString().trim();
}
/**
* Returns the XML node corresponding to the offset in the given document.
* @param document
* @param offset the offset
* @return a node or null if no node could be found
*/
public static Node getNodeByOffset( IDocument document, int offset ) {
Node result = null;
try {
IStructuredModel model = StructuredModelManager.getModelManager().getExistingModelForRead( document );
IndexedRegion indexedRegion = model.getIndexedRegion( offset );
if( indexedRegion == null )
indexedRegion = model.getIndexedRegion( offset - 1 );
if( indexedRegion instanceof Node )
result = (Node) indexedRegion;
} catch( Exception e ) {
PetalsCommonPlugin.log( e, IStatus.ERROR );
}
return result;
}
/**
* Still experimental.
* <p>
* 1. We get the XSD reference matching the parent element.<br />
* 2. We get all the XSD references for the possible children of this parent element.<br />
* 3. For all the child references...<br />
* 4. We eliminate nodes that are not elements.<br />
* 5. We eliminate elements whose name is not the one we look for (prefix are ignored).<br />
* 6. We eliminate elements whose XSD reference's target name space is not declared in the current document.<br />
* 7. We eliminate elements whose insertion is impossible...<br />
* 7.a. We check if appending this child is valid ({@link #INSERTION_APPEND}).<br />
* 7.b. We check all the child positions until we find one valid ({@link #INSERTION_ANYWHERE_VALID}).<br />
* 7.c. We check the given index position.
* </p>
* <p>
* By default, when two elements have the same name but different name spaces, and can be inserted,
* then the name space prefix is not proposed in the created element.
* </p>
*
* @param parentElement
* @param newElementName
* @param insertionIndex
* @return
*
* FIXME: make the reporting and the error processing correct
*/
public static IndexedElement getChildElementToInsert( Element parentElement, String newElementName, int insertionIndex ) {
int exactInsertionIndex = -1;
CMElementDeclaration cmElt = null;
// Get possibilities from the XML schemas
Document doc = parentElement.getOwnerDocument();
ModelQuery mQuery = ModelQueryUtil.getModelQuery( doc );
CMElementDeclaration parentEd = mQuery.getCMElementDeclaration( parentElement );
if( parentEd != null ) {
List<?> nodes = mQuery.getAvailableContent( parentElement, parentEd, ModelQuery.INCLUDE_CHILD_NODES );
int childrenSize = parentElement.getChildNodes().getLength();
// Exit the loop as soon as one valid position is found
childNodesLoop: for( Object o : nodes ) {
if(((CMNode) o).getNodeType() != CMNode.ELEMENT_DECLARATION )
continue;
// Check element name
cmElt = (CMElementDeclaration) o;
if( ! newElementName.equals( cmElt.getNodeName()))
continue;
// Check element name space
// Do not include elements whose name space is not already declared
if( cmElt instanceof XSDElementDeclarationAdapter ) {
XSDElementDeclarationAdapter a = (XSDElementDeclarationAdapter) cmElt;
String nsUri = (String) a.getCMDocument().getProperty( XSDImpl.PROPERTY_TARGET_NAMESPACE_URI );
if( nsUri == null
|| DomUtils.lookupNamespacePrefix( nsUri, parentElement ) == null )
continue;
}
// Yes, we're strict: only XSD models are expected
else {
continue;
}
// Append the new element
if( insertionIndex == INSERTION_APPEND ) {
if( mQuery.canInsert( parentElement, cmElt, childrenSize, ModelQuery.VALIDITY_STRICT )) {
exactInsertionIndex = childrenSize;
break childNodesLoop;
}
}
// Insert at the first valid position
else if( insertionIndex == INSERTION_ANYWHERE_VALID ) {
for( int insertionPos = childrenSize; insertionPos >= 0; insertionPos -- ) {
if( mQuery.canInsert( parentElement, cmElt, insertionPos, ModelQuery.VALIDITY_STRICT )) {
exactInsertionIndex = insertionPos;
break childNodesLoop;
}
}
}
// Insert at an exact index
else {
if( mQuery.canInsert( parentElement, cmElt, insertionIndex, ModelQuery.VALIDITY_STRICT )) {
exactInsertionIndex = insertionIndex;
break childNodesLoop;
}
}
}
}
// Build the new element
Element result = null;
if( cmElt == null || exactInsertionIndex < 0 ) {
PetalsCommonPlugin.log( "Unexpected result!", IStatus.ERROR );
}
else {
DOMContentBuilder builder = new DOMContentBuilderImpl( doc );
builder.setBuildPolicy( DOMContentBuilder.BUILD_ONLY_REQUIRED_CONTENT );
builder.build( parentElement, cmElt );
List<?> additions = builder.getResult();
if( additions.size() == 1 ) {
Object o = additions.get( 0 );
if( o instanceof Element )
result = (Element) o;
}
else {
PetalsCommonPlugin.log( "Unexpected cardinality in result!", IStatus.ERROR );
}
}
return new IndexedElement( result, exactInsertionIndex );
}
/**
* @param parentElement
* @param childElementName
* @param insertionIndex
* @return
*/
public static Element getOrCreateAndInsertChildElement( Element parentElement, String childElementName, int insertionIndex ) {
Element result = DomUtils.getChildElement( parentElement, childElementName );
if( result == null ) {
IndexedElement ie = getChildElementToInsert( parentElement, childElementName, insertionIndex );
if( ie.element != null && ie.insertionIndex != -1 ) {
DomUtils.insertChildElement( parentElement, ie.element, insertionIndex );
result = ie.element;
}
}
return result;
}
/**
*
*/
public static class IndexedElement {
Element element;
int insertionIndex;
/**
* Constructor.
* @param element
* @param insertionIndex
*/
private IndexedElement( Element element, int insertionIndex ) {
this.element = element;
this.insertionIndex = insertionIndex;
}
/**
* @return the element
*/
public Element getElement() {
return this.element;
}
/**
* @return the insertionIndex
*/
public int getInsertionIndex() {
return this.insertionIndex;
}
}
}