/*
 * @(#)$Id: XalanExtension.java 1554 2003-05-13 23:01:42Z kk122374 $
 *
 * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * This software is the proprietary information of Sun Microsystems, Inc.  
 * Use is subject to license terms.
 * 
 */
package com.sun.msv.schmit;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerException;

import org.apache.xalan.extensions.ExpressionContext;
import org.apache.xalan.extensions.XSLProcessorContext;
import org.apache.xalan.templates.ElemExtensionCall;
import org.apache.xpath.NodeSet;
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 com.sun.msv.driver.textui.DebugController;
import com.sun.msv.grammar.ExpressionPool;
import com.sun.msv.grammar.Grammar;
import com.sun.msv.reader.GrammarReader;
import com.sun.msv.schmit.grammar.AnnotatedPattern;
import com.sun.msv.schmit.reader.relaxng.SchmitRELAXNGReader;

/**
 * Extension element/function definitions for Xalan.
 * 
 * @author
 *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
 */
public class XalanExtension {
    
    /**
     * Stores {@link com.sun.msv.schmit.grammar.AnnotatedElementPattern}
     * for each Element node.
     */
    private final XalanNodeAssociationManager psvi =
        XalanNodeAssociationManager.createInstance();
    
    /**
     * Stores {@link PSVIRecorder} for each document.
     */
    private final XalanNodeAssociationManager schema =
        XalanNodeAssociationManager.createInstance();
    
    /** Used as a node factory. */
    private final Document document;
    
    
    public XalanExtension(Document _document) {
        this.document = _document;
    }
    
    public XalanExtension() throws ParserConfigurationException {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         dbf.setNamespaceAware(true);
         this.document = dbf.newDocumentBuilder().newDocument();
     }
    
    
    /**
     * <code>useSchema</code> extension element that
     * associates a schema to the input.
     */
    public void useSchema( XSLProcessorContext context, ElemExtensionCall call ) throws TransformerException {
        try {
            // obtain the schema location
            Node n = context.getContextNode();
            String href = call.getAttribute("href", n, context.getTransformer());

            // determine the root node
            Element root = getFirstElement(
                // workaround a bug in Xalan. The getOwnerDocument method does't work correctly
                // if the node itself is a document.
                n.getNodeType()==Node.DOCUMENT_NODE?(Document)n:n.getOwnerDocument());
            
            try {
                href = new URL( new URL(call.getBaseIdentifier()), href ).toExternalForm();
            } catch( MalformedURLException e ) {
                ;   // failed to absolutize
            }
            
            // parse the grammar
            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setNamespaceAware(true);
            GrammarReader reader = new SchmitRELAXNGReader(
                new DebugController(false),
                spf,
                new ExpressionPool() );
            
            reader.parse(href);
            Grammar grammar = reader.getResultAsGrammar();
            
    //        // remember this association
    //        schema.put( context.getSourceTree(), new PSVIRecorder(grammar) );
    
            // run PSVI annotation
            new PSVIRecorder(grammar,psvi).annotate(
                // DTM node proxy doesn't support the getDocumentElement method.
                root );
        } catch( RuntimeException re ) {
            re.printStackTrace();
            throw re;
        }
    }
    
    /**
     * Equivalent of the getDocumentElement method.
     */
    private Element getFirstElement( Document document ) {
        for( Node n = document.getFirstChild(); n!=null; n=n.getNextSibling() )
            if( n.getNodeType()==Node.ELEMENT_NODE )
                return (Element)n;
        return null;
    }

    public NodeSet annotation( ExpressionContext context ) throws ParserConfigurationException {
        Element e = document.createElement("dummy");
        buildResult( context.getContextNode(), e );
        return new NodeSet(e);
    }

    public NodeSet annotation( ExpressionContext context, NodeList list ) throws ParserConfigurationException {
        // put all the nodes under a dummy element so that the stylesheets
         // can be written as "schmit:annotation(.)/@test" etc.
        Element e = document.createElement("dummy");
                
        for( int i=0; i<list.getLength(); i++ )
            buildResult( list.item(i), e );

        return new NodeSet(e);
    }
    
    private void buildResult( Node n, Element result ) {
        AnnotatedPattern p = (AnnotatedPattern)psvi.get(n);
            
        if( p==null )   return;
            
        for( Iterator itr=p.getAnnotations().iterator(); itr.hasNext(); ) {
            Node o = document.importNode( (Node)itr.next(), true );
            if( o instanceof Attr )
                result.setAttributeNodeNS((Attr)o);
            else
                result.appendChild( o );
        }
    }
}
