package com.delcyon.capo.xml.cdom;
import java.util.List;
import java.util.Vector;
import java.util.function.Predicate;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import com.delcyon.capo.xml.XPath;
import com.delcyon.capo.xml.cdom.OccurancePredicate.TestErrorResult;
public class CNodeDefinition implements NodeValidationUtilitesFI
{
private CElement def = null;
private CNodeDefinition(){}
public CNodeDefinition(CElement definingElement)
{
this.def = definingElement;
}
public CElement getDefiningElement()
{
return def;
}
public enum NodeDefinitionType
{
annotation,
simpleType,
complexType,
unique,
key,
keyref
}
public NodeDefinitionType getNodeDefinitionType()
{
for (Node childNode : def.nodeList)
{
if(childNode.getNodeType() == Node.ELEMENT_NODE)
{
switch (childNode.getLocalName())
{
case "annotation":
return NodeDefinitionType.annotation;
case "simpleType":
return NodeDefinitionType.simpleType;
case "complexType":
return NodeDefinitionType.complexType;
case "unique":
return NodeDefinitionType.unique;
case "key":
return NodeDefinitionType.key;
case "keyref":
return NodeDefinitionType.keyref;
default:
return NodeDefinitionType.simpleType;
}
}
}
return NodeDefinitionType.simpleType;
}
public CElement getNodeDefinitionTypeElement(NodeDefinitionType nodeDefinitionType)
{
for (Node childNode : def.nodeList)
{
if(childNode.getNodeType() == Node.ELEMENT_NODE)
{
if(childNode.getLocalName().equals(nodeDefinitionType.toString()))
{
return (CElement) childNode;
}
}
}
return null;
}
public List<CNodeDefinition> getPossibleChildren(CNode node,short nodeType,boolean filtered)
{
return null;
}
public boolean isValid(CNode node, Vector<CValidationException> exceptionVector) throws Exception
{
switch (node.getNodeType())
{
case Node.COMMENT_NODE:
case Node.PROCESSING_INSTRUCTION_NODE:
return true;
case Node.TEXT_NODE:
return validateTextNode(node,exceptionVector);
case Node.ELEMENT_NODE:
case Node.ATTRIBUTE_NODE:
default:
for (Node childNode : def.nodeList)
{
switch (childNode.getLocalName())
{
case "annotation":
break;
case "simpleType":
validateNodeAgainstSimpleType(node,childNode, exceptionVector);
break;
case "complexType":
validateNodeAgainstComplexType(node,(CNode) childNode, exceptionVector);
break;
case "unique":
nodeInvalid("unspported definition", node, exceptionVector);
break;
case "key":
nodeInvalid("unspported definition", node, exceptionVector);
break;
case "keyref":
nodeInvalid("unspported definition", node, exceptionVector);
break;
default:
break;
}
}
}
return false;
}
/**
*
* @return the base data type for this node.
*/
public String getTypeDefinition()
{
return null;
}
private boolean validateTextNode(CNode node, Vector<CValidationException> exceptionVector) throws Exception
{
return true;
}
private void validateNodeAgainstComplexType(CNode node, CNode complexNodeDef, Vector<CValidationException> exceptionVector) throws Exception
{
// TODO Auto-generated method stub
System.out.println(node+":"+complexNodeDef);
for (Node childDefNode : complexNodeDef.nodeList)
{
switch (childDefNode.getLocalName())
{
case "annotation":
break;
case "simpleContent":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
case "complexContent":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
case "group":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
case "all":
validateNodeAgainstAll(node,(CNode) childDefNode, exceptionVector);
break;
case "choice":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
case "sequence":
validateNodeAgainstSequence(node,(CNode) childDefNode, exceptionVector);
break;
case "attribute":
validateNodeAgainstAttribute(node,(CNode) childDefNode, exceptionVector);
break;
case "attributeGroup":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
case "anyAttribute":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
default:
break;
}
}
//TODO verify/find elements and attributes NOT mentioned in schema
for (Node attributeNode : node.attributeList)
{
if(attributeNode.getPrefix() == null && attributeNode.getLocalName().equals("xmlns"))
{
continue;
}
if(((CNode) attributeNode).getNodeDefinition() == null)
{
nodeInvalid("Attribute not allowed", (CNode) attributeNode, exceptionVector);
}
}
}
private void validateNodeAgainstAttribute(CNode node, CNode attributeDefNode, Vector<CValidationException> exceptionVector) throws Exception
{
CElement element = (CElement) node;
CElement elementDef = (CElement) attributeDefNode;
String prefix = element.getPrefix();
String namespaceURI = element.namespaceURI;
String attrName = elementDef.hasAttribute("name") ? elementDef.getAttribute("name") : elementDef.getAttribute("ref");
switch (elementDef.getAttribute("use"))
{
case "prohibited":
if(element.hasAttribute(attrName))
{
nodeInvalid("@"+attrName+" not allowed on ["+node.getLocalName()+"]", node, exceptionVector);
}
break;
case "required":
if(!element.hasAttribute(attrName))
{
nodeInvalid("@"+attrName+" required on ["+node.getLocalName()+"]", node, exceptionVector);
}
else
{
((CNode) element.getAttributeNode(attrName)).setNodeDefinition(new CNodeDefinition(elementDef));
}
break;
case "optional":
default:
if(element.hasAttribute(attrName))
{
((CNode) element.getAttributeNode(attrName)).setNodeDefinition(new CNodeDefinition(elementDef));
}
break;
}
//todo process simple type
}
private void validateNodeAgainstSequence(CNode node, CNode nodeDef, Vector<CValidationException> exceptionVector) throws Exception
{
CElement element = (CElement) node;
String prefix = element.getPrefix();
String namespaceURI = element.namespaceURI;
Vector<OccurancePredicate> functionVector = new Vector<>();
for (Node childDefNode : nodeDef.nodeList)
{
if(childDefNode.getNodeType() != Node.ELEMENT_NODE)
{
continue;
}
CElement def = (CElement)childDefNode;
Predicate<CElement> chain = null;
switch (childDefNode.getLocalName())
{
case "annotation":
break;
case "element":
chain = buildElementPredicateChain(chain,def);
break;
case "group":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
case "choice":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
case "sequence":
validateNodeAgainstSequence(node,(CNode) childDefNode, exceptionVector);
break;
case "any":
validateNodeAgainstAttribute(node,(CNode) childDefNode, exceptionVector);
break;
default:
break;
}
if(chain != null)
{
functionVector.add(new OccurancePredicate(def, chain));
}
}
//DOCUMENT ME
//Verify each child of this node according to the rules from above
for (Node childNode : node.nodeList)
{
//we're only interested in element nodes for the moment, text nodes will also be an option
if(childNode.getNodeType() != Node.ELEMENT_NODE)
{
continue;
}
//convert the child node to an element
CElement childElement = (CElement)childNode;
System.out.println("SEQ testing "+childElement+"===>"+XPath.getXPath(childElement));
//keep track of whether or not we've satisfied our predicate/test vector
boolean satisfied = false;
TestErrorResult overAllResult = null;
//Iterate over our list of match tests
for (OccurancePredicate occurancePredicate : functionVector)
{
TestErrorResult result = occurancePredicate.increment(childElement);
if(result == null)
{
satisfied = true;
break;
}
else if(result == TestErrorResult.FULL)
{
overAllResult = result;
}
}
if(satisfied == false)
{
if(overAllResult == TestErrorResult.FULL)
{
nodeInvalid("Limit exceeded ", (CNode) childNode, exceptionVector);
}
else
{
nodeInvalid("unknown element ", (CNode) childNode, exceptionVector);
}
}
}
for (OccurancePredicate occurancePredicate : functionVector)
{
if(occurancePredicate.isSatisfied() == false)
{
nodeInvalid("missing child "+occurancePredicate.getDefinition(), node, exceptionVector);
break;
}
}
}
/**
* (all) contain all and only exactly zero or one of each element specified in {particles}. The elements can occur in any order. In this case, to reduce implementation complexity, {particles} is restricted to contain local and top-level element declarations only, with {min occurs}=0 or 1, {max occurs}=1.
* @param node
* @param childNode
* @param exceptionVector
* @throws Exception
* @throws DOMException
*/
private void validateNodeAgainstAll(CNode node, CNode allNode, Vector<CValidationException> exceptionVector) throws Exception
{
CElement element = (CElement) node;
String prefix = element.getPrefix();
String namespaceURI = element.namespaceURI;
//=============Structure validation only=====================
//You can see who the kids are, verify their existence, cardinality, and order, but not whether or not they are valid in and of themselves.
//NodeList nodeList = XPath.selectNSNodes(node, "child::"+prefix+":"+((CElement)childNode).getAttributeNS(XML_SCHEMA_NS, "name"), prefix+"="+namespaceURI);
//build tests
Vector<OccurancePredicate> functionVector = new Vector<>();
for (Node childDefNode : allNode.nodeList)
{
if(childDefNode.getNodeType() != Node.ELEMENT_NODE)
{
continue;
}
CElement def = (CElement)childDefNode;
Predicate<CElement> chain = null;
switch (childDefNode.getLocalName())
{
case "annotation":
break;
case "element": //add element to head of test list
chain = buildElementPredicateChain(chain,def);
break;
case "any":
chain = buildAnyElementPredicateChain(chain,node,namespaceURI,def);
break;
case "group":
nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
break;
}
if(chain != null)
{
functionVector.add(new OccurancePredicate(def, chain));
}
}
for (Node childNode : node.nodeList)
{
if(childNode.getNodeType() != Node.ELEMENT_NODE)
{
continue;
}
CElement childElement = (CElement)childNode;
System.out.println("ALL testing "+childElement+"===>"+XPath.getXPath(childElement));
boolean satisfied = false;
TestErrorResult overAllResult = null;
for (OccurancePredicate occurancePredicate : functionVector)
{
TestErrorResult result = occurancePredicate.increment(childElement);
if(result == null)
{
satisfied = true;
break;
}
else if(result == TestErrorResult.FULL)
{
overAllResult = result;
}
}
if(satisfied == false)
{
if(overAllResult == TestErrorResult.FULL)
{
nodeInvalid("Limit exceeded ", (CNode) childNode, exceptionVector);
}
else
{
nodeInvalid("unknown element ", (CNode) childNode, exceptionVector);
}
}
}
for (OccurancePredicate occurancePredicate : functionVector)
{
if(occurancePredicate.isSatisfied() == false)
{
nodeInvalid("missing child "+occurancePredicate.getDefinition(), node, exceptionVector);
break;
}
}
}
private Predicate<CElement> buildAnyElementPredicateChain(Predicate<CElement> chain, CNode node,String namespaceURI,CElement def)
{
//add any element to end of test list
//set default chain, must consist of whether or not it's a defined element TODO this is where lax comes in
/*
Specifies the validation constraint to be applied on the content that matched the wildcard. Possible values are
skip - that means no validation
lax - that means validation will be performed if a schema is available
strict - that means validation is required
The default is strict.
*/
chain = (_node)->{
String processContents = "strict";
((CDocument)node.getOwnerDocument()).getNamespaceSchemaMap();
if(def.hasAttribute("processContents"))
{
processContents = def.getAttribute("processContents");
}
switch (processContents)
{
case "skip":
return true;
case "lax": //make sure that we can find a definition for this element, if we have a schema for it's namespace
if (((CDocument)node.getOwnerDocument()).getNamespaceSchemaMap().containsKey(_node.getNamespaceURI()) == false)
{
return true;
}
case "strict": //make sure that we can find a definition for this element
default:
if(getDefinitionForNode(_node) == null)
{
return false;
}
}
return true;
};
chain = buildPredicate(chain, (_def)-> _def.hasAttribute("namespace"),
_node -> {
switch (def.getAttribute("namespace"))
{
case "##any":
return true;
case "##other":
return (!_node.getNamespaceURI().equals(namespaceURI));
case "##targetNamespace":
return (_node.getNamespaceURI().equals(namespaceURI));
case "##local":
return (_node.getNamespaceURI().equals(namespaceURI));
case "":
return (_node.getNamespaceURI().equals(namespaceURI));
default:
return (_node.getNamespaceURI().equals(namespaceURI));
}
}, def);
chain = buildPredicate(chain, (_def)-> _def.hasAttribute("notNamespace"),
_node -> {
switch (def.getAttribute("notNamespace"))
{
case "##targetNamespace":
return (!_node.getNamespaceURI().equals(namespaceURI));
case "##local":
return (!_node.getNamespaceURI().equals(namespaceURI));
default:
return (!_node.getNamespaceURI().equals(namespaceURI));
}
}, def);
if(def.hasAttribute("notQName"))
{
//nodeInvalid("unspported definition ["+childDefNode.getLocalName()+"]", node, exceptionVector);
}
return chain;
}
private Predicate<CElement> buildElementPredicateChain(Predicate<CElement> chain,CElement def)
{
//localname test
chain = buildPredicate(chain, (_def)-> _def.hasAttribute("name"),_node -> _node.getLocalName().equals(def.getAttribute("name")), def);
//refname test
chain = buildPredicate(chain, (_def)-> _def.hasAttribute("ref"), _node -> _node.getLocalName().equals(def.getAttribute("ref")), def);
return chain;
}
private <T> Predicate<T> buildPredicate(Predicate<T> chain,Predicate<T> additionTest, Predicate<T> predicateToAdd, T t)
{
if(additionTest.test(t))
{
if(chain == null)
{
chain = predicateToAdd;
}
else
{
chain = chain.and(predicateToAdd);
}
}
return chain;
}
private void validateNodeAgainstSimpleType(CNode node, Node simpleNode, Vector<CValidationException> exceptionVector)
{
// TODO Auto-generated method stub
}
//XXX serious caching needed here
//XXX this also needs to verify that it's the right definition, as an element defined as a NON-top level element can have a different definition as the child of some other complex element
public static CNodeDefinition getDefinitionForNode(CNode node)
{
//===========PROCESS NAMESPACE DECLARATION===============
if("xmlns".equals(node.getLocalName()))
{
System.out.println("name space declaration");
return new CNodeDefinition(){
@Override
public boolean isValid(CNode node, Vector<CValidationException> exceptionVector) throws Exception
{
if(node.getNodeType() == Node.ATTRIBUTE_NODE)
{
if(CDocument.XML_NAMESPACE_NS.equals(node.getNamespaceURI()))
{
if("xmlns".equals(node.getLocalName()) || "xmlns".equals(node.getPrefix()))
{
return true;
}
}
}
nodeInvalid("Not a valid xmlns attribute", node, exceptionVector);
return false;
}
};
}
//===========END PROCESS NAMESPACE DECLARATION===============
CNode textNode = null;
if(node.getNodeType() == Node.TEXT_NODE)
{
textNode = node;
node = (CNode) node.getParentNode();
}
CDocument schemaDocument = ((CDocument)node.getOwnerDocument()).getNamespaceSchemaMap().get(node.getNamespaceURI());
try
{
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="+CDocument.XML_SCHEMA_NS);
break;
case Node.ELEMENT_NODE:
schemaDeclElement = (CElement) XPath.selectNSNode(schemaDocument, "//xs:element[@name = '"+node.getLocalName()+"']","xs="+CDocument.XML_SCHEMA_NS);
break;
case Node.TEXT_NODE:
schemaDeclElement = (CElement) XPath.selectNSNode(schemaDocument, "//xs:element[@name = '"+node.getParentNode().getLocalName()+"']","xs="+CDocument.XML_SCHEMA_NS);
default:
break;
}
if(schemaDeclElement != null)
{
return new CNodeDefinition(schemaDeclElement);
}
}
} catch (Exception exception)
{
exception.printStackTrace();
}
return null;
}
}