/** The XLink Filter class is a SAX implementation of an XLink parser
filter.  This class should be used between a SAX parser and the application, and may be
'layered' on top of or underneath other SAX parser filters.
XLinkFilter requires the presence of the Link and LinkSet classes, as well as a SAX parser and the ParserFilter parent class.
* @version 0.23, 11/28/98
* Copyright 1998 Simon St.Laurent 
* Information at: http://www.simonstl.com/projects/xlinkfilter/
* This code is licensed by Simon St.Laurent under the Mozilla Public License.
* See http://www.mozilla.org/MPL/ for details.
* No warranty provided - use at your own risk */

/* Modification history
* 11/14/98 Using new XLinkSet, added makeForwardLink and makeReverseLink -  Toivo Lainevool
* 11/16/98 worked on startElement, looking for "xml:link" in name, added warning stuff - Toivo Lainevool
* 11/17/98 cleaned up to spec - Simon St.Laurent
* 11/19/98 Used super() methods instead of direct calls to theDocumentHandler.  This makes it easier to 'cap' the filter and just collect the side effects, the links.  This will be important during processing of hub groups. -SSL
* 11/19/98 Began adding more comments for methods - SSL
* 11/20/98 Revised URL handling to support relative URLs - all URLs are stored internally as absolute URLs. -SSL
* 11/21/98 Added makeSidewaysLinks() method to support connection across extended links. - SSL
* 11/22/98 Added create functions to support subclassing - Bill la Forge
* 11/22/98 Added "this." to assignment in setDocumentLocator - Bill la Forge
* 11/23/98 Corrected parse of href (was chopping last char from url) - Bill la Forge
* 11/28/98 Added Connection information #, |, or ?XML-XPTR= (Required changes to Link class also) -SSL
* 11/29/98 Added support for HTML A HREF (or a href, A href, a HREF) links.  Created setHTMLProcessing methods. -SSL
* 12/4/98 Added use of LocationFilter, setLocationMark(boolean) method to control.  By default, not used. -SSL
*/


package com.simonstl.sax.xlink;

import org.ccil.cowan.sax.*;
import org.xml.sax.*;
import com.simonstl.sax.location.LocationFilter;
import java.io.IOException;
import java.util.Enumeration;
import java.net.URL;

public class XLinkFilter extends ParserFilter 
   {
   protected Parser theParser;
   protected LinkSet theLinkSet;
   protected AttributeList localAtts;
   protected LinkSet holdingTank;
   protected String baseURL;
   protected Locator locator;
   protected String linkElement;
   protected boolean processHTML=false; //don't process HTML by default.
   protected String htmlLinkType="simple";
   protected boolean markLocation=false;//don't add XPointer to inline link origins.


   // Constructors

   /**zero-argument constructor */
   public XLinkFilter()
      throws IllegalAccessException, InstantiationException,
      ClassNotFoundException 
      {
      super();
      holdingTank = createLinkSet();
      theParser=null;
      }

   /**one-argument constructor
   * @param parser - the SAX parser to be used underneath the filter. */
   public XLinkFilter(Parser parser) 
      {
      super(parser);
      holdingTank = createLinkSet();
      theParser=parser;
      }

   public void setDocumentLocator(Locator locator) 
      {
      this.locator = locator;
      }

/** one-argument setHTMLProcessing turns HTML processing on and off. By default, HTML processing is off.
* @param process boolean value.  True turns HTML processing on, false turns it off.
*/
   public void setHTMLProcessing(boolean process)
      {
	this.processHTML=process;
      }


/** two-argument setHTMLProcessing turns HTML processing on and off, allows more experimentation
* @param process boolean value.  True turns HTML processing on, false turns it off.
* @param linkType String value.  Allows you to change the type of links generated by HTML links.  By default, they'll be simple one-way, but extended could be fun as well, allowing two-way links.
*/
   public void setHTMLProcessing(boolean process, String linkType)
      {
	this.processHTML=process;
	this.htmlLinkType=linkType;
      }

/** setLocationMark turns Location marking on and off. By default, marking is off.
* @param process boolean value.  True turns Location marking on, false turns it off.
* note that you must use the LocationFilter underneath XLinkFilter for this to work!
*/
   public void setLocationMark(boolean mark)
      {
	this.markLocation=mark;
      }


   /**
    * Parse an XML document
    * @param source An InputSource to parse.
    */
   public void parse(InputSource source)
      throws SAXException, IOException 
      {
      baseURL = source.getSystemId();
      super.parse(source);
      }

   /**
    * Parse an XML document from a system identifier (URI).
    * @param systemId A string containing a URL to parse.
    */

   public void parse(String systemId)
      throws SAXException, IOException 
      {
      baseURL = systemId;

      try 
         {
         super.parse(systemId);
         }
      catch (IOException e) 
         {
         e.printStackTrace();
         }
      catch (SAXException e) 
         {
         e.printStackTrace();
         }

      }

   /** setLinkSet allows the application to register a LinkSet that
   should receive all the linking information.  This LinkSet will only
   be added to.  Applications may either pass an empty LinkSet and use
   the merge() LinkSet method to combine it with another LinkSet or pass
   this method the 'master' LinkSet, depending on the complexity of the
   application. For safety, it's definitely best to pass a temporary 
   LinkSet and then merge it if nothing goes horribly wrong.
   @param LinkSet the link set to use*/

   public void setLinkSet(LinkSet linkset) 
      {
      theLinkSet = linkset;
      }


   // DocumentHandler implementation

   public void startElement(String element, AttributeList atts)
      throws SAXException 
      {
         //initialize Strings 
	 String originConnect="#";
         String originLoc = "%none";
         String originContentRole = "none";
         String originContentTitle = "no title";
         String destURL = "none";
	 String destConnect="#";
         String destLoc = "%none";
         String destRole = "none";
         String destTitle = "no title";
         String destShow = "replace";
         String destActuate = "user";
         String destBehavior = "none";
         String linkType=null;

         //initialize booleans to inline, forward link, non-HTML derived
         boolean inline = true; 
         boolean direction = true; 
         boolean linkHTML = false;

	//if attribute name is A, special treatment for HTML links
	if ( (processHTML) && ((element.equals("A"))||(element.equals("a"))) ){
	  linkType=htmlLinkType;
	  linkHTML=true;
        }
	//if location marking on, use it.
	if (markLocation) {
	   LocationFilter tempFilter;
	   tempFilter=(LocationFilter) theParser;
	   originLoc=tempFilter.getLocationAsXPtr();
	}
         //evaluate attributes here
         int length = atts.getLength();

         for (int i = 0; i < length; i++) 
            {

            String attName = atts.getName(i);

            String value = atts.getValue(i);

            String attType=atts.getType(i);

            //DEBUG
/*            System.out.println( "c:" + locator.getColumnNumber()
                     + ",r:" + locator.getLineNumber()
                     + " Element:" + element + "  Name:" + attName + "Value:" + value );
*/
            if (attName.equals("xml:link")) 
               {
               if ((value.equals("simple")) || (value.equals("extended")) || (value.equals("locator")) )
                  {
                  linkType=value;		
                  }
               }

            else if ( (attName.equals("href")) || ( (attName.equals("HREF") && (processHTML) )) ) 
               {
               int testId=value.indexOf('#');
               if (testId != -1)
                  {
		  URL tempURL;
		  URL baseURLtoURL;
		  try {
			  baseURLtoURL=new URL(baseURL);
			  tempURL=new URL(baseURLtoURL,value.substring(0,testId));
			  destURL=tempURL.toString();
                	  destLoc=value.substring(testId+1);
                  } catch (Exception e) {
		    	e.printStackTrace();
		  }//end try-catch

                  }//end if
	else
	 {
		testId=value.indexOf("|");
                if (testId != -1)
                  {
		  URL tempURL;
		  URL baseURLtoURL;

		  destConnect="|";
		  try {
			  baseURLtoURL=new URL(baseURL);
			  tempURL=new URL(baseURLtoURL,value.substring(0,testId));
			  destURL=tempURL.toString();
                	  destLoc=value.substring(testId+1);
                  } catch (Exception e) {
		    	e.printStackTrace();
		  }//end try-catch

                  }//end if
		else
	{
		testId=value.indexOf("?XML-XPTR=");
               if (testId != -1)
                  {
		  URL tempURL;
		  URL baseURLtoURL;

		  destConnect="?XML-XPTR=";
		  destURL=value;
		  destLoc="%none";

                  }//end if
		//original else
		else
                  {
		  URL tempURL;
		  URL baseURLtoURL;
		  
		  try {
			baseURLtoURL=new URL(baseURL);
			tempURL=new URL(baseURLtoURL,value);
			destURL=tempURL.toString();
			destLoc="%none";
		    } catch (Exception e) {
			e.printStackTrace();
		    }//end try-catch

                  }//end original else
		}//end XML-XPTR else
		}//end | else

               
               }//end HREF
            else if (attName.equals("inline") && value.equals("false")) 
               {
               inline=false;
               }

            else if (attName.equals("role")) 
               {
               destRole=value;
               }

            else if (attName.equals("title")) 
               {
               destTitle=value;
               }

            else if (attName.equals("show")) 
               {
               destShow=value;
               }

            else if (attName.equals("actuate")) 
               {
               destActuate=value;
               }

            else if (attName.equals("behavior")) 
               {
               destBehavior=value;
               }

            else if (attName.equals("content-role")) 
               {
               originContentRole=value;
               }

            else if (attName.equals("content-title")) 
               {
               originContentTitle=value;
               }
         
            if (attType.equals("ID")) 
               {
               originLoc=value;
               }
            } //end for

	 
         if (linkType!=null)
            {


	    if ((linkType.equals("simple"))||(linkType.equals("extended"))) {linkElement=element;}
	    
            Link link = createLink(baseURL, originConnect, originLoc, 
                                 originContentRole, originContentTitle, 
                                 destURL, destConnect, destLoc, 
                                 destRole, destTitle,
                                 destShow, destActuate,
                                 destBehavior, linkType,
                                 inline, direction, linkHTML);
            holdingTank.addLink(link);
            }
/*         else
            {
            //There is no type
            //is this right? should we default to simple? What does spec say???
            //createWarning( "Malformed link" );
            }
*/
         //pass on to application's SAX handler
         super.startElement(element, atts);
     
      }

   
   public void endElement(String element)
      throws SAXException 
      {
    /*if this is closing out a simple or extended link, process*/
    if (element.equals(linkElement)) {
      //examine the holding tank
      //if there are no links, do nothing
      int numLinks = holdingTank.size();

      if (numLinks>0) {
         //if the first link is a simple link, process it only
         Link firstLink =  holdingTank.linkAt(0);
         String linkTypeTest=firstLink.getLinkType();

         if (linkTypeTest.equals("simple")) 
            {
            theLinkSet.addLink(firstLink);
	    holdingTank.removeAllLinks();
            }//end simple link
	    
         //if the first link is an extended link, process the set
         else if (linkTypeTest.equals("extended"))
            {
		      Link linkTemp; 
		      Link linkNext;
		      LinkSet localLinks;
		      //  algorithm combines destination links so
		      //  they connect to each other.
		      localLinks=createLinkSet();

		      if (firstLink.getInline() == true) 

		      {
		         for (int i = 1; i<numLinks; i++) 
			 {
		            //connect the first link to all the other links
		            //forward
		            linkNext = holdingTank.linkAt(i); 
                  
                  Link forwardLink = makeForwardLink( firstLink, linkNext );
                  localLinks.addLink( forwardLink );

                  Link reverseLink = makeReverseLink( firstLink, linkNext );
                  localLinks.addLink( reverseLink );
			}//end for
		      }//end inline link
   
      		//connect all the other links to each other
		      for (int i=1; i<numLinks; i++) 
		    {

		     linkTemp = holdingTank.linkAt(i);
			for (int j=i+1; j<numLinks; j++)
			  {
			    linkNext = holdingTank.linkAt(j); 
			    Link sidewaysLink = makeSidewaysLink( linkTemp, linkNext );
			    localLinks.addLink( sidewaysLink );
			    sidewaysLink = makeSidewaysLink( linkNext, linkTemp );
			    localLinks.addLink( sidewaysLink );
                  }//end inner for (j)
               }//end outer for (i)
	       
            //add localLinks to main list	
            theLinkSet.addLinkSet( localLinks );
            }//end the extend link area
         }//end numLinks>0
          //clear the holding tank
	  holdingTank.removeAllLinks();
  	}//end the element match
      //pass on to application's SAX handler
            super.endElement(element);
      }


   void createWarning( String message ) throws SAXException
      {
      warning( new SAXParseException( message, locator ) ); 
      }   

   /** Create a Forward Link.  Takes two links.
   @param firstLink the 'starting' link
   @param linkNext the 'ending' link
    */
   protected Link makeForwardLink( Link firstLink, Link linkNext )
      {
      Link forwardLink = createLink(
                           firstLink.getOriginURL(),
			   firstLink.getOriginConnect(),
                           firstLink.getOriginLoc(),
                           firstLink.getOriginContentRole(), 
                           firstLink.getOriginContentTitle(),
                           linkNext.getDestURL(),
			   linkNext.getDestConnect(),
                           linkNext.getDestLoc(),
                           linkNext.getDestRole(), 
                           linkNext.getDestTitle(),
                           linkNext.getDestShow(), 
                           linkNext.getDestActuate(),
                           linkNext.getDestBehavior(), 
                           "resolved", true, true, false
                           );
      return forwardLink;
      }   

   
   /** Create a reverse Link.
   @param firstLink the 'starting' link (remember, this will be the target because we're reversing)
   @param linkNext the 'ending' link
    */
   protected Link makeReverseLink( Link firstLink, Link linkNext )
      {
      Link reverseLink = createLink(
                           linkNext.getDestURL(),
			   linkNext.getDestConnect(),
                           linkNext.getDestLoc(),
                           linkNext.getDestRole(), 
                           linkNext.getDestTitle(),
                           firstLink.getOriginURL(), 
			   firstLink.getOriginConnect(),
                           firstLink.getOriginLoc(),
                           firstLink.getOriginContentRole(), 
                           firstLink.getOriginContentTitle(),
                           linkNext.getDestShow(), 
                           linkNext.getDestActuate(),
                           linkNext.getDestBehavior(), 
                           "resolved", true, false, false
                           );
      return reverseLink;
      }
      
   /** Create a sideways Link.  Takes two links, and combines their destinations.
   @param firstLink one of the two links to join
   @param linkNext one of the two links to join.
    */
   protected Link makeSidewaysLink( Link firstLink, Link linkNext )
      {
      Link forwardLink = createLink(
                           firstLink.getDestURL(),
			   firstLink.getDestConnect(),
                           firstLink.getDestLoc(),
                           firstLink.getDestRole(), 
                           firstLink.getDestTitle(),
                           linkNext.getDestURL(),
			   linkNext.getDestConnect(),
                           linkNext.getDestLoc(),
                           linkNext.getDestRole(), 
                           linkNext.getDestTitle(),
                           linkNext.getDestShow(), 
                           linkNext.getDestActuate(),
                           linkNext.getDestBehavior(), 
                           "resolved", true, true, false
                           );
      return forwardLink;
      }

   /** Create a link, short version.  Allows overriding of this method within descendants of XLinkfilter. */
   protected Link createLink(String originURL, String originLoc, 
		String originContentTitle, String destURL, 
		String destLoc, String destTitle) 
      {
      return new Link(
               originURL,
               originLoc,
               originContentTitle,
               destURL,
               destLoc,
               destTitle);
      }

   /** Create a link, long version.  Allows overriding of this method within descendants of XLinkfilter. */
   protected Link createLink(String originURL, String originConnect, String originLoc, 
		String originContentRole, String originContentTitle, 
		String destURL, String destConnect, String destLoc, 
		String destRole, String destTitle,
		String destShow, String destActuate,
		String destBehavior, String linkType,
		boolean inline, boolean direction, boolean linkHTML) 
      {
      return new Link(
               originURL,
	       originConnect,
               originLoc,
               originContentRole,
               originContentTitle,
               destURL,
	       destConnect,
               destLoc,
               destRole,
               destTitle,
               destShow,
               destActuate,
               destBehavior,
               linkType,
               inline,
               direction,
               linkHTML);
      }

   /** Create a LinkSet  Allows overriding of this method within descendants of XLinkfilter. */
   protected LinkSet createLinkSet()
      {
         return new LinkSet();
      }
   }
