/**
Copyright (c) 2012 Delcyon, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.delcyon.capo.xml.cdom;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import com.delcyon.capo.util.CloneControl;
import com.delcyon.capo.util.CloneControl.Clone;
import com.delcyon.capo.xml.XPath;
/**
* @author jeremiah
*
*/
public class CDocument extends CNode implements Document, NodeValidationUtilitesFI
{
public static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema";
public static final String XML_NAMESPACE_NS = "http://www.w3.org/2000/xmlns/";
private static int documentIDCounter = 0;
private static int incrementDocumentID()
{
documentIDCounter++;
return documentIDCounter;
}
private CElement documentElement = null;
private String documentURI = null;
private DocumentType doctype;
private String defaultNamespace;
@CloneControl(filter=Clone.exclude)
private long documentID = incrementDocumentID();
private boolean silenceEvents = false;
private VariableProcessor variableProcessor;
private boolean onlyAllowValidNodeNames = false;
private HashMap<String, CDocument> namespaceSchemaMap = new HashMap<String, CDocument>();
public CDocument()
{
setOwnerDocument(this);
}
public CDocument(DocumentType doctype)
{
setOwnerDocument(this);
this.doctype = doctype;
}
@Override
public Node removeChild(Node oldChild) throws DOMException
{
if(oldChild.equals(documentElement))
{
documentElement = null;
}
return super.removeChild(oldChild);
}
@Override
public Node appendChild(Node newChild) throws DOMException
{
if(newChild instanceof CElement && documentElement == null)
{
this.documentElement = (CElement) newChild;
removeNodeTypeChildrenAll(newChild.getNodeType());
super.appendChild(newChild);
((CNode) newChild).setParent(this);
return newChild;
}
else if(newChild instanceof CComment || newChild instanceof CProcessingInstruction)
{
return super.appendChild(newChild);
}
else
{
throw new UnsupportedOperationException();
}
}
@Override
public String getNodeName()
{
return "#document";
}
@Override
public short getNodeType()
{
return Node.DOCUMENT_NODE;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getDoctype()
*/
@Override
public DocumentType getDoctype()
{
return doctype;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getImplementation()
*/
@Override
public DOMImplementation getImplementation()
{
return new CDOMImplementation();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getDocumentElement()
*/
@Override
public Element getDocumentElement()
{
return this.documentElement;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createElement(java.lang.String)
*/
@Override
public Element createElement(String tagName) throws DOMException
{
CElement cElement = new CElement(tagName);
cElement.setOwnerDocument(this);
return cElement;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createDocumentFragment()
*/
@Override
public DocumentFragment createDocumentFragment()
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createTextNode(java.lang.String)
*/
@Override
public Text createTextNode(String data)
{
CText text = new CText();
text.setData(data);
text.setOwnerDocument(this);
return text;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createComment(java.lang.String)
*/
@Override
public Comment createComment(String data)
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createCDATASection(java.lang.String)
*/
@Override
public CDATASection createCDATASection(String data) throws DOMException
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createProcessingInstruction(java.lang.String, java.lang.String)
*/
@Override
public ProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createAttribute(java.lang.String)
*/
@Override
public Attr createAttribute(String name) throws DOMException
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createEntityReference(java.lang.String)
*/
@Override
public EntityReference createEntityReference(String name) throws DOMException
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getElementsByTagName(java.lang.String)
*/
@Override
public NodeList getElementsByTagName(String tagname)
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#importNode(org.w3c.dom.Node, boolean)
*/
//XXX incomplete implementation
@Override
public Node importNode(Node importedNode, boolean deep) throws DOMException
{
Node clonedNode = null;
if(importedNode instanceof CNode == false)
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try
{
XPath.dumpNode(importedNode, byteArrayOutputStream);
InputSource inputSource = new InputSource(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
CDocumentBuilder documentBuilder = new CDocumentBuilder();
importedNode = documentBuilder.parse(inputSource).getDocumentElement();
}
catch (Exception e)
{
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, e.getMessage());
}
}
else
{
clonedNode = importedNode.cloneNode(true);
}
((CNode) clonedNode).detach();
adoptNode(clonedNode);
return clonedNode;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#adoptNode(org.w3c.dom.Node)
*/
//XXX incomplete implementation
@Override
public Node adoptNode(Node source) throws DOMException
{
((CNode) source).detach();
final CDocument ownerDocument = this;
boolean originalSilenceValue = ownerDocument.isSilenceEvents();
ownerDocument.setSilenceEvents(true);
NodeProcessor nodeProcessor = new NodeProcessor()
{
@Override
public void process(Node parentNode, Node node) throws Exception
{
if(node instanceof CNode)
{
((CNode) node).setOwnerDocument(ownerDocument);
}
}
};
try
{
walkTree(null,source, nodeProcessor, false);
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
finally
{
ownerDocument.setSilenceEvents(originalSilenceValue);
}
return source;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createElementNS(java.lang.String, java.lang.String)
*/
@Override
public Element createElementNS(String namespaceURI, String qualifiedName) throws DOMException
{
CElement element = new CElement(qualifiedName);
element.setNamespaceURI(namespaceURI);
element.setOwnerDocument(this);
return element;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#createAttributeNS(java.lang.String, java.lang.String)
*/
@Override
public Attr createAttributeNS(String namespaceURI, String qualifiedName) throws DOMException
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getElementsByTagNameNS(java.lang.String, java.lang.String)
*/
@Override
public NodeList getElementsByTagNameNS(String namespaceURI, String localName)
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getElementById(java.lang.String)
*/
@Override
public Element getElementById(String elementId)
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getInputEncoding()
*/
@Override
public String getInputEncoding()
{
// TODO Auto-generated method stub
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getXmlEncoding()
*/
@Override
public String getXmlEncoding()
{
// TODO Auto-generated method stub
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getXmlStandalone()
*/
@Override
public boolean getXmlStandalone()
{
// TODO Auto-generated method stub
Thread.dumpStack();
return false;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#setXmlStandalone(boolean)
*/
@Override
public void setXmlStandalone(boolean xmlStandalone) throws DOMException
{
// TODO Auto-generated method stub
Thread.dumpStack();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getXmlVersion()
*/
@Override
public String getXmlVersion()
{
// TODO Auto-generated method stub
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#setXmlVersion(java.lang.String)
*/
@Override
public void setXmlVersion(String xmlVersion) throws DOMException
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getStrictErrorChecking()
*/
@Override
public boolean getStrictErrorChecking()
{
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#setStrictErrorChecking(boolean)
*/
@Override
public void setStrictErrorChecking(boolean strictErrorChecking)
{
// TODO Auto-generated method stub
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getDocumentURI()
*/
@Override
public String getDocumentURI()
{
return this.documentURI;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#setDocumentURI(java.lang.String)
*/
@Override
public void setDocumentURI(String documentURI)
{
this.documentURI = documentURI;
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#getDomConfig()
*/
@Override
public DOMConfiguration getDomConfig()
{
// TODO Auto-generated method stub
Thread.dumpStack();
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#normalizeDocument()
*/
@Override
public void normalizeDocument()
{
this.normalize();
}
/* (non-Javadoc)
* @see org.w3c.dom.Document#renameNode(org.w3c.dom.Node, java.lang.String, java.lang.String)
*/
@Override
public Node renameNode(Node n, String namespaceURI, String qualifiedName) throws DOMException
{
// TODO Auto-generated method stub
Thread.dumpStack();
throw new UnsupportedOperationException();
}
public String getDefaultNamespace()
{
return this.defaultNamespace;
}
public void setDefaultNamespace(String defaultNamespace)
{
this.defaultNamespace = defaultNamespace;
}
@Override
public String toString()
{
// TODO Auto-generated method stub
return documentID+" "+super.toString();
}
public boolean isSilenceEvents()
{
return silenceEvents ;
}
public void setSilenceEvents(boolean silenceEvents)
{
this.silenceEvents = silenceEvents;
}
public void setVariableProcessor(VariableProcessor variableProcessor)
{
this.variableProcessor = variableProcessor;
}
public VariableProcessor getVariableProcessor()
{
return this.variableProcessor;
}
/**
* This is a simple method to return a simple document structure based on a list of element names.
* This will return a CDocument as the root node.
* @param path, a '/' separated XML path of element names.
* @return
*/
public static CDocument buildPath(String path)
{
return (CDocument) buildPath(null,path);
}
/**
* This is a simple method to return a simple document structure based on a list of element names.
* This will return a CDocument as the root node.
* @param contextNode, the node with which to build the path onto, If null, will return a CDocument as the root of the path.
* @param path, a '/' separated XML path of element names.
* @return
*/
public static CNode buildPath(CNode contextNode, String path)
{
if(contextNode == null)
{
contextNode = new CDocument();
}
String[] splitPath = path.split("/");
CNode currentNode = contextNode;
for (String pathItem : splitPath)
{
CElement pathItemElement = new CElement(pathItem);
currentNode.appendChild(pathItemElement);
currentNode = pathItemElement;
}
return contextNode;
}
/**
*
* @return whether or not node names are checked for validity when being created.
* This allows us to loosen up our node name restrictions if we want.
* Spaces etc. can be allowed.
*/
public boolean onlyAllowValidNodeNames()
{
return this.onlyAllowValidNodeNames ;
}
public void setOnlyAllowValidNodeNames(boolean onlyAllowValidNodeNames)
{
this.onlyAllowValidNodeNames = onlyAllowValidNodeNames;
}
/**
* set the schema document to validate against for a namespace declaration in this document
* @param namespaceURI
* @param schemaDocument
*/
public void setSchemaForNamespace(String namespaceURI, CDocument schemaDocument)
{
if(schemaDocument.getDocumentElement().getNamespaceURI().equals(XML_SCHEMA_NS) == true)
{
namespaceSchemaMap.put(namespaceURI, schemaDocument);
}
else
{
throw new UnknownError("Unknown Schema URI");
}
}
public boolean isNodeValid(CNode node, boolean deep, boolean requireNamespace, Vector<CValidationException> exceptionVector) throws Exception
{
int initialExceptionCount = 0;
if(exceptionVector != null)
{
initialExceptionCount = exceptionVector.size();
}
if(requireNamespace == true)
{
if(node.getNamespaceURI() == null)
{
nodeInvalid("Missing Namespace", node, exceptionVector);
}
if (namespaceSchemaMap.containsKey(node.getNamespaceURI()) == false && node.getNamespaceURI().equals(XML_SCHEMA_NS) == false && node.getNamespaceURI().equals(XML_NAMESPACE_NS) == false)
{
nodeInvalid("Unknown Namespace ["+node.getNamespaceURI()+"]", node, exceptionVector);
}
}
//find namespace
CDocument schemaDocument = namespaceSchemaMap.get(node.getNamespaceURI());
if(schemaDocument != null)
{
//find node declaration
CElement schemaDeclElement = null;
switch (node.getNodeType())
{
case Node.ATTRIBUTE_NODE:
schemaDeclElement = (CElement) XPath.selectNSNode(schemaDocument, "//xs:attribute[@name = '"+node.getLocalName()+"']","xs="+XML_SCHEMA_NS);
break;
case Node.ELEMENT_NODE:
schemaDeclElement = (CElement) XPath.selectNSNode(schemaDocument, "//xs:element[@name = '"+node.getLocalName()+"']","xs="+XML_SCHEMA_NS);
break;
default:
break;
}
System.out.println(schemaDeclElement);
if(schemaDeclElement != null)
{
node.setNodeDefinition(new CNodeDefinition(schemaDeclElement));
node.isValid(deep,exceptionVector);
}
else
{
nodeInvalid("unknown node", node, exceptionVector);
}
}
if(deep == true)
{
//iterate over children (node,deep,requireNamespace,exceptionVector);
}
return exceptionVector == null ? true : (exceptionVector.size() == initialExceptionCount);
}
public HashMap<String, CDocument> getNamespaceSchemaMap()
{
return namespaceSchemaMap;
}
}