/**
* Copyright 2015 Santhosh Kumar Tekuri
*
* The JLibs authors license this file to you 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.
*/
package jlibs.xml.xsd;
import jlibs.core.graph.*;
import jlibs.core.graph.navigators.FilteredTreeNavigator;
import jlibs.core.graph.walkers.PreorderWalker;
import jlibs.core.net.URLUtil;
import jlibs.xml.Namespaces;
import jlibs.xml.sax.SAXUtil;
import jlibs.xml.sax.XMLDocument;
import jlibs.xml.sax.helpers.MyNamespaceSupport;
import org.apache.xerces.xs.*;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import java.io.IOException;
import java.net.URL;
import java.util.*;
/**
* @author Santhosh Kumar T
*/
public class XSUtil{
public static MyNamespaceSupport createNamespaceSupport(XSModel model){
MyNamespaceSupport nsSupport = new MyNamespaceSupport();
StringList list = model.getNamespaces();
for(int i=0; i<list.getLength(); i++){
if(list.item(i)!=null) // default namespace is null
nsSupport.declarePrefix(list.item(i));
}
return nsSupport;
}
public static QName getQName(XSObject obj){
return getQName(obj, null);
}
public static QName getQName(XSObject obj, MyNamespaceSupport nsSupport){
if(obj instanceof XSAttributeUse)
obj = ((XSAttributeUse)obj).getAttrDeclaration();
if(obj.getName()==null)
return new QName("");
String ns = obj.getNamespace()==null ? "" : obj.getNamespace();
String prefix;
if(nsSupport==null || (ns.isEmpty() && obj instanceof XSAttributeDeclaration))
prefix = "";
else
prefix = nsSupport.findPrefix(ns);
return new QName(ns, obj.getName(), prefix);
}
public static List<XSComplexTypeDefinition> getSubTypes(XSModel xsModel, XSComplexTypeDefinition complexType){
List<XSComplexTypeDefinition> subTypes = new ArrayList<XSComplexTypeDefinition>();
XSNamedMap namedMap = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
XSObject anyType = namedMap.itemByName(Namespaces.URI_XSD, "anyType");
for(int i=0; i<namedMap.getLength(); i++){
XSObject item = namedMap.item(i);
if(item instanceof XSComplexTypeDefinition){
XSComplexTypeDefinition complexItem = (XSComplexTypeDefinition)item;
if(!complexItem.getAbstract()){
do{
complexItem = (XSComplexTypeDefinition)complexItem.getBaseType();
}while(complexItem!=anyType && complexItem!=complexType);
if(complexItem==complexType)
subTypes.add((XSComplexTypeDefinition)item);
}
}
}
return subTypes;
}
@SuppressWarnings({"unchecked"})
public static List<XSElementDeclaration> guessRootElements(final XSModel model){
XSNamedMap components = model.getComponents(XSConstants.ELEMENT_DECLARATION);
if(components.getLength()==0)
return Collections.emptyList();
final List<XSElementDeclaration> elements = new ArrayList<XSElementDeclaration>(components.getLength());
for(int i=0; i<components.getLength(); i++){
XSElementDeclaration elem = (XSElementDeclaration)components.item(i);
if(!elem.getAbstract())
elements.add(elem);
}
Filter filter = new Filter(){
@Override
public boolean select(Object elem){
return elem instanceof XSModel || elem instanceof XSElementDeclaration;
}
};
Navigator navigator = new FilteredTreeNavigator(new XSNavigator(), filter);
WalkerUtil.walk(new PreorderWalker(model, navigator), new Processor<Object>(){
@Override
public boolean preProcess(Object elem, Path path){
if(path.getRecursionDepth()>0)
return false;
if(elements.size()>1 && path.getLength()>2){
XSElementDeclaration elemDecl = (XSElementDeclaration)elem;
if(elemDecl.getAbstract()){
XSObjectList substitutionGroup = model.getSubstitutionGroup(elemDecl);
for(int i=0; i<substitutionGroup.getLength(); i++){
elements.remove(substitutionGroup.item(i));
if(elements.size()==1)
break;
}
}else
elements.remove(elem);
}
return true;
}
@Override
public void postProcess(Object elem, Path path){}
});
return elements;
}
public static List<String> getEnumeratedValues(XSSimpleTypeDefinition simpleType){
ArrayList<String> enums = new ArrayList<String>();
XSObjectList facets = simpleType.getMultiValueFacets();
if(facets!=null){
for(int i=0; i<facets.getLength(); i++){
XSMultiValueFacet facet = (XSMultiValueFacet)facets.item(i);
if(facet.getFacetKind()==XSSimpleTypeDefinition.FACET_ENUMERATION) {
StringList values = facet.getLexicalFacetValues();
for(int j=0; j<values.getLength(); j++)
enums.add(values.item(j));
}
}
}
return enums;
}
public static XSNamespaceItem getNamespaceItem(XSModel schema, String namespace){
XSNamespaceItemList list = schema.getNamespaceItems();
for(int i=0; i<list.getLength(); i++){
XSNamespaceItem item = list.item(i);
String ns = item.getSchemaNamespace();
if(ns==null)
ns = "";
if(ns.equals(namespace))
return item;
}
return null;
}
/*-------------------------------------------------[ Find Declaration ]---------------------------------------------------*/
private static final RuntimeException STOP_SEARCHING = new RuntimeException("STOP_SEARCHING");
@SuppressWarnings("unchecked")
public static XSElementDeclaration findElementDeclaration(XSModel schema, final List<QName> xpath){
XSNamespaceItem nsItem = XSUtil.getNamespaceItem(schema, xpath.get(0).getNamespaceURI());
if(nsItem==null)
return null;
FilteredTreeNavigator navigator = new FilteredTreeNavigator(new XSNavigator(), new Filter(){
@Override
public boolean select(Object elem){
return elem instanceof XSElementDeclaration;
}
});
final XSElementDeclaration declaration[] = new XSElementDeclaration[1];
Walker walker = new PreorderWalker(nsItem, navigator);
try{
WalkerUtil.walk(walker, new Processor(){
int i = 0;
@Override
public boolean preProcess(Object elem, Path path){
if(path.getParentPath()!=null){
QName qname = XSUtil.getQName((XSElementDeclaration)elem);
if(qname.equals(xpath.get(i))){
i++;
if(i==xpath.size()){
declaration[0] = (XSElementDeclaration)elem;
throw STOP_SEARCHING;
}
return true;
}else
return false;
}
return true;
}
@Override
public void postProcess(Object elem, Path path){}
});
}catch(RuntimeException ex){
if(ex!=STOP_SEARCHING)
throw ex;
}
return declaration[0];
}
@SuppressWarnings("unchecked")
public static XSAttributeDeclaration findAttributeDeclaration(XSModel schema, final List<QName> xpath){
XSNamespaceItem nsItem = XSUtil.getNamespaceItem(schema, xpath.get(0).getNamespaceURI());
if(nsItem==null)
return null;
FilteredTreeNavigator navigator = new FilteredTreeNavigator(new XSNavigator(), new Filter(){
@Override
public boolean select(Object elem){
return elem instanceof XSElementDeclaration || elem instanceof XSAttributeUse;
}
});
final XSAttributeDeclaration declaration[] = new XSAttributeDeclaration[1];
Walker walker = new PreorderWalker(nsItem, navigator);
try{
WalkerUtil.walk(walker, new Processor(){
int i = 0;
@SuppressWarnings("ConstantConditions")
@Override
public boolean preProcess(Object elem, Path path){
if(path.getParentPath()!=null){
QName qname = XSUtil.getQName(elem instanceof XSAttributeUse? ((XSAttributeUse)elem).getAttrDeclaration() : (XSObject)elem);
if(qname.equals(xpath.get(i)) && (i!=xpath.size()-1 || elem instanceof XSAttributeUse)){
i++;
if(i==xpath.size()){
declaration[0] = ((XSAttributeUse)elem).getAttrDeclaration();
throw STOP_SEARCHING;
}
return true;
}else
return false;
}
return true;
}
@Override
public void postProcess(Object elem, Path path){}
});
}catch(RuntimeException ex){
if(ex!=STOP_SEARCHING)
throw ex;
}
return declaration[0];
}
public static void getNamespacePrefixes(final URL xsdURL, final Set<URL> crawled, final Properties prefixes) throws ParserConfigurationException, SAXException, IOException{
crawled.add(xsdURL);
SAXParser saxParser = SAXUtil.newSAXParser(true, false, false);
saxParser.parse(xsdURL.toString(), new DefaultHandler(){
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException{
if(Namespaces.suggestPrefix(uri)==null && !prefixes.containsKey(uri))
prefixes.put(uri, prefix);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException{
try{
if(Namespaces.URI_XSD.equals(uri)){
if("import".equals(localName) || "include".equals(localName)){
String location = attributes.getValue("location");
if(location!=null){
URL url = URLUtil.toURI(xsdURL).resolve(location).toURL();
if(!crawled.contains(url))
getNamespacePrefixes(url, crawled, prefixes);
}
}
}
}catch(Exception ex){
if(ex instanceof SAXException)
throw (SAXException)ex;
else
throw new SAXException(ex);
}
}
});
}
public static void suggestNamespacePrefixes(URL xsdURL, XMLDocument xml) throws IOException, SAXException, ParserConfigurationException{
Properties prefixes = new Properties();
getNamespacePrefixes(xsdURL, new HashSet<URL>(), prefixes);
for(Map.Entry<Object, Object> entry : prefixes.entrySet()){
String uri = (String)entry.getKey();
String prefix = (String)entry.getValue();
xml.suggestPrefix(prefix, uri);
}
}
}