// $Id: hyperdaml.java,v 1.22 2002/08/14 21:49:14 mdean Exp $


/**
 * produce a hypertext version of a DAML/RDF document.
 */
class hyperdaml
{
    static String source = null;
    static String prefix = "";
    /**
     * are we being run as a non-parsed-header CGI script?
     */
    static boolean nph = false;


    static int rdfDepth = 0;	// just in case rdf:RDF is erroneously nested


    static String quoted(String string)
    {
	return '"' + string + '"';
    }


    /**
     * escape HTML characters
     */
    static String escaped(String string)
    {
	StringBuffer retval = new StringBuffer();
	for (int i = 0; i < string.length(); i++)
	    {
		char ch = string.charAt(i);
		switch (ch)
		    {
		    case '<':
			retval.append("&lt;");
			break;
		    case '>':
			retval.append("&gt;");
			break;
		    case '&':
			retval.append("&amp;");
			break;
		    default:
			retval.append(ch);
		    }
	    }
	return retval.toString();
    }


    /**
     * determine if this is the RDF (M&S) namespace
     */
    static boolean rdfNamespace(String namespaceURI)
    {
	return namespaceURI.equals("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    }


    /**
     * determine if this is the named RDF attribute.
     */
    static boolean rdfAttribute(org.xml.sax.Attributes atts, int index, String name)
    {
	String localName = atts.getLocalName(index);

	// be liberal about namespaces
	return localName.equals(name);
    }
    

    static String maybeApplyPrefix(String uri)
    {
	// check if self-relative
	if (uri.equals("")
	    || (uri.charAt(0) == '#'))
	    return uri;
	else 
	    return prefix + uri;
    }


    /**
     * turn a relative URI into an absolute URI
     */
    static String makeAbsolute(String maybeRelative)
    {
	try {
	    return new java.net.URL(new java.net.URL(source),
				    maybeRelative).toString();
	} catch (java.net.MalformedURLException e) {
	    return maybeRelative;
	}
    }


    static void usage()
    {
	System.err.println("Usage:  [-prefix prefix] [-nph] uri-or-file");
	System.exit(1);
    }


    public static void main(String args[])
	throws Exception
    {
	// argument parsing
	int i;
	for (i = 0; i < args.length; i++)
	    {
		String arg = args[i];
		if (arg.charAt(0) == '-')
		    {
			if (arg.equals("-prefix"))
			    {
				if ((i+1) >= args.length)
				    usage();
				prefix = args[++i];
			    }
			else if (arg.equals("-nph"))
			    nph = true;
			else
			    usage();
		    }
		else
		    {
			source = args[i];
			break;
		    }
	    }
	if (i != (args.length - 1))
	    usage();

	javax.xml.parsers.SAXParserFactory spf = javax.xml.parsers.SAXParserFactory.newInstance();
	spf.setValidating(false); // not needed for our use -- reinforce default
	javax.xml.parsers.SAXParser saxParser = spf.newSAXParser();
	org.xml.sax.XMLReader xmlReader = saxParser.getXMLReader();

	xmlReader.setFeature("http://xml.org/sax/features/namespaces", true);

	// get xmlns attributes
	xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);

	org.xml.sax.helpers.DefaultHandler handler = new org.xml.sax.helpers.DefaultHandler()
	    {
		boolean foundDAML = false;
		boolean startedHTML = false;
		org.xml.sax.Locator locator;
		/**
		 * stack indicating whether each level of element has children.
		 */
		java.util.Stack elementStack = new java.util.Stack();

		/**
		 * code to check if this is the first child for the parent element
		 */
		void child()
		{
		    if (elementStack.empty())
			return;

		    Boolean b = (Boolean) elementStack.peek();
		    if (b.equals(Boolean.TRUE))
			{
			    // not first child
			    return;
			}
		    
		    // close element start
		    System.out.print("&gt;");

		    elementStack.pop();
		    elementStack.push(Boolean.TRUE);
		}

		public void setLocator(org.xml.sax.Locator l)
		{
		    locator = l;
		}

		public void startDocument()
		{
		}

		void startHTML()
		{
		    if (startedHTML)
			return;

		    System.out.println("<html>");
		    System.out.println("  <body>");
		    
		    startedHTML = true;
		}

		public void startElement(String namespaceURI, String localName,
					 String rawName, org.xml.sax.Attributes atts)
		    throws org.xml.sax.SAXException
		{
		    if (rdfNamespace(namespaceURI)
			&& localName.equals("RDF"))
			{
			    rdfDepth++;
			    if (! foundDAML)
				{
				    // first time
				    if (nph)
					{
					    System.out.println("HTTP/1.1 200 OK");
					    System.out.println("Content-type:  text/html");
					    System.out.println();
					}
				    startHTML();
				    System.out.println("    <code>");
				    System.out.println("      <pre>");
				}
			    foundDAML = true;
			}

		    if (rdfDepth == 0)
			return;

		    child();	// of any parent
		    elementStack.push(Boolean.FALSE);

		    System.out.print("&lt;<a href=" + quoted(maybeApplyPrefix(makeAbsolute(namespaceURI + localName))) + ">" + rawName + "</a>");

		    // attributes
		    for (int i = 0; i < atts.getLength(); i++)
			{
			    String attrLocalName = atts.getLocalName(i);
			    String attrQName = atts.getQName(i);
			    String value = atts.getValue(i);
			    boolean hyperlink = (rdfAttribute(atts, i, "resource")
						 || rdfAttribute(atts, i, "about")
						 || attrQName.equals("xmlns")
						 || attrQName.startsWith("xmlns:"));

			    if (rdfAttribute(atts, i, "ID"))
				System.out.print("<a name=" + quoted(value) + " />");
			    System.out.print(" " + atts.getQName(i) + "=");
			    if (hyperlink)
				System.out.print(quoted("<a href=" + quoted(maybeApplyPrefix(value)) + ">" + value + "</a>"));
			    else
				System.out.print(quoted(value));
			}
		}

		public void characters(char [] ch, int start, int length)
		{
		    if (rdfDepth == 0)
			return;

		    child();

		    for (int i = 0; i < length; i++)
			System.out.print(ch[start + i]);
		}

		public void ignorableWhitespace(char [] ch, int start, int length)
		{
		    // handle like text
		    characters(ch, start, length);
		}

		public void endElement(String uri, String localName, String qName)
		{
		    if (rdfDepth == 0)
			return;

		    Boolean b = (Boolean) elementStack.pop();
		    if (b.equals(Boolean.FALSE))
			System.out.print("/&gt;");
		    else
			System.out.print("&lt;/<a href=" + quoted(maybeApplyPrefix(makeAbsolute(uri + localName))) + ">" + qName + "</a>&gt;");

		    if (rdfNamespace(uri)
			&& localName.equals("RDF"))
			{
			    rdfDepth--;
			    System.out.println("<br />");
			}
		}

		/**
		 * redirect to the original document if it doesn't seem to contain DAML
		 */
		void maybeRedirect()
		{
		    if (nph && (! foundDAML))
			{
			    System.out.println("HTTP/1.1 301 Redirect");
			    System.out.println("Location:  " + source);
			    System.out.println();
			    System.exit(0);
			}
		}

		public void endDocument()
		{
		    maybeRedirect();

		    startHTML();
		    if (foundDAML)
			{
			    System.out.println();
			    System.out.println("        </pre>");
			    System.out.println("      </code>");
			}
		    else
			{
			    System.out.println("    No DAML or RDF content found.");
			}
		    
		    System.out.println("    <hr />");
		    System.out.println("    <address>");
		    System.out.println("      Produced from");
		    System.out.println("      <a href=" + quoted(source) + ">" + source + "</a>");
		    System.out.println("      using");
		    System.out.println("      <a href=" + quoted("http://www.daml.org/2001/04/hyperdaml/") + ">hyperdaml</a>.java");
		    System.out.println("    </address>");
		    System.out.println("  </body>");
		    System.out.println("</html>");
		}

		void printException(String label,
				    org.xml.sax.SAXParseException e)
		{
		    maybeRedirect();
		    startHTML();
		    System.out.println("<br />");
		    System.out.println("<b>" + label + ":  " + escaped(e.toString()) + "</b>");
		    System.out.println("<br />");
		}

		public void warning(org.xml.sax.SAXParseException e)
		{
		    printException("warning", e);
		}

		public void error(org.xml.sax.SAXParseException e)
		{
		    printException("recoverable error", e);
		}

		public void fatalError(org.xml.sax.SAXParseException e)
		{
		    printException("fatal error", e);
		    endDocument();
		    System.exit(1);
		}
	    };
	xmlReader.setContentHandler(handler);
	xmlReader.setErrorHandler(handler);
	xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler",
			      new org.xml.sax.ext.LexicalHandler()
				  {
				      public void comment(char [] ch, int start, int length)
				      {
					  if (rdfDepth == 0)
					      return;
					  
					  System.out.print("&lt;!--");
					  for (int i = 0; i < length; i++)
					      System.out.print(ch[start + i]);
					  System.out.print("--&gt;");
				      }
				      
				      public void endCDATA()
				      {
				      }
				      
				      public void endDTD()
				      {
				      }
				      
				      public void endEntity(String name)
				      {
				      }
				      
				      public void startCDATA()
				      {
				      }
				      
				      public void startDTD(String name, String publicId, String systemId)
				      {
				      }
				      
				      public void startEntity(String name)
				      {
				      }
				  });

	xmlReader.parse(source);
    }
}

