/*
 * PaperWeb publication display.  Version 0.9.
 *
 * Copyright 2004 John Kubiatowicz and the University of Berkeley.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

var topversion="PaperWeb Version 0.95";
var versionid="$Id: paperweb.js,v 1.21 2005/02/23 00:38:08 kubitron Exp $";
var paperlist = [];
var schemalist = [];
var catgroups = [];
var visitordata = null;

var months=["January","February","March","April","May","June",
	    "July","August","September","October","November","December","Undefined"];
months.monthsize=[31,29,31,30,31,30,31,31,30,31,30,31,0];
months.ERROR=13;

var savewin=null;
var EditMode=false;
var UserResponse=null;
var undolist=[];
var editlist=[];

// Path in same directory as top.document.URL
var CGIReturnScript="./cgianswer.html";

if (!window.Node) {
    var Node= {
	ELEMENT_NODE: 1,
	ATTRIBUTE_NODE: 2,
	TEXT_NODE: 3,
	CDATA_SECTION_NODE: 4,
	ENTITY_REFERENCE_NODE: 5,
	ENTITY_NODE: 6,
	PROCESSING_INSTRUCTION_NODE: 7,
	COMMENT_NODE: 8,
	DOCUMENT_NODE: 9,
	DOCUMENT_TYPE_NODE: 10,
	DOCUMENT_FRAGMENT_NODE: 11,
	NOTATION_NODE: 12 };
}

/*
 * This is the primary entry point from the top-level frame.
 *
 * Action: Load XML file, then trigger the XMLparsed();
 */
function loaddocuments(XMLurl) {
    var argv=getArgv(location.search);
    var XMLFile = (argv.database?argv.database:XMLurl);

    EditMode = (argv.edit)?true:false;
    window.status="LOADING "+XMLFile+"...";
    
    infomessage(content.document,"One Moment.<br>Loading Database ("+XMLurl+")...");
    if (!loadXML(XMLFile,XMLparsed)) {
	errormessage(content.document,"Cannot load "+XMLurl+": Unsupported Browser.<br>Try Netscape 6.1+, Mozilla 1.4x, Firefox, Apple Safari, <br>Opera 7.6+, or IE 5.5+");
	window.status="";
    }
}

function XMLparsed(XMLurl,XMLdoc) {
    if (!(XMLdoc.getElementsByTagName("schema").length) ||
	!(XMLdoc.getElementsByTagName("paper").length)) {
	errormessage(content.document,"Database "+XMLurl+" Malformed!");
	return;
    }
    
    window.status="PARSING "+XMLurl+"...";
    schemalist=loadschemas(XMLdoc);  // Grab Schemas
    catgroups=loadcatgroups(XMLdoc); // Grab category groups
    paperlist=loadpapers(XMLdoc);    // Grab papers
    
    window.status="";
    getBrowsingInfo();		     // Try cookies if possible

    /*
     * Force a reload of content window (and make sure that search
     * string in content window matches top window.
     */
    var topargv=getSchemaIndex(location.search);
    var myargv=getSchemaIndex(content.location.search);
    if (myargv.abs || (myargv.schema && (myargv.index==topargv.index) &&
		       (myargv.sortdir==topargv.sortdir)))
	content.location.reload();
    else { 
	content.location.replace(content.location.pathname+
				 schemaString(topargv.index,topargv.sortdir));
    }
}

    
/****************************************************************
 *								*
 * Object Definitions for Schemas, Category Groups, and Papers. *
 *								*
 * Note: the notion of cloning objects in Javascript is not as	*
 * clean as in, say, Java, so we do a partial job.  We defined  *
 * clone methods for Papers, Document Sources, and XREFs, but   *
 * not for primitive types like arrays; the extra properties	*
 * make dealing with primitive types difficult.			*
 *								*
 ****************************************************************/

 /*
  * Define the Schema class:
  *
  * Schemas consist of names, special identifiers, and a list of
  * sections (see definition of the Section object).
  *
  * Valid special identifiers:
  *	DATEGEN: The input schema is a prototype. Each section
  *		 is expanded into N different sections, one for
  *		 each year.
  */
function Schema(name,special) {
    this.name = name;
    this.special = special;
    this.sections = []; // Empty section array
}

/*
 * Define the Section class. Sections are part of the section lists in
 * Schemas.
 *
 * Args: id, menu		required
 *	 heading,matchfn	optional
 */
function Section(id,menu,heading,matchfn) {
    this.id = "sect-"+id;
    this.menu = menu;
    this.heading = heading?heading:"<Missing Section Header>";
    this.matchfn = matchfn?matchfn:function(paper){return true;};
}

/*
 * Define the category group class (CatGroup).
 *
 * CatGroups consist of a name and list of category definitions
 * (CatDef objects).
 */
function CatGroup(name) {
    this.name=name;
    this.catdef=[];
}

function CatDef(group,id,name,sourcetype,bibtype) {
    this.group=group;
    this.id=id;
    this.name=name?name:id;
    this.sourcetype=sourcetype;
    this.bibtype=bibtype;
}


/*
 * Define the Papers class.
 *
 * Papers have many subcomponents. Note that there are two different
 * IDs assigned to a paper: the unique id, and the "xrefid".  The
 * unique id is autogenerated, while the xrefid is in the database and
 * used for cross-references.
 */
function Paper(xrefid,uniqueid) {
    this.project = null;
    this.docroot = null;
    this.id = xrefid;
    if (uniqueid)
	this.uniqueid = uniqueid;
    else
	this.uniqueid = UniquePaperId();
    this.title="";
    this.publication="";
    this.specifics="";
    this.author = [];	 	// author list 
    this.category = []; 	// category list
    this.source = [];		// list of sources
    this.xref = [];	 	// list of cross refs.
    this.flag=EditState.IDLE;
}

/******** Paper methods *****************/

/*
 * Return an ID for this paper.  Either the xrefid, or (if this
 * doesn't exist), the uniqueid (which always exists).
 */
Paper.prototype.getID = function() {
    return this.id?this.id:this.uniqueid;
};

/*
 * Return true if the paper has the specified category.
 */
Paper.prototype.hasCategory = function(cat) {
    for (var i=0; i<this.category.length; i++) {
	if (this.category[i]==cat)
	    return true;
    }
    return false;
};
	

/*
 * Create a "deep-copy" duplicate of Paper.  Unless the 'dest'
 * parameter is present, create new paper.
 *
 * Input:	[optional] Paper structure to fill.  If omitted,
 * 			   function creates new paper with new unique ID
 *
 * Output:	Copy of input paper
 *
 * Things get screwed up if Array has a clone method, so assume that
 * top-level objects in Papers are arrays.
 * Note that
 *
 */
Paper.prototype.clone = function(dest) {
    var newpaper = dest?dest:(new Paper(this.id));

    for (var prop in this) {
	// Don't copy some fields
	if (prop=="uniqueid" || prop=="flag") continue;
	
	if (this[prop]==null || (typeof this[prop] != "object"))
	    newpaper[prop] = this[prop];
	else 
	    newpaper[prop] = DeepArrayCopy(this[prop]);
    }
    return newpaper;
};

function DeepArrayCopy(thearray) {
    var newarray=new Array(thearray.length);
    for (var i=0; i<thearray.length; i++) {
	if (thearray[i]==null || ((typeof thearray[i]) != "object"))
	    newarray[i]=thearray[i];
	else if (!(thearray[i].clone)) 	    
	    // Assume an array
	    newarray[i]=DeepArrayCopy(thearray[i]);
	else
	    newarray[i]=thearray[i].clone();
    }
    return newarray;
}

/*
 * Return true if a paper has the minimum acceptable set of
 * properties.
 */
Paper.prototype.valid = function() {
    if ((this.title) && (this.author.length) && (this.source))
        return true;
    else
        return false;
};

/****** End of Paper methods ********/

/*
 * Define the DocSource class
 *
 * This object represents a source (such as a PDF) for the document.
 */
function DocSource(link,format,size) {
    this.link = link;
    this.format = format;
    this.size = size;
}

/**** Clone function for DocSources ****/
DocSource.prototype.clone=function() {
    var newdoc=new DocSource(this.link,this.format,this.size);
    return newdoc;
};

/*
 * Define the Xref class
 *
 * This represents a cross-reference entry for the document.  Xrefs
 * are distinguished from DocSources in that they are typically
 * referencing other papers or unrelated links.
 *
 * The isxref parameter is true if the "link" is an xrefid for some
 * other paper in the paperlist.  It is false if the "link" is a URL.
 */
function Xref(isxref,link,name) {
    this.isxref = isxref;
    this.link = link;
    this.name = name;
}

/**** Clone function for Xrefs ****/
Xref.prototype.clone=function() {
    var newxref=new Xref(this.isxref,this.link,this.name);
    return newxref;
};


/****************************************************************
 *								*
 * Code for parsing the XML into internal structures, using the *
 * above object definitions 					*
 *								*
 ****************************************************************/

/*
 * This function uses browser-specific code to create a parsed XML
 * document that can be traversed with DOM-level mechanisms.
 *
 * Unfortunately, this will not work on old browsers...
 */
function loadXML(XMLurl,handler) {    
    // Prevent Caching by making URL unique
    var nocacheXMLurl=
	XMLurl+((XMLurl.indexOf('?')+1)?'&':'?')+(new Date()).getTime();
	
    if (window.XMLHttpRequest) {
	// alternate XMLHTTP request -- Gecko, Safari 1.2,  Opera 7.6+
	var XMLreq = new XMLHttpRequest();
	XMLreq.onreadystatechange = function() { // Specify onload
	    if (XMLreq.readyState == 4) {
 	        if (XMLreq.status==200)
	            handler(XMLurl,XMLreq.responseXML);
		else
		errormessage(content.document,
			    "Error retrieving "+XMLurl+":"+XMLreq.status+":"+XMLreq.statusText);
	    }
	}
	XMLreq.open("GET",XMLurl,true);
	XMLreq.send(null);
	return true;
    } else if (document.implementation && document.implementation.createDocument) {
	// Create a new Document object
	var XMLdoc = document.implementation.createDocument("", "", null);
	// Specify what should happen when it finishes loading
	XMLdoc.onload = function() { handler(XMLurl,XMLdoc); }
	// And tell it what URL to load
	XMLdoc.load(XMLurl);
	return true;
    } else if (window.ActiveXObject) {
	// Otherwise, use Microsoft's proprietary API for Internet Explorer
	var XMLdoc = new ActiveXObject("Microsoft.XMLDOM"); // create doc
	XMLdoc.onreadystatechange = function() { // Specify onload
	    if (XMLdoc.readyState == 4) handler(XMLurl,XMLdoc);
	}
	XMLdoc.load(XMLurl);
	return true;
    } else {
	return false;
    }
}     

/*
 * This function walks the included schemas and produces a list of
 * them.  Note that each schema includes header material as well as
 * an expression that describes which papers will match.
 *
 * Input:  parsed XML document
 * Output: list of schema objects:
 *	{ id, menu, header, matchfn }
 */
function loadschemas(XMLdoc) {
    var schemas = XMLdoc.getElementsByTagName("schema");
    var resultlist=[];

    for (var i=0; i<schemas.length; i++) {
	var element = schemas[i];
	var newschema =
	    new Schema(normWS(element.getAttribute("name")),
		       normWS(element.getAttribute("special")));
	
	var sections=element.getElementsByTagName("section");
	
	for (var j=0; j<sections.length; j++) {
	    var sectionelement=sections[j];
	    var newsection =
		new Section(normWS(sectionelement.getAttribute("id")),
			    normWS(sectionelement.getAttribute("menu")));
	    
	    var children=sectionelement.childNodes;
	    for (k=0; k<children.length; k++) {
		var child=children[k];
		if (child.nodeType == Node.ELEMENT_NODE && child.firstChild) {
		    switch (child.tagName) {
		    case 'heading':
			newsection.heading=normWS(child.firstChild.nodeValue);
			if (!newsection.menu)
			    newsection.menu=normWS(newsection.heading);
			break;
		    case 'match':
			var temp = makefilter(child.childNodes,7);
			if (temp) {
			    newsection.matchfn=temp[0].fun;
			    newsection.matchexpn=temp[0].xml;
			} else {
			    newsection.matchfn=function(paper){return true;};
			}
			break;
		    }
		}
	    }
	    (newschema.sections).push(newsection);
	}
	resultlist.push(newschema);
    }
    
    return resultlist;
}

/*
 * Load in category groups from the XML file.
 */
function loadcatgroups(XMLdoc) {
    var catgroups=XMLdoc.getElementsByTagName("catgroup");
    var resultlist=[];
    resultlist.assoc=[];
    resultlist.special=[];

    for (var i=0; i<catgroups.length; i++) {
	var element=catgroups[i];
	var newcatgroup=new CatGroup(normWS(element.getAttribute("name")));
	var catdefs=element.getElementsByTagName("catdef");

	for (var j=0; j<catdefs.length; j++) {
	    var sectionelement=catdefs[j];

	    if (sectionelement.firstChild) {
		var newdef=
		    new CatDef(newcatgroup,
			       normWS(sectionelement.getAttribute("id")),
			       normWS(sectionelement.firstChild.nodeValue),
			       normWS(sectionelement.getAttribute("sourcetype")),
			       normWS(sectionelement.getAttribute("bibtype")));
		if (newdef.id && newdef.name) {
		    newcatgroup.catdef.push(newdef);
		    resultlist.assoc[newdef.id]=newdef;
		    
		    if (newdef.sourcetype || newdef.bibtype)
			resultlist.special[newdef.id]=newdef;
		}
	    }
	}
	resultlist.push(newcatgroup);
    }
    return resultlist;
}		

function loadpapers(XMLdoc) {
    // The result;
    var resultlist=[];
    
    // Grab root URL for document sourcee
    var docroot =normWS(XMLdoc.getElementsByTagName("docroot")[0].firstChild.nodeValue);
    docroot=docroot.replace(/\/$/,"");
    resultlist.docroot=docroot;
    
    // Grab project name
    var project = normWS(XMLdoc.getElementsByTagName("project")[0].firstChild.nodeValue);
    resultlist.project=project;

    var version = XMLdoc.getElementsByTagName("version");
    if (version && version[0].firstChild)
	resultlist.version=normWS(version[0].firstChild.nodeValue);

    var update = XMLdoc.getElementsByTagName("update");
    if (update && update[0].firstChild) {
	resultlist.updateid=update[0].getAttribute("id");
	resultlist.update=normWS(update[0].firstChild.nodeValue);
    }

    // Get parsed list of papers
    var papers = normWS(XMLdoc.getElementsByTagName("paper"));
    for (var i=0; i<papers.length; i++) {	
	var element = papers[i];
	var newpaper =
	    new Paper(normWS(element.getAttribute("id")));
	var children = element.childNodes;

	for (var j=0; j<children.length; j++) {
	    var child=children[j];
	    if (child.nodeType == Node.ELEMENT_NODE && child.firstChild) {
		switch (child.tagName) {
		case 'author':
		case 'category':
		    var temp=(normWS(child.firstChild.nodeValue)).split(/\s*,\s*/);	
		    newpaper[child.tagName]=newpaper[child.tagName].concat(temp);
		    break;
		case 'abstract':
		    newpaper["abstext"]=extractHTML(child.childNodes);
		    break;
		case 'copyright':
		    newpaper["copyright"]=extractHTML(child.childNodes);
		    break;
		case 'source':
		    var newsource=
			new DocSource(normWS(child.firstChild.nodeValue),
				      normWS(child.getAttribute("format")),
				      normWS(child.getAttribute("size")));

		    newpaper["source"].push(newsource);
		    break;
		case 'xref':
		    var tempid=normWS(child.getAttribute("id"));
		    var templink=normWS(child.getAttribute("link"));
		    			
		    var newxref = new Xref((tempid?true:false),
					   (tempid?tempid:templink),
					   normWS(child.firstChild.nodeValue));
		    if (newxref.link && newxref.name) {
			newpaper["xref"].push(newxref);
		    }
		    break;
		case 'month':
		case 'month2':
		    var monthid=normalizeMonth(normWS(child.firstChild.nodeValue));
		    if (monthid)
			newpaper[child.tagName]=monthid;
		    break;
		case 'docroot':
		    var localroot=normWS(child.firstChild.nodeValue);
		    var localroot=localroot.replace(/\/$/,"");
		    newpaper[child.tagName]=docroot;
		    break;
		default:
		    newpaper[child.tagName]=normWS(child.firstChild.nodeValue);
		    break;
		}
	    }
	}
	if (newpaper.valid()) {
	    if (!newpaper.project)
		newpaper.project=project;
	    if (!newpaper.docroot)
		newpaper.docroot=docroot;
	    
	    if (newpaper.month2 && !newpaper.month) {
		newpaper.month=newpaper.month2;
		delete newpaper.month2;
	    }

	    newpaper.authorlist=normalizeAuthorlist(newpaper.author);

	    // Save result
	    resultlist.push(newpaper);
	}
    }

    // Sort things in forward direction/compute global info
    reflowPaperlist(resultlist); 
    return resultlist;
}

function extractHTML(elementlist) {
    var currentHTML="";
    for (var i=0; i<elementlist.length; i++) {
	var node = elementlist[i];
	if (node.nodeType == Node.ELEMENT_NODE || node.nodeType == Node.ENTITY_NODE) {
	    var internalHTML=extractHTML(node.childNodes);
	    currentHTML+='<'+node.tagName+'>'+internalHTML+'</'+node.tagName+'>';
	} else {
	    if (currentHTML)
		currentHTML+=' ';
	    currentHTML+=node.nodeValue;
	}
    }
    return currentHTML;
}

/*
 * This function produces a nice printed form of the author list.
 *
 * Cases: 	1 author=> 	That author
 *		2 authors=>	"Author1 and Author2"
 *		3 or more=>	"author1, author2, .. and authorN")
 */
function normalizeAuthorlist(authorarray) {
    var result;
    
    switch (authorarray.length) {
    case 0:
	result="";
	break;
    case 1:
	result=authorarray[0];
	break;
    case 2:
	result=(authorarray[0]+" and "+authorarray[1]);
	break;
    default:
	result=(authorarray.slice(0,-1)).join(", ")+
	       ", and "+authorarray[authorarray.length-1];
	break;
    }
    return result;
}
    
function normalizeMonth(monthINFO) {
    if (!monthINFO)
	return false;
    if (!(monthid=parseInt(monthINFO))) {
	for (monthid=0; monthid<months.length; monthid++) {
	    if (((months[monthid]).toLowerCase()).indexOf(monthINFO.toLowerCase()) != -1)
		return monthid+1;
	}
	return months.ERROR;
    } else {
	if (monthid<1 || monthid>12)
	    return months.ERROR;
	else
	    return monthid;
    }
}

/*
 * This function produces an array of lambdas() that operate on paper
 * descriptors and produce booleans.  It also (reproduces) an XML
 * description of the function.
 *
 * It is recursively described and handles operators "not", "and", and
 * "or".
 *
 * Note that it does two things: produce a function that filters
 * according to the criteria, and produces a formatted version of the
 * XML for later output.
 */

makefilter.lotsofspaces="                                                                       ";

function makefilter(children,indentlevel) {
    var theresult=[];

    for (var i=0; i<children.length; i++) {
	var child=children[i];
	
	var newfunction=null;
	var newdescriptor=null;
	switch(child.tagName) {
	case 'property':
	    var propname=child.getAttribute("name");

	    if (!propname || !child.firstChild || !child.firstChild.nodeValue) {
		// Null constraint -- return true.
		newfunction=function(paper){return true;}
		newdescriptor='<property name="'+propname+'"></property>';
		break;
	    }
	    var propvalue=(child.firstChild.nodeValue).toLowerCase();

	    switch(propname) {
	    case 'author':
	    case 'category':
		// Array properties.  Deal with it.
		newfunction=function(paper) {
		    var theprop=paper[propname];

		    for (i=0; i<theprop.length; i++) 
			if (theprop[i].indexOf(propvalue) != -1)
			    return true;
		    return false;
		}
		break;
	    default:
		// Scalar properties
		newfunction=function(paper) {
		    var theprop=paper[propname];

		    if (theprop != null && theprop != undefined &&
			theprop.toString().indexOf(propvalue) != -1)
		        return true;
		    else
		        return false;
		}
	    }
	    newdescriptor='<property name="'+propname+'">'+propvalue+'</property>';
	    break;
	case 'not':
	    var resultlist=makefilter(child.childNodes,(indentlevel+5));
	    newfunction=makenot(resultlist);
	    
	    if (resultlist.length==0) {
		newdescriptor="<not></not>";
	    } else {
		newdescriptor="<not>"+resultlist[0].xml+"</not>";
	    }
	    break;
	case 'and':
	    var resultlist=makefilter(child.childNodes,(indentlevel+5));
	    newfunction=makeand(resultlist);
	    
	    newdescriptor="<and>";
	    for (var x=0; x<resultlist.length; x++) {
		if (x>0)
		    newdescriptor+="\n"+makefilter.lotsofspaces.substring(0,(indentlevel+5));
		newdescriptor+=resultlist[x].xml;
	    }
	    newdescriptor+="</and>";
	    break;
	case 'or':
	    var resultlist=makefilter(child.childNodes,(indentlevel+4));
	    newfunction=makeor(resultlist);

	    newdescriptor="<or>";
	    for (var x=0; x<resultlist.length; x++) {
		if (x>0)
		    newdescriptor+="\n"+makefilter.lotsofspaces.substring(0,(indentlevel+4));
		newdescriptor+=resultlist[x].xml;
	    }
	    newdescriptor+="</or>";
	    break;
	}
	if (newdescriptor) {
	    theresult.push({fun:newfunction, xml:newdescriptor});
	}
    }
    return theresult;
}

/*
 * Make a "not" function
 */
function makenot(resultlist) {
    if (resultlist.length==0) {
	return function(paper) {
	    return true;
	};
    } else {
	return function(paper) {
	    return !((resultlist[0].fun)(paper));
	};
    }
}
    
/*
 * Make an "and" function.
 */
function makeand(resultlist) {
    return function(paper) {
	for (var x=0; x<resultlist.length; x++) {
	    if (!((resultlist[x].fun)(paper)))
		return false;
	}
	return true;
    }
}

/*
 * Make an "or" function.
 */
function makeor(resultlist) {
    return function(paper) {
	for (var x=0; x<resultlist.length; x++) {
	    if ((resultlist[x].fun)(paper))
		return true;
	}
	return false;
    }
}

/*
 * Modify a filter by adding the additional constraint of matching the
 * year....
 */
function tweekFilterByYear(oldfilter,year) {
    return function(paper) {
	if ((paper.year==year) && oldfilter(paper))
	    return true;
	else
	    return false;
    };
}

/****************************************************************
 *								*
 * Code for displaying and refreshing the content windows.	*
 *								*
 ****************************************************************/

/*
 * This function is called by the content subpage during loading to
 * build the page.
 */
function refreshContent(window) {
    disableCantWork(content.document);
    if (!paperlist.length) {
	// Oops.  Not loaded yet.
	return;
    }
    var absargs=getSchemaIndex(content.location.search);

    // Done Loading and Sorting: setup for menu 
    makenavigate(header.document,"parent.schemaChange();","parent.schemaChange();",
		 absargs.index,absargs.sortdir);
    if (absargs.abs && paperlist.assoc[absargs.abs]) {
	outputSchema(null,header.document,absargs.index,absargs.sortdir);
	displayabstract(absargs.abs);
    } else {
	outputSchema(content.document,header.document,absargs.index,absargs.sortdir);
        laterContentScrollTo(getFocusSelect());
	unsetFocusSelect();
    }
}

/*
 * Disable the "CantWork" message since Javascript seems to work...
 */
function disableCantWork(mainhandle) {
    // Since Javascript is clearly enabled, turn off "CantWork" message
    var errorchunk=mainhandle.getElementById("errorchunk");
    if (errorchunk)
	errorchunk.style.display="none";
}
    
/*
 * Put a message in the information window.
 */

function infomessage(mainhandle,message,error) {
    var infochunk=mainhandle.getElementById("infochunk");
    var errorchunk=mainhandle.getElementById("errorchunk");
    if (error) {
	var selector=mainhandle.getElementById("errormess");
	if (selector) 
	    selector.innerHTML=message;
	// Make sure to turn the error message back on
	if (infochunk)
	    infochunk.style.display="none";
	if (errorchunk)
	    errorchunk.style.display="block";
    } else {
	var selector=mainhandle.getElementById("infomess");
	if (selector) 
	    selector.innerHTML=message;
	// Make sure to turn the error message back on
	if (infochunk)
	    infochunk.style.display="block";
	if (errorchunk)
	    errorchunk.style.display="none";
    }
}

function errormessage(mainhandle,message){
     infomessage(mainhandle,message,true);
}
    
/*
 * Actually output the selected schema.
 */
function outputSchema(mainhandle,menuhandle,index,sortdir) {
    var menuitems = [];
    var presentitems = [];
    var xrefitems = [];
    var myschema=schemalist[index];

    /* Hm... Autogenerated schema -- go create it */
    if (myschema.special)
	myschema=getSpecialSchema(myschema);
    
    /* Make sure that we have a clean slate on tags */
    resetUniqueTag();
    
    outputProlog(mainhandle);

    for (var i=0; i<myschema.sections.length; i++) {
	var cursec=myschema.sections[i];
	var isvisible=false;
	matchfun=cursec.matchfn;
	
	for (var j=0; j<paperlist.length; j++) {
	    if (sortdir == sortOrder.FORWARD)
		var thepaper=paperlist[j];
	    else
		var thepaper=paperlist[paperlist.length-j-1];
	    
	    if (matchfun(thepaper) && !(thepaper.flag&EditState.INVISIBLE)) {
		// Have a matching document!
		if (!isvisible) {
		    // Need to actually place header, anchor, etc.
		    outputSectionProlog(mainhandle,cursec);
		    
		    // Save pair of menu text/anchor name for menu
		    menuitems.push({menu:cursec.menu,id:cursec.id});
		    isvisible=true;
		}
		outputEntry(mainhandle,thepaper);

		/* Keep track of present ids and xref ids (match up later) */
		presentitems[thepaper.getID()]=true; 	// Have this item
		for (var k=0; k<thepaper.xref.length; k++) {
		    if (thepaper.xref[k].isxref)
			xrefitems[thepaper.xref[k].link]=true;  // Referencing this
		}
	    }
	}
	if (isvisible) 
	    // Actually output something.  Close out section
	    outputSectionEpilog(mainhandle,cursec);
    }

    /*
     * Now: figure out if any referenced items were omitted.  If
     * they were, we need to output them.
     */
    var missing=[];
    for (var item in xrefitems) {
        if (item in presentitems)
	    continue;

	// Found a missing one!
	if (item in paperlist.assoc) {
	    missing.push(paperlist.assoc[item]);
	}
    }
    if (missing.length) {
	var specialsec={menu:"Additional",
			id:"sect--add",
			heading:"Additional Reference"+((missing.length==1)?"":"s")};

	sortOrder(sortdir,missing);
	menuitems.push(specialsec);
	outputSectionProlog(mainhandle,specialsec);
	for (var i=0; i<missing.length; i++) {
	    outputEntry(mainhandle,missing[i]);
	}
	outputSectionEpilog(mainhandle,specialsec);
    }

    /*
     * Finally, include any editing papers that didn't get displayed.
     */
    var isvisible=false;
    for (var i=0; i<paperlist.editing.length; i++) 
	if (!(paperlist.editing[i] in presentitems)) 
	    isvisible=true;
    if (isvisible) {
	var editsec={menu:"Uncommitted Invisible",
		     id:"sec--edit",
		     heading:"Uncommited Papers Outside Schema"};
	menuitems.push(editsec);

	outputSectionProlog(mainhandle,editsec);
	for (var i=0; i<paperlist.editing.length; i++) {
	    var item=paperlist.assoc[paperlist.editing[i]];
	    if (item in presentitems)
		item.flag &= ~EditState.OUTSIDESCHEMA;
	    else {
		item.flag |= EditState.OUTSIDESCHEMA;
		outputEntry(mainhandle,item);
	    }
	}
	outputSectionEpilog(mainhandle,editsec);
    }
    
    if (mainhandle) {
	mainhandle.write("<br><br><hr size=4>");
	for (var i=0;i<40;i++)
	    mainhandle.write('<br>');
    }
    // Do the "about" link
    var aboutsec={menu:"About",
		  id:"sec--about",
		  heading:"About Software"}
    menuitems.push(aboutsec);
    outputSectionProlog(mainhandle,aboutsec);
    outputAbout(mainhandle);
    outputSectionEpilog(mainhandle,aboutsec);

    if (mainhandle) {
	outputAnchor(mainhandle,'_bottom_');
    }
    
    outputEpilog(mainhandle);
    outputNavigation(menuhandle,menuitems);
}

/*
 * Autoconstruct one of the specialized schemas (there is only 1 at
 * the moment, namely 'DATEGEN').
 *
 * DATEGEN: The input schema is a prototype.  Each section is expanded
 * into N different sections, one for each year represented by papers
 * in the papers list.  During the expansion, the "id", "menu", and
 * "heading" fields of the prototype section are scanned, and any
 * instances of $year are replace by the year. Further, the match
 * function (if it exists) is modified to filter by year as well.
 */
function getSpecialSchema(schema) {
    switch(schema.special) {
    case "DATEGEN":
	var result=new Schema(schema.name);
	var argv=getSchemaIndex(content.location.search);

	/*
	 * Ok.  We are going to generate sections for every year
	 * between range of years.
	 */
	if (argv.sortdir == sortOrder.FORWARD) {
	    first=paperlist.lowyear;
	    second=paperlist.highyear+1;
	    order=1;
	} else {
	    first=paperlist.highyear;
	    second=paperlist.lowyear-1;
	    order=-1;
	}

	for (var i=0; i<schema.sections.length; i++) {
	    // This is the prototype schema.  Expand by year. 
	    var protoschema=schema.sections[i];

	    for (var year=first; year != second; year += order) {
		// Replace $year by actual year (id, menu, heading)
		var newschema=
		    new Section((protoschema.id).replace(/\$year/g,year),
				(protoschema.menu).replace(/\$year/g,year),
				(protoschema.heading).replace(/\$year/g,year));

		// Add constraint of year to match function
		newschema.matchfn=tweekFilterByYear(protoschema.matchfn,year);

		(result.sections).push(newschema);
	    }
	}
	return result;
    default:
	// Unknown special schema -- return old one
	return schema;
    }
}

function displayabstract(paperid) {
    var thepaper=paperlist.assoc[paperid]; 
    var thetarget=((visitordata.$usecookies)&&(!EditMode))?"_top":"_blank";
    var newhistory=true;

    top.status="";

    /*
     * Note that we *were* trying to set replace based on
     * "newhistory", but this doesn't work well with Safari (it opens
     * extra windows).
     */
    outputProlog(content.document);
    content.document.writeln('<center>');
    content.document.writeln('<table width="80%"><tbody><tr><td>');
    content.document.writeln('<p><span style="font-size: 1.5em; font-family: arial; color: #0000ff">Paper Abstract:<br>');
    content.document.write('<a title="View paper in '+thepaper.source[0].format+'" ');
    content.document.write('onclick="top.setFocusSelect(); return true;" ');
    content.document.writeln('href="'+absLink(thepaper.source[0].link,thepaper.docroot)+'" target="'+thetarget+'">'+thepaper.title+'</a></span><br>');
    content.document.writeln(thepaper.authorlist+'.<br></p>');
    content.document.writeln('<p style="text-align:justify; font-size:larger;">');
    outputReference(content.document,thepaper);
    content.document.writeln('</p>');
    
    if (thepaper.abstext) {
	content.document.write('<div style="text-align:justify; font-size:larger">');
	content.document.writeln('<p><em>'+thepaper.abstext+'</em></P></div>');
    } else {
	content.document.writeln('<br>');
    }
    
    outputSourceList(content.document,thepaper,null,true);

    // Output Special Copyright (if necessary)
    if (thepaper.copyright)
	content.document.writeln('<span style="font-size:smaller;">'+thepaper.copyright+'</span><br>');

    content.document.writeln('</td></tr></tbody></table></center>');
    outputEpilog(content.document);
}

/*
 * Document start/finish
 */
function outputProlog(handle,unloadhandle) {
    if (!handle)
	return;
    handle.writeln('<!DOCTYPE html PUBLIC "-//w3c//dtd html 4.0 transitional//en">');
    handle.writeln('<html><head>');
    handle.writeln('<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">');
    handle.write('</head><body style="background-color: rgb(255, 255, 255);"');
    handle.writeln((unloadhandle?' onunload="'+unloadhandle+'"':'')+'>')
}

function outputEpilog(handle) {
    if (!handle)
	return;
    handle.writeln('</body></html>');
}

/*
 * Section entry/exit
 */
function outputSectionProlog(handle,cursec) {
    if (!handle)
	return;
    outputAnchor(handle,cursec.id);
    handle.writeln('<table width=100% style="margin:0;"><tbody><tr><td><h2>'+cursec.heading+'</h2><ul>');
}

function outputSectionEpilog(handle,cursec) {
    if (!handle)
	return;
    handle.writeln('</ul></td></tr></tbody></table>');
}

/*
 * Entry Formatting
 */
function outputEntry(handle, paper) {
    if (!handle)
	return;
    // If cookie tracking, open papers in same window otherwise new one
    var thetarget=((visitordata.$usecookies)&&(!EditMode))?"_top":"_blank";
    
    // Generate unique tag
    var tag = UniqueTag("xref-"+paper.getID());
	
    // Bullet and anchor for this paper.
    handle.write('<li>');
    outputAnchor(handle,tag);
    
    if (EditMode) {
	handle.write('<span style="font-size:larger;"><form style="margin:0;"><table border=1 width=95% ><tbody><tr>');
	handle.write('<td width=100%><b>XREF ID: '+(paper.id?'"'+paper.id+'"':'(unspecified)')+'</b></td>');
	handle.write('<td style="white-space:nowrap;">');
	if (!(paper.flag&EditState.EDITING)) {
	    handle.write('<input type="button" value="Edit Paper" onclick="parent.editEntry(\''+paper.getID()+'\'); return false;">');
	    handle.write('<input type="button" value="Delete Paper" onclick="parent.deleteEntry(\''+paper.getID()+'\'); return false;">');
	} else {
	    handle.write('<span style="font-size:1.5em; background-color:#ff0000;">');
	    if (paper.flag&EditState.TEMPORARY)
		handle.write('Uncommitted Entry');
	    else
		handle.write('Editing In Progress');
	    if (tag != "xref-"+paper.getID())
		handle.write(' (duplicate)');
	    handle.write('</b></span>');	
	}
	handle.write('</td></tr></tbody></table></form></span>'); 
    }		     
    // Put in title and author list.  Title links to first source
    if (paper.bibtype)
	handle.write('<b>'+paper.bibtype+':</b> ');
    handle.write('<b><a title="View paper in '+paper.source[0].format+'" ');    
    handle.write('onclick="top.setFocusSelect(); return true;" ');
    handle.write('href="'+absLink(paper.source[0].link,paper.docroot)+'" target="'+thetarget+'"');
    handle.write('>'+paper.title+'</a></b>, ');
    handle.writeln(paper.authorlist+'. ');
    
    // Output Reference
    outputReference(handle,paper);
    handle.write('<br>');

    // Output Special Copyright (if necessary)
    if (paper.copyright)
	handle.writeln('<span style="font-size:smaller;">'+paper.copyright+'</span><br>');
    
    // output list of paper sources and links. 
    outputSourceList(handle,paper,tag,false);
    handle.writeln('</li>');
}

function outputReference(handle,paper) {
    if (!handle)
	return;
    
    // Output Reference
    handle.write('Appears in <i>'+paper.publication+'</i>, ');
    if (paper.specifics)
	handle.write(paper.specifics+', ');
    if (paper.month) {
	handle.write(months[paper.month-1]);
	if (paper.month2) {
	    if (paper.month2-paper.month1>1)
		handle.write(' - ');
	    else
		handle.write('/');
	    handle.write(months[paper.month2-1]+' ');
	} else if (paper.day)
	    handle.write(' '+paper.day+', ');
	else
	    handle.write(' ');
    }
    handle.writeln(paper.year+'<br>');
}

function outputSourceList(handle,paper,tag,absflag) {
    if (!handle)
	return;
    var base=content.location.pathname;
    var parms=getSchemaIndex(content.location.search);
    var basetotal=base+content.location.search;

    // If cookie tracking && not editing, open papers in same window
    // otherwise new one 
    var thetarget=((visitordata.$usecookies)&&(!EditMode))?"_top":"_blank";

    // Output links
    handle.writeln('[ ');
    if (paper.abstext && !absflag) {
	handle.write('<a title="View Abstract for \''+paper.title+'\'" ');
	handle.write('onclick="top.setFocusSelect(); return true;" ');
	handle.write('href="'+base+schemaString(parms.index,parms.sortdir,
						paper.getID())+'">');
	handle.write('Abstract</a>');
    }
    
    if (paper.source.length) {
	if (paper.abstext && !absflag)
	    handle.write(', ');
	if (!paper.sourcetype)
	    handle.write('Paper: ');
	else 
	    handle.write(paper.sourcetype+": ");
	for (var i=0; i<paper.source.length; i++) {
	    if (i>0)
		handle.write(', ');
	    handle.write('<a title="View paper in '+paper.source[i].format+'" ');
	    handle.write('onclick="top.setFocusSelect(); return true;" ');
	    handle.write('href="'+absLink(paper.source[i].link,paper.docroot)+'" target="'+thetarget+'"');
	    handle.write('>'+ paper.source[i].format+'</a>');
	    if (paper.source[i].size)
		handle.write('('+paper.source[i].size+')');
	}
	handle.writeln(' ]<br>');
    }
    if (paper.xref.length) {
	handle.write('[ <b>See Also: </b>');
	for (var i=0; i<paper.xref.length; i++) {
	    var xrefitem=paper.xref[i];
	    if (i>0)
		handle.write(', ');	
	    
	    handle.write('<a title="Go to '+xrefitem.name+'" ');
	    handle.write('onclick="top.setFocusSelect(); return true;" ');
	    if (xrefitem.isxref) {
		handle.write('style="white-space:nowrap"');
		if (absflag) {
		    // in abstract.  All refs are abstract links
		    handle.write(' href="'+base+schemaString(parms.index,parms.sortdir,
							     xrefitem.link)+'"');
		} else {
		    // In main window.  All refs are navigate links
		    handle.write(' href="'+basetotal+'#xref-'+xrefitem.link+'"');
		}
	    } else {
		handle.write('href="'+absLink(xrefitem.link,paper.docroot)+'" target="'+thetarget+'"');
	    }
	    handle.write('>'+xrefitem.name+'</a>');
	}
	handle.writeln(' ]<br>');
    }
    if (paper.award) {
	handle.writeln('[ <b>Award: <span style="color: #ff0000">'+paper.award+'</span></b> ]<br>');
    }
    handle.writeln('<br>');		    
}

/*
 * Set up the navigation with schema choice.
 */
function makenavigate(handle,onchangeschema,onchangesort,index,sortdir) {
    
    /* Going to do something with titlebar. */
    var titlebase=paperlist.title;
    if (!titlebase)
	titlebase=top.document.title;
    if (titlebase.indexOf(" [") != -1)
	titlebase = titlebase.substring(0,titlebase.indexOf(" ["));
    titlebase+=" ["+schemalist[index].name+"]";
    if (EditMode)
	titlebase+=": EDIT MODE ";
    top.document.title=titlebase;
    content.document.title=titlebase;
    
    var parsestring="";
    var selector=handle.getElementById("selectslot");

    if (selector) {
	parsestring+='<form style="margin:0;" name="docchoice">';
	parsestring+='<select name="schematype" onchange="'+onchangeschema+'">';
	for (var i=0; i<schemalist.length; i++) {
	    parsestring+='<option value="option_'+i+'">'+schemalist[i].name;
	}
	parsestring+='</select><br>';
	parsestring+='<select name="sortorder" onchange="'+onchangesort+'">';
	parsestring+='<option value="newfirst">Reverse Chronological';
	parsestring+='<option value="oldfirst">Forward Chronological';
	parsestring+='</select></form>';
	
	selector.innerHTML=parsestring;
    
	handle.docchoice.schematype.options[index].selected = true;

	if (sortdir==sortOrder.REVERSE) 
	    handle.docchoice.sortorder.options[0].selected=true;
	else
	    handle.docchoice.sortorder.options[1].selected=true;
     }
}

/*
 * Output the about description.
 */
function outputAbout(handle) {
    if (!handle)
	return;
    handle.write('<div style="font-size:larger;">'+topversion+': ')
    handle.write('Copyright 2004 John Kubiatowicz and the University of Berkeley.<br><br>');
    handle.write('This web system generated automatically by PaperWeb. Javascript and Document versions:</div><br>');
    handle.write('<table border=yes cellpadding=4><tbody>')
    handle.write('<tr><td align=right><b>PaperWeb Javascript</b>:</td><td>'+versionid+'</td></tr>');
    handle.write('<tr><td align=right><b>Document Data</b>:</td><td>Project: '+paperlist.project+'<br>XML Version: '+paperlist.version+'</td></tr>');
    handle.write('</tbody></table>');
}


/*
 * Output the menu
 */
function outputNavigation(handle,menuitems) {
    var menutext="";
    var menuplace=handle.getElementById("menuslot");

    if (EditMode) {
	var color=(undolist.length?"#ff0000":"#00ff00");
	menutext+='<form name="editbuttons" style="margin:0;">';
	menutext+='<table border=1 width=80% style="background-color:'+color+';"><tbody><tr><td width="100%" align="left">';
	menutext+='<span style="margin:0; font-family: arial; font-size:medium; white-space:nowrap;"><b>EDIT MODE:</b></td>';
	menutext+='<td style="white-space:nowrap;">';
	if (paperlist.update)
	    menutext+='<input type="button" value="Upload XML" onclick="parent.CGISave(); return false;">';
	menutext+='<input type="button" value="Save XML" onclick="parent.manualXMLSave(); return false;">';
	menutext+='<input type="button" value="Add Entry" onclick="parent.editEntry(); return false;">';
	if (undolist.length) {
	    menutext+='<input type="button" value="Undo '+undolist[undolist.length-1].op+'" onclick="parent.editUndo(); return false;">';
	}
	menutext+='</span></td></tr></tbody></table></form>';
    } else {
	menutext="<b><span style='font-family: arial; font-size:medium;'>Menu Items:";
	menutext+="</span></b><br>";
    }
    
    if (menuplace) {
	// Always use the search path from the top (indicates schema)
	var myargv=getSchemaIndex(content.location.search);
	var basetotal=content.location.pathname+
	    schemaString(myargv.index,myargv.sortdir);
	for (var i=0; i<menuitems.length; i++) {
	    if (i>0)
		menutext+=" | ";
	    menutext+='<a style="white-space:nowrap" href="'+basetotal+'#'+menuitems[i].id+'"';
	    menutext+=' onclick="top.setFocusSelect(\'#'+menuitems[i].id+'\'); return true;"';
	    menutext+=' onmouseover="top.status=\'Select:'+menuitems[i].menu+'\'; return true;" ';
	    menutext+=' onmouseout="top.status=\'\'; return true;" ';
	    menutext+=' target="content">';
	    menutext+=menuitems[i].menu+'</a>';
	}
	menutext+='</b>';
	menuplace.innerHTML=menutext;
    }
}

/*
 * Output a navigation anchor.  We have to be a bit tricky here,
 * because IE has a weird bug that prevents anchor focus under many
 * circumstances).
 *
 * I believe that this is related to discussion at:
 * 	http://www.jimthatcher.com/skipnav.htm#simple
 */
function outputAnchor(handle,name) {
    if (!handle)
	return;
    handle.write('<a id="'+name+'" href="#'+name+'" target="_top"></a>');
}
    
/*
 * Scroll document in content frame to some position.
 *
 * If dest is a number, scroll the content window to that position.
 * Otherwise, if dest is non-null, use it as a hash and scroll to that
 * position in the content window.
 */
function contentScrollTo(dest) {
    if (!dest || (dest=="null") || (dest=="undefined"))
	return;
    var value = parseInt(dest);
    if (!isNaN(value)) {
	content.scrollTo(0,dest);
	return true;
    } else {
	content.location.hash=dest;
    }
    if (UserResponse) {
	var editid=UserResponse;
	UserResponse=null;
	commitQuery(editid);
    }
}
function laterContentScrollTo(dest) {
    laterContentScrollTo.argument=dest;
    setTimeout("contentScrollTo(laterContentScrollTo.argument)",100);
}

/*
 * Function on Schema or Sort change: redraw.
 */
function schemaChange(absid) {
    /*
     * Because Opera doesn't seem to do well just changing the
     * top-level search, we also get the base and set it as well.
     */
    var newschema=schemaString(getSchemaSelect(),getSortOrder(),absid);
    
    setFocusSelect();

    /*
     * Note that, when not editing, we put the changed schema in the
     * top-level URL so that a reload will hit the same schema.
     */
    if (!EditMode) {
	location=location.pathname+schemaString(getSchemaSelect(),getSortOrder(),absid);
    } else {
	content.location=content.location.pathname+schemaString(getSchemaSelect(),getSortOrder(),absid);
    }
    unsetFocusSelect();
}

function getSchemaSelect() {
    return header.document.docchoice.schematype.selectedIndex;
}

function getSortOrder() {
    switch(header.document.docchoice.sortorder.selectedIndex) {
    case 0:
	return sortOrder.REVERSE;
    case 1:
	return sortOrder.FORWARD;
    }
}

sortOrder.FORWARD = 0;
sortOrder.REVERSE = 1;
function sortOrder(ordering,thelist) {
    // Sort ordering: missing date fields => later than present ones
    function compare(a,b) {
	if (!(comp=(a.year?a.year:10000)-(b.year?b.year:10000)))
	    if (!(comp=(a.month?a.month:13)-(b.month?b.month:13)))
		comp=(a.day?a.day:32)-(b.day?b.day:32);
	return ((ordering==sortOrder.FORWARD)?comp:-comp);
    }
    thelist.sort(compare);
}

function reflowPaperlist(thelist) {
    var lowyear = 10000;
    var highyear = 0;
    var extracat=[]; 

    sortOrder(sortOrder.FORWARD,thelist);

    // Update associative array: paper.id=>paper
    thelist.assoc=[];
    thelist.editing=[];

    for (var i=0; i<thelist.length; i++) {
	var mypaper=thelist[i];
	if (mypaper.flag & EditState.INVISIBLE)
	    continue;
	// Track the editing papers (to make sure they are visible)
	if (mypaper.flag & EditState.EDITING)
	    thelist.editing.push(mypaper.getID());

	// Keep low-high statistics
	var curyear=parseInt(mypaper.year); //get year as number
	if (curyear < lowyear)
	    lowyear = curyear;
	if (curyear > highyear)
	    highyear = curyear;

	thelist.assoc[mypaper.id]=mypaper;
	thelist.assoc[mypaper.uniqueid]=mypaper;

	// Keep one sourcetype/bibtype combo.
	for (var j=0; j<mypaper.category.length; j++) {
	    var mycat=catgroups.special[mypaper.category[j]];
	    if (mycat) {
		mypaper.sourcetype=mycat.sourcetype;
		mypaper.bibtype=mycat.bibtype;
	    }
	}
    }

    thelist.lowyear = lowyear;
    thelist.highyear = highyear;
}    

/*
 * Unique-ification of cross-ref tags
 */
function UniqueTag(name) {
    if (!UniqueTag.assoc) {
	UniqueTag.assoc=new Object;
	UniqueTag.count = 0;
    }
    UniqueTag.count++;
    
    // Name left along unless it already exists
    if (UniqueTag.assoc[name])
	name=name+"_"+UniqueTag.count;
    else
	UniqueTag.assoc[name] = 1;

    return name;
}

function resetUniqueTag() {
    UniqueTag.assoc=new Object;

    /*
     * If item doesn't have ID, will call uniqueTag("xref-").  Make
     * sure that it is present (forcing number append).
     */
    UniqueTag.assoc["xref-"] = 1;

    UniqueTag.count = 0;
}

function UniqueWinName() {
    if (!UniqueTag.wincount)
	UniqueTag.wincount=0;
    return UniqueTag.wincount++;
}

function UniquePaperId() {
    if (!UniqueTag.papercount)
	UniqueTag.papercount=1;
    return "Paper-"+UniqueTag.papercount++;
}

/*****************************************************************************
 *
 * User State.
 *
 * The following mess is to keep state for the user.
 *
 * We keep 3 pieces of info: sortOrder, schemaSelect, and focusSelect.
 *
 * sortOrder:	 {sortOrder.FORWARD | sortOrder.REVERSE}
 * schemaSelect: 0 ... number of schemas - 1
 * focusSelect:	 string | integer
 *
 * focusSelect is either a symbolic reference to an element or an
 * integer that represents a YOffset of the document in the content
 * window.
 */

/*
 * Try to keep persistant state for user
 */
function getBrowsingInfo() {
    // Cookies expire in 10 minute
    visitordata = new Cookie(document, "PaperWeb", 10);
    
    visitordata.$usecookies=true;

    // Now.  Attempt to get browsing cookie
    if (!visitordata.load() || (visitordata.editmode!=(EditMode?1:0))) {
	// Check to see if we can even use cookies
	visitordata.focusSelect=[];
	visitordata.editmode=(EditMode?1:0);

	visitordata.store();     // Try it
	if (!visitordata.load()) 
	    visitordata.$usecookies = false ;    // Cookies must be disabled
    } else {
	// Validate returned data
	if (!visitordata.focusSelect || (typeof visitordata.focusSelect)!="object")
	    visitordata.focusSelect=[];
    }
}	

/*
 *
 */
function unloadFrame() {
    if (EditMode) {
	// Try to kill off edit windows! 
	for (editid in editlist) {
	    editlist[editid].win.close();
	    delete editlist[editid];
	}
	// Kill off save window if it exists.
	if (savewin) {
	    savewin.close();
	    savewin=null;
	}
    }
}

/*
 * This function called during navigation.
 *
 * This could fail because we are out of frame.  Ignore.
 */
function setFocusSelect(position) {
    if (visitordata == null)
	return;
    try {
	// First try to get scroll offset...
	var myargs=getSchemaIndex(content.location.search);
	var myindex=((myargs.index)*2+myargs.sortdir);
	if (!myargs.abs && !position) {
	    // Not in abstract/explicit position
	    var scroll = getContentScroll();
	    if (scroll >= 0)
		visitordata.focusSelect[myindex] = scroll;
	} else {
	    visitordata.focusSelect[myindex] = position;
	}

	// Do not save position on reload if cookie expired
	if ((new Date()).getTime() < visitordata._expire.getTime()) {
	    visitordata.store();	/* Save state */
	}
    } catch(e) {; }
}

function unsetFocusSelect() {
    if (visitordata == null)
	return;
    var myargs=getSchemaIndex(content.location.search);
    var myindex=((myargs.index)*2+myargs.sortdir);
    visitordata.focusSelect[myindex]=null;
    visitordata.store();
}    

function getFocusSelect() {
    if (visitordata == null)
	return;
    var myargs=getSchemaIndex(content.location.search);
    var myindex=((myargs.index)*2+myargs.sortdir);
    
    var retvalue=visitordata.focusSelect[myindex];
    return retvalue;
}    
	
function getContentScroll() {
    var pos = -1;
    
    if (content.pageYOffset)
	pos = content.pageYOffset;
    else if (content.document.documentElement &&
	     content.document.documentElement.scrollTop)
	pos = content.document.documentElement.scrollTop;
    else if (content.document.body)
	pos = content.document.body.scrollTop;

    return pos;
}

/*
 * This following routines handle cookies and is from the O'Reily
 * book.
 *
 * Arguments:
 *   document: 	The Docuemnt object for which the cookie is stored. Required
 *   name:	A string that specifies the name of the cookie.
 *   minutes:	An optional number that number of hours to expiration
 *   path:	[optional] string cookie path attribute
 *   domain:	[optional] string cookie domain attribute
 *   secure:	[optional] boolean; true=>secure cookie.
 */
function Cookie(document, name, minutes, path, domain, secure) {
    //All the predefined properties of this object begins with '$'
    //to distinguish them from other properties to be stored in cookie.
     
    this.$document=document;
    this.$name=name;
    if (minutes)
	this.$minutes=minutes;
    else
	this.$minutes=null;
    this.$path=(path)?path:null;
    this.$domain=(domain)?domain:null;
    this.$secure=(secure)?secure:null;
}

// Store cookie
Cookie.prototype.store = function() {
    if (this.$minutes) {
	// Have expiration.  Compute time
	this.$expiration = new Date((new Date()).getTime() + (this.$minutes)*60000);
	this._expire=this.$expiration; /* Store in cookie as well */
    }

    // Loop through and construct the value of cookie.
    var cookieval = "";
    for (var prop in this) {
	// Ignore properties with names that begin with '$' and methods
	if ((prop.charAt(0) == '$') || ((typeof this[prop])=='function'))
	    continue;
	if (cookieval != "")
	    cookieval += '&';
	if ((prop != "_expire") && (typeof this[prop] == "object")) {
	    var temparray=escapeArray(this[prop]);
	    var stringrepresent=temparray.join(":");
	    cookieval += prop + ":@"+escape(stringrepresent);
	} else {
	    cookieval += prop + ":" + escape(this[prop]);
	}
    }	
    
    // Now, put together complete cookie string
    var cookie = this.$name + "=" + cookieval;
    if (this.$expiration)
        cookie += '; expires=' + this.$expiration.toUTCString();
    
    if (this.$path) cookie += '; path=' + this.$path;
    if (this.$domain) cookie += '; domain=' + this.$domain;
    if (this.secure) cookie += '; secure';
    this.$document.cookie = cookie;
}

// Load Cookie
Cookie.prototype.load = function() {
    // First, get a list of all cookies that pertain to this document
    // Do this by reading the Document.cookie property
    var allcookies = this.$document.cookie;
    if (allcookies == "")
    return false;

    // Now extract just the named cookie from that list
    var start = allcookies.indexOf(this.$name + '=');
    if (start == -1)
    return false;  // Cookie not defined
    start += this.$name.length+1;   // Skip name and equals sign
    var end = allcookies.indexOf(';', start);
    if (end == -1) end = allcookies.length;
    var cookieval = allcookies.substring(start, end);
    
    // Now, break into individual name/value pairs
    var a = cookieval.split('&');
    for (var i=0; i<a.length; i++) {
        var keypair = a[i].split(':');
	var keyvalue = unescape(keypair[1]);
	if (keyvalue.charAt(0) == '@') {
	    // have an array
	    keyvalue=keyvalue.slice(1,keyvalue.length);
	    if (keyvalue=="")
		keyvalue=[];
	    else
		keyvalue=unescapeArray(keyvalue.split(":"));
	}
	this[keypair[0]] = keyvalue;
    }
    
    // Finally, check expiration.
    this._expire = new Date(this._expire);
    now = new Date();
    if (now.getTime() > this._expire.getTime())
        return false;
    else 
        return true;
}

// This function is the remove() method
Cookie.prototype.remove = function() {
    var cookie;
    cookie = this.$name + '=';
    if (this.$path) cookie += '; path=' + this.$path;
    if (this.$domain) cookie += '; domain=' + this.$domain;
    cookie += '; expires=Fri, 02-Jan-1970 00:00:00 GMT';

    this.$document.cookie = cookie;
}

/****************************************************************
 * Output XML formatting					*
 ****************************************************************/

/*
 * Check before saving.
 */
function checksave(uploading) {
    var flag=false;
    for (editid in editlist) {
	flag=true;
    }
    if (flag) {
	alert('You have one or more edit windows still open!\nPlease click "Save and Exit" or "Exit without Saving"\nto close these windows before uploading or saving XML.');
	return false;
    }	
    if (!undolist.length) {
	if (uploading) {
	    alert("No Changes To Upload!");
	    return false;
	} else {
	    alert("Warning: No Changes Made!\nXML will be identical to that from website.");
	}
    }
    return true;
}

/*
 * Manually save the XML to a file.
 */
function manualXMLSave() {
    if (!checksave(false))
	return;
    
    var XMLData=generateXML();
    if (savewin)
	savewin.close();
    savewin=window.open("","XMLwindow","resizable=yes,menubar=yes,scrollbars=yes,width=400,height=200",true);
    savewin.document.open();
    savewin.document.write(XMLData);
    savewin.document.close();
    savewin.document.title='XML Output Window: Click "Save File" and edit Result';
    savewin.focus();
}

function CGISave() {
    if (!checksave(true))
	return;
    
    if (savewin)
	savewin.close();
    savewin=window.open("","xmlsave","resizable=yes,width=600,height=400,status=yes",true);
    uploadXML();
}
    
/*
 * Upload the XML to the server.
 */
function uploadXML(message) {
    var back_color="#39E5FF";  // Cyan
    var error_color="#FF0000"; // Red
    var XMLData=generateXML();

    savewin.document.open();
    outputProlog(savewin.document);

    if (paperlist.updateid)
	var databaseid=paperlist.updateid;
    else
	var databaseid=paperlist.project;

    savewin.document.write('<form onsubmit="opener.checkXMLSubmit();" name="submitxml" method="post" action="'+paperlist.update+'">\n');
    savewin.document.write('<center><br><br><div style="font-size:1.5em;"><b>Upload Window for Changes to Database</b></div><p>\n');
    savewin.document.write('<table cell-padding=4 style="font-size:larger; font-weight:bold;"><tbody>');
    savewin.document.write('<tr><td align=right>Upload Server:</td><td align=left>'+ExtractServer(paperlist.update)+'</td></tr>\n');
    if (paperlist.updateid)
	savewin.document.write('<tr><td align=right>Database ID:</td><td align=left>'+paperlist.updateid+'</td></tr>\n');
    else
	savewin.document.write('<tr><td align=right>Research Project:</td><td align=left>'+paperlist.project+'</td></tr>\n');
    savewin.document.write('<tr><td>&nbsp;<br></td><td>&nbsp;</td></tr>\n');
    savewin.document.write('<tr><td colspan=2 align=center><b>Enter Credentials:</b></td></tr>\n');
    savewin.document.write('<tr><td align=right>Username:</td><td align=left><input type="text" size=20 name="username"></td></tr>\n');
    savewin.document.write('<tr><td align=right>Password:</td><td align=left><input type="password" size=20 name="password"></td></tr>\n');
    savewin.document.write('<tr><td colspan=2 align=center><input type="submit" value="Submit Changes"></td></tr>\n');
    savewin.document.write('<input type="hidden" name="returnstatus" value="'+BuildPath(ExtractDirectory(top.document.URL),CGIReturnScript)+'">\n');
    savewin.document.write('<input type="hidden" name="databaseid" value="'+databaseid+'">\n');
    savewin.document.write('<input type="hidden" name="XMLDATA" value="">\n');
    savewin.document.write('</tbody></table></center>\n');
    savewin.document.write('</form>\n');

    if (message) {
	savewin.document.write('<p><table border=4 width=100% style="background:'+error_color+'; font-size: larger;">');
	savewin.document.write('<tbody><tr><td><div style="font-size:1.5em;">'+message+'</div></td></tr></tbody></table>');
    }
	    
    outputEpilog(savewin.document);
    savewin.document.close();

    savewin.document.title="XML Save Dialog";
    savewin.document.body.style.background=back_color;
}	

function checkXMLSubmit() {
    if (!savewin)
	return false;  // Should never happen

    // Make sure to get latest XML data (in case they left the window up)
    savewin.document.submitxml.XMLDATA.value=generateXML();
    
    return true;
}

/*
 * This function called by the CGI return script to get return values
 * from a CGI submission.  Note that we do not get called if there was
 * a server misconfiguration.
 */
function CGIReturn(thewindow,args) {
    if (!savewin || thewindow != savewin)
	return;

    if (args['result']=="success") {
	undolist=[];
	setTimeout("closeSaveWin()",100);
    } else {
	// Assume failure and give user chance to correct.
	uploadXML('Upload Status: '+args['result']+'<br>Reason: '+args['message']);
    }
}

function closeSaveWin() {
    if (savewin) {
	savewin.close();
	savewin=null;
	top.location.reload();
    }
}
	    
/*
 * This function generates an XML representation of the current
 * database.
 *
 * Note: This will eventually take a "project" argument when we allow
 * multiple project databases to be loaded.
 */
function generateXML() {
    var outputValue="";

    outputValue ='<?xml version="1.0" encoding="ISO-8859-1" ?>\n\n';
    outputValue += '<paperdatabase>\n';
    if (paperlist.title)
	outputValue += '<title>'+paperlist.title+'</title>/n';
    if (paperlist.version)
	outputValue += '<version>'+paperlist.version+'</version>\n';
    if (paperlist.update) {
	outputValue += '<update';
	if (paperlist.updateid)
	    outputValue += ' id="'+paperlist.updateid+'"';
	outputValue += '>'+paperlist.update+'</update>\n';
    }
    outputValue += '<project>'+paperlist.project+'</project>\n';
    outputValue += '<docroot>'+paperlist.docroot+'</docroot>\n\n';
    
    outputValue += '<!-- Here are the Schemas -->\n';
    var count=0;
    for (i=0; i<schemalist.length; i++) {
	if (count++ > 0)
	    outputValue += '\n<!-- Schema '+(count)+' -->\n\n';
	theschema=schemalist[i];
	outputValue += '<schema name="'+theschema.name+'"';;
	if (theschema.special)
	    outputValue += ' special="'+theschema.special+'"';
	outputValue += '>\n';
	for (j=0; j<theschema.sections.length; j++) {
	    thesection=theschema.sections[j];

	    var id=thesection.id;
	    id=id.substring(5);  // Filter out the "sect-"
	    outputValue += '<section id="'+id+'"';

	    if (thesection.heading != thesection.menu)
		outputValue += ' menu="'+thesection.menu+'"';
	    outputValue += '>\n<heading>'+thesection.heading+'</heading>\n';
	    if (thesection.matchexpn)
		outputValue += '<match>'+thesection.matchexpn+'</match>\n';
	    outputValue += '</section>\n';
	}
	outputValue += '</schema>\n';
    }
    
    outputValue += "\n<!-- Here are the Category Groups -->\n";

    for (i=0; i<catgroups.length; i++) {
	thegroup=catgroups[i];
	outputValue += '<catgroup name="'+thegroup.name+'">\n';
	for (j=0; j<thegroup.catdef.length; j++) {
	    mycat=thegroup.catdef[j];
	    outputValue += '<catdef id="'+mycat.id+'"';
	    if (mycat.bibtype)
		outputValue += ' bibtype="'+mycat.bibtype+'"';
	    if (mycat.sourcetype)
		outputValue += ' sourcetype="'+mycat.sourcetype+'"';
	    outputValue += '>'+thegroup.catdef[j].name+'</catdef>\n';
	}
	outputValue += '</catgroup>\n';
    }
	
    outputValue += "\n<!-- Here are the papers -->\n";

    var count=0;
    for (i = 0; i < paperlist.length; i++) {
	if (paperlist[i].flag & EditState.TEMPORARY)
	    continue;  // Don't output uncommitted entries
	if (count++ > 0)
	    outputValue +='\n<!-- Paper '+(count)+' -->\n\n';
	var thepaper = paperlist[i];

	if (thepaper.id)
	    outputValue += '<paper id="'+thepaper.id+'">\n';
	else
	    outputValue += '<paper>';

	if (thepaper.project != paperlist.project)
	    outputValue += '<project>'+thepaper.project+'</project>\n';
	if (thepaper.docroot != paperlist.docroot)
	    outputValue += '<docroot>'+thepaper.docroot+'</docroot>\n';
	    
	outputValue += '<title>'+thepaper.title+'</title>\n';

	outputValue += multitag('author',thepaper.author,4,',');

	if (thepaper.publication)
	    outputValue += '<publication>'+thepaper.publication+'</publication>\n';
	if (thepaper.specifics)
	    outputValue += '<specifics>'+thepaper.specifics+'</specifics>\n';
	if (thepaper.year)
	    outputValue += '<year>'+thepaper.year+'</year>\n';
	if (thepaper.month)
	    outputValue += '<month>'+months[thepaper.month-1]+'</month>\n';
	if (thepaper.month2)
	    outputValue += '<month2>'+months[thepaper.month2-1]+'</month2>\n';
	else if (thepaper.day)
	    outputValue += '<day>'+thepaper.day+'</day>\n';

	if (thepaper.award)
	    outputValue += '<award>'+thepaper.award+'</award>\n';

	if (thepaper.abstext)
	    outputValue +='<abstract><![CDATA[\n'+wrapText(thepaper.abstext,80)+'\n]]>\n</abstract>\n\n';
	if (thepaper.copyright)
	    outputValue +='<copyright><![CDATA[\n'+wrapText(thepaper.copyright,80)+'\n]]>\n</copyright>\n\n';
	    
	if (thepaper.category) 
	    outputValue += multitag('category',thepaper.category,6,',');
	
	if (thepaper.source.length) {
	    for (var j=0; j<thepaper.source.length; j++) {
		outputValue += '<source format="'+thepaper.source[j].format+'"';
		if (thepaper.source[j].size)
		    outputValue += ' size="'+thepaper.source[j].size+'"';
		outputValue += '>'+webescape(thepaper.source[j].link)+'</source>\n';
	    }
	}
		
	if (thepaper.xref.length) {
	    for (var j=0; j<thepaper.xref.length; j++) {
		var isxref=thepaper.xref[j].isxref;
		var link=thepaper.xref[j].link;
		outputValue += '<xref ';
		if (isxref)
		    outputValue += 'id="'+link;
		else
		    outputValue += 'link="'+webescape(link);
		outputValue+='">'+thepaper.xref[j].name+'</xref>';
	    }
	}
	    
	outputValue += '</paper>\n';
    }
	
		 
    outputValue += '</paperdatabase>\n';

    return outputValue;
}

/*
 * output multiple values at a time, separated by the separator
 */
function multitag(tag,inarray,num,separator) {
    var result = "";

    for (var index = 0; index <inarray.length; index++) {
	if (index % num == 0) {
	    if (index != 0)
		result +='</'+tag+'>\n';
	    result += '<'+tag+'>';
	} else {
	    result += separator;
	}
	result += inarray[index];
    }
    result +='</'+tag+'>\n';
    return result;
}

/****************************************************************
 * Editing Functions            				*
 ****************************************************************/

/*
 * This function allows us to edit (or create) a paper entry.
 *
 * We create a big form and fill it in with the original values (if
 * they exit).
 */
function EditState(ident, window, proto, data) {
    this.ident=ident;	// The paper identifier
    this.win=window;	// The edit window
    this.proto=proto;	// The original structure
    this.data=data;	// A paper structure
    this.viewing=null;	// Live entry (none yet)
    this.flag=EditState.IDLE;  // Not refreshing.
}

/*
 * Flags used during editing (both for EditState and Paper objects).
 */
EditState.IDLE=0;
EditState.EDITING = 1;		// Paper being edited. 
EditState.TEMPORARY=2;		// Paper is not permanent
EditState.INVISIBLE=4;		// Paper should not be displayed
EditState.OUTSIDESCHEMA=8;	// Paper is invisible in current schema.
EditState.REFRESHING=16;	// EditState being refreshed.
    
function editEntry(ident) {
    var original=null;
    var editwin,handle;
    var editid="EditWin_"+UniqueWinName();
    
    if (ident) {
	original=paperlist.assoc[ident];
	original.flag=EditState.EDITING;
	setFocusSelect();
	content.location.reload();
    }

    editwin=window.open("",editid,"resizable=yes,menubar=yes,scrollbars=yes",true);
    
    editlist[editid]=new EditState(ident,editwin,original,null);

    formatEditWin(editid);
}

/*
 * This function discards all changes from an edit window and starts
 * with initial values.
 */
function editDiscard(editid) {
    var editentry=editlist[editid];
    if (!editentry)
	return;

    // Zero out the temporary state.
    editentry.data=null;
    formatEditWin(editid);
}
    
/*
 * This function is called to clean up state when the edit window is unloaded.  It
 * frees up edit state and resets edited papers (if any) to visible.
 */
function exitWin(editid) {
    var editentry=editlist[editid];
    if (!editentry)
	return;

    // If we were triggered during refresh, ignore.
    if (editentry.flag&EditState.REFRESHING)
	return;

    var proto=editentry.proto;
    var viewing=editentry.viewing;

    // Make sure paper stays visible
    if (proto)
	proto.flag=EditState.IDLE;

    // Make sure that papers being "tried out" go away.
    if (viewing)
	deleteObjectFromList(viewing,paperlist);

    reflowPaperlist(paperlist);		    
    if (proto)
	setFocusSelect("#xref-"+proto.getID());
    content.location.reload();
    top.focus();

    delete editlist[editid];  // Detach state.

}

function editView(editid) {
    viewSetup(editid,"view");
}

function editCommit(editid) {
    viewSetup(editid,"commit");
}

/*
 * This function is called to insert changes into database
 * provisionally: steps:
 *
 * 1) validate current contents of form
 * 2) if "viewing" object doesn't exit, then:
 *	a) make new viewing object, state "TEMPORARY|EDITING", insert
 * 	   into document.
 *	b) set prototype object (if it exists) to "INVISIBLE"
 * 3) copy edit state into viewing object
 * 4) reflow document and set focus to new entry.
 *
 * Note that error result is put into the verifyResult property.
 *
 * Input:	editid		Edit state block
 *		operation	[optional]String describing operation
 */
function viewSetup(editid,operation) {
    var editentry=editlist[editid];
    if (!editentry)
	return;
    var newpaper=editentry.data;
    var editwin=editentry.win;

    // First, verify entries in theform
    var result=verifyForm(editid);
    editentry.verifyResult=result;
    
    // Identification of paper
    if (editentry.ident)
	var paperid="changes to Paper "+editentry.ident;
    else
	var paperid="new paper entry";
    
    // If fails to pass, do nothing.
    if (result=="error") {
	message="Error: cannot "+operation+" "+paperid;
	message+="\nbecause of errors (highlighted in red).\n";
	message+="Please correct these and try again.";
 	editwin.alert(message);
	return;
    } else if (result=="warning" && operation=="commit") {
	// Note that we only ask about warnings when we commit.
	message = "Warning: "+paperid+" has warnings\ (highlighted in yellow on edit page).\n";
	message += "Do you still want to commit?";
	if (!editwin.confirm(message))
	    return;
    } 

    // Ok.  Form can be inserted into document.
    if (!editentry.viewing) {
	// Create new viewing paper as copy of edit state
	editentry.viewing=newpaper.clone();
	editentry.viewing.flag= (EditState.TEMPORARY | EditState.EDITING);
	paperlist.push(editentry.viewing);
	
	if (editentry.proto)
	    // Make old object invisible
	    editentry.proto.flag |= EditState.INVISIBLE;
    } else {
	// Copy state into document
	newpaper.clone(editentry.viewing);
    }
    editentry.viewing.authorlist=normalizeAuthorlist(newpaper.author);
	
    // Set up for later user response.  Triggered during execution
    // of laterContentScrollTo().
    if (operation=="commit")
	UserResponse=editid;
	
    reflowPaperlist(paperlist);
    var myargv=getSchemaIndex(content.location.search);
    var myhash="#xref-"+editentry.viewing.getID();
    
    setFocusSelect(myhash);
    if (myargv.abs)
	content.location=
	    content.location.pathname+
	    schemaString(myargv.index,myargv.sortdir)+myhash;
    else
	content.location.reload();
    top.focus();
}

/*
 * This function queries the user about final commits.  It is
 * triggered after changes are shown to the user in the main window.
 */
function commitQuery(editid) {
    var editentry=editlist[editid];
    if (!editentry)
	return;
    var editwin=editentry.win;
    var newpaper=editentry.data;
    var proto=editentry.proto;
    var viewing=editentry.viewing;
    
    var message;
    var paperid;
    
    if (!viewing) 
	return;

    // Moving forward with commit!
    message="Check final formatting. \nAre you sure you want to finish editing this paper?";
    if (viewing.flag & EditState.OUTSIDESCHEMA)
	message+="\n\nNOTE: Paper is outside of current\nschema and will not appear!";
    if (!header.confirm(message))
	return;

    viewing.flag = EditState.IDLE;
    if (proto) {
	proto.flag = EditState.IDLE;
	undolist.push({op:"edit",value:proto,newvalue:viewing});
	deleteObjectFromList(proto,paperlist);
    } else
	undolist.push({op:"add",newvalue:viewing});

    reflowPaperlist(paperlist);		    
    setFocusSelect("#xref-"+viewing.getID());
    content.location.reload();
    top.focus(); // Make sure that editwindow is visible.
    
    delete editlist[editid];
    editwin.close();
}
    
    
/*
 * This function formats an editwindow to edit a paper document.  The
 * assumption is that the current paper entry can be found on the
 * editlist by using the editid.  Although a bit complex, this allows
 * maximum multithreading on editing (and makes tracking state easier).
 */
function formatEditWin(editid) {
    var head_color="#ff0000";  // Red
    var odd_color="#39E5FF";  // Cyan
    var even_color="#ffffff";  // White
	
    var editentry=editlist[editid];
    
    var editwin=editentry.win;
    if (!editwin)
	return;
    
    var oldpaperid=editentry.ident;
    var oldpaper=editentry.proto;
    var newpaper=editentry.data;
    editChange.mess=[];  // For holding "normal" messages
    editChange.extra=[]; // For buttons in message area
    
    if (!newpaper) {
	if (oldpaper) {
	    // Editing something.  Copyit
	    var newpaper=oldpaper.clone();
	} else {
	    var newpaper=new Paper();
	    newpaper.project=paperlist.project;
	    newpaper.docroot=paperlist.docroot;
	    newpaper.source.push(new DocSource("","PDF",""));
	}
	/* Save for future ref */
	editentry.data=newpaper;
    }
    
    if (oldpaperid)
	var editheader='Edit Paper ID "'+oldpaperid+'"';
    else
	var editheader='Add New Paper';

    editentry.flag=EditState.REFRESHING;
    handle=editwin.document;
    handle.open();
    
    outputProlog(handle,"if (opener.exitWin) opener.exitWin('"+editid+"');");

    // Identification and major buttons at top.
    handle.write('<form name="datainput">\n');
    handle.write('<table border=1 cellpadding=2 width=100%><tbody>\n');
    handle.write('<tr style="background-color:'+head_color+';">');
    handle.write('<td colspan=3 align="center" style="white-space:nowrap;" width=80%>');
    handle.write('<br><h2>Edit Window: '+editheader+'</h2><br></td>');
    handle.write('<td align="center">');
    handle.write('<input type="button" value="Discard Changes" onclick="opener.editDiscard(\''+editid+'\'); return false;"><br>');
    handle.write('<input type="button" value="View Changes" onclick="opener.editView(\''+editid+'\'); return false;"><br>');
    handle.write('<input type="button" value="Save and Exit" onclick="opener.editCommit(\''+editid+'\'); return false;"><br>');
    handle.write('<input type="button" value="Exit Without Saving" onclick="self.close(); return false;">');
    handle.writeln('</td></tr>\n');
         
    // Cross-ref ID
    editTextbox(editid,"id",odd_color,20,
		"Cross-ref ID","[optional]")
    
    // Paper-Name
    editTextarea(editid,"title",even_color,70,2,
		 "Paper Title:","Required");
    
    // Authors
    newpaper.authorlist=newpaper.author.join(', ');
    editTextarea(editid,"authorlist",odd_color,70,4,
		 "Comma-Delimited<br>Author List:","One Author Required",
		 wrapText(newpaper.authorlist,68,', '));
		 
    // Publication
    editTextarea(editid,"publication",even_color,70,2,
		 "Publication <br>(will be italicized):","Required");
    
    // Specifics
    editTextarea(editid,"specifics",odd_color,70,2,
		 "Specifics<br>(Volume,pages, etc.):","[optional]");

    // Date
    editDate(editid,"date",even_color,
	     "Date:","Month range or Day,<br>(not both).<br>Year Required");
    
    // Award
    editTextbox(editid,"award",odd_color,60,"Award:","[optional]");
    
    // Abstract
    editTextarea(editid,"abstext",even_color,70,10,
		 "Abstract:","[optional]<br>simple HTML allowed",
		 wrapText(newpaper.abstext,69));

    // Copyright
    editTextarea(editid,"copyright",odd_color,70,5,
		 "Copyright Message:","[optional]<br>simple HTML allowed",
		 wrapText(newpaper.copyright,69));

    // Paper Source types
    editSource(editid,"source",even_color,
	       "Source Matter","Requires at Least One");

    // Paper XREFs
    editXref(editid,"xref",odd_color,
	     "Cross References","[optional]");
    
    // Edit Categories
    // Initialize user-defined categories (not in cat groups)
    newpaper.othercat=[];
    for (var i=0; i<newpaper.category.length; i++) 
	if (!catgroups.assoc[newpaper.category[i]]) {
	    newpaper.othercat.push(newpaper.category[i]);
	}

    editCategories(editid,"category",even_color,
		   "Subject Categories:","[optional]");
    
    handle.writeln('</tbody></table></form>');
    outputEpilog(handle);
    handle.close();
    handle.title=editheader;
    editentry.flag=EditState.IDLE;

    editwin.focus();
}

/*
 * Generic function that deals with input parameters expressed as text boxes.
 */
function editTextbox(editid,name,bgcolor,size,menu,message,contents) {
    editentry=editlist[editid];
    if (!editentry)
	return;
    var editwin=editentry.win;
    var handle=editwin.document;
    
    var newpaper=editentry.data;
    editChange.mess[name]=message;
    editChange.extra[name]="";
    
    var localcontents=(contents?contents:(newpaper[name]?newpaper[name]:""));
    var safeContents=(localcontents.toString()).replace(/\"/g,"&quot;");

    handle.write('<tr style="background-color:'+bgcolor+';">');
    handle.write('<td align=center valign=center>'+menu+'</td><td colspan=2>\n');
    handle.write('<input type="text" name="'+name+'" size='+size+' value="'+safeContents+'" ');
    handle.write('onchange="opener.editChange(\''+name+'\',\''+editid+'\');">\n');
    handle.write('<td align="center" id="'+name+'-status">'+message+'</td></tr>\n');
}

/*
 * Generic function that deals with input parameters expressed as textareas.
 */
function editTextarea(editid,name,bgcolor,cols,rows,menu,message,contents) {
    if (!(editentry=editlist[editid]))
	return;
    var editwin=editentry.win;
    var handle=editwin.document;
    
    var newpaper=editentry.data;
    editChange.mess[name]=message;
    editChange.extra[name]="";

    var localcontents=(contents?contents:(newpaper[name]?newpaper[name]:""));

    handle.write('<tr style="background-color:'+bgcolor+';">');
    handle.write('<td align=center valign=center>'+menu+'</td><td colspan=2>\n');
    handle.write('<textarea name="'+name+'" cols='+cols+' rows='+rows+' ');
    handle.write('onchange="opener.editChange(\''+name+'\',\''+editid+'\');">\n');
    handle.write((localcontents)+'\n</textarea>\n');
    handle.write('<td align="center" id="'+name+'-status">'+message+'</td></tr>\n');
}

function editDate(editid,name,bgcolor,menu,message) {
    if (!(editentry=editlist[editid]))
	return;
    var editwin=editentry.win;
    var handle=editwin.document;
    
    var newpaper=editentry.data;
    editChange.mess[name]=message;
    editChange.extra[name]="";
    
    handle.write('<tr style="background-color:'+bgcolor+';">');
    handle.write('<td align=center valign=center>'+menu+'</td><td colspan=2>\n');
    handle.write('<table width=90%><tbody><tr><td align=center>Year: <input type="text" name="year" size=5 value="');
    handle.write((newpaper.year?newpaper.year:"")+'" onchange="opener.editChange(\'date\',\''+editid+'\');"></td>\n');
    handle.write('<td align=center>Month(s): <input type="text" name="month" size=10 value="');
    handle.write((newpaper.month?months[newpaper.month-1]:"")+'" onchange="opener.editChange(\'date\',\''+editid+'\');">\n');
    handle.write(' to <input type="text" name="month2" size=10 value="');
    handle.write((newpaper.month2?months[newpaper.month2-1]:"")+'" onchange="opener.editChange(\'date\',\''+editid+'\');"></td>\n');

    handle.write('<td>Day: <input type="text" name="day" size=3 value="');
    handle.write((newpaper.day?newpaper.day:"")+'" onchange="opener.editChange(\'date\',\''+editid+'\');">');
    handle.write('</td></tr></tbody></table></td>\n');
    handle.write('<td align="center" id="'+name+'-status">'+message+'</td></tr>\n');
}

function editSource(editid,name,bgcolor,menu,message) {
    if (!(editentry=editlist[editid]))
	return;
    var editwin=editentry.win;
    var handle=editwin.document;
    
    var newpaper=editentry.data;

    var extramess='<p><input type="button" value="Add Source" onclick="opener.addSource(\''+editid+'\'); return false;">';
    editChange.mess[name]=message;
    editChange.extra[name]=extramess;

    handle.write('<tr style="background-color:'+bgcolor+';">');
    handle.write('<td rowspan='+(newpaper.source.length+1)+' align=center valign=center>'+menu+'</td>\n');
    handle.write('<td colspan=2 align=center><b>Link Root:</b> '+newpaper.docroot+'</td>');
    handle.write('<td id="'+name+'-status" rowspan='+(newpaper.source.length+1)+' align=center valign=center>');
    handle.write(message+extramess+'</td></tr>\n');
    
    for (var i=0; i<newpaper.source.length; i++) {
	var thesource=newpaper.source[i];
	var theformat=thesource.format.toLowerCase();
	var isPDF=(theformat=='pdf');
	var isHTML=(theformat=='html');
	var isps=(theformat=='postscript');
	var ispsgz=(theformat=='compressed postscript');
	var isppt=(theformat=='ppt');
	var isother=(!(isPDF||isHTML||isps||ispsgz||isppt));
	
	var tname="source-type:"+i;
	var oname="source-other:"+i;
	var slink="source-link:"+i;
	var sname="source-name:"+i;
	
	if (!thesource.othertext) {
	    if (isother)
		thesource.othertext=thesource.format;
	    else
		thesource.othertext="";
	}
		
	handle.write('<tr style="background-color:'+bgcolor+';"><td>');
	handle.writeln('<input type="radio" name="'+tname+'" value="PDF" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(isPDF?" checked":"")+'>PDF<br>');
	handle.writeln('<input type="radio" name="'+tname+'" value="Postscript" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(isps?" checked":"")+'>Postscript<br>');
	handle.writeln('<input type="radio" name="'+tname+'" value="Compressed Postscript" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(ispsgz?" checked":"")+'>PS.gz<br>');
	handle.writeln('<input type="radio" name="'+tname+'" value="HTML" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(isHTML?" checked":"")+'>HTML<br>');
	handle.writeln('<input type="radio" name="'+tname+'" value="PPT" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(isppt?" checked":"")+'>PPT<br>');
	handle.writeln('<input type="radio" name="'+tname+'" value="other" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(isother?" checked":"")+'>');
	handle.writeln('<input type="text" name="'+oname+'" size=8 value="'+thesource.othertext+'" onchange="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"></td>');

	handle.writeln('<td><table><tbody><tr><td>Document Link (can be absolute or relative to root)<br>');
	handle.write('<textarea name="'+slink+'" cols=55 rows=1 onchange="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');">\n');
	handle.writeln(thesource.link+'</textarea><br>');
	handle.write('Size [optional]: <input type="text" name="'+sname+'" size=5 value="'+(thesource.size?thesource.size:"")+'" onchange="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"></td></tr>\n');

	if (newpaper.source.length > 1)
	    handle.write('<tr><td align=right><input type="button" value="Delete This Source" onclick="opener.delSource(\''+editid+'\',\''+i+'\');"></td></tr>');
	handle.write('</tbody></table></td></tr>');
    }
}    

function editXref(editid,name,bgcolor,menu,message) {
    if (!(editentry=editlist[editid]))
	return;
    var editwin=editentry.win;
    var handle=editwin.document;
    
    var newpaper=editentry.data;

    var extramess='<p><input type="button" value="Add XREF" onclick="opener.addXref(\''+editid+'\'); return false;">';
    editChange.mess[name]=message;
    editChange.extra[name]=extramess;
    
    var span=newpaper.xref.length;

    if (!newpaper.xref.length) {
	handle.write('<tr style="background-color:'+bgcolor+';">');
	handle.write('<td align=center valign=center>'+menu+'</td>\n');
	handle.write('<td colspan=2><b>No Cross-Reference Entries at this time</b></td>');
	handle.write('<td id="'+name+'-status" align=center valign=center>'+message+extramess+'</td></tr>\n');
	return;
    }
    // Some Xrefs exist!
    for (var i=0; i<newpaper.xref.length; i++) {
	var thexref=newpaper.xref[i];
	var tname="xref-type:"+i;
	var slink="xref-value:"+i;
	var sname="xref-name:"+i;
	var isxref=thexref.isxref;
	
	handle.write('<tr style="background-color:'+bgcolor+';">');
	if (i==0)
	    handle.write('<td rowspan='+(span?span:1)+' align=center valign=center>'+menu+'</td>\n');

	handle.writeln('<td><input type="radio" name="'+tname+'" value="CrossRef" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(isxref?" checked":"")+'>Cross Reference<br>');
	handle.writeln('<input type="radio" name="'+tname+'" value="Link" onclick="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"'+(isxref?"":" checked")+'>External Link</td>');

	handle.writeln('<td><table><tbody><tr><td>Destination (xref label or HTTP link)<br>');
	handle.write('<textarea name="'+slink+'" cols=55 rows=1 onchange="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');">');
	handle.writeln(thexref.link+'</textarea><br>');
	handle.write('Hyperlink Text (required): <input type="text" name="'+sname+'" size=40 value="'+thexref.name+'" onchange="opener.editChange(\''+name+'\',\''+editid+'\',\''+i+'\');"></td></tr>\n');
	handle.write('<tr><td align=right><input type="button" value="Delete This Xref" onclick="opener.delXref(\''+editid+'\',\''+i+'\');"></td></tr>');
	handle.write('</tbody></table></td>');

	if (i==0) {	
	    handle.write('<td id="'+name+'-status" rowspan='+(span?span:1)+' align=center valign=center>');
	    handle.write(message+extramess+'</td></tr>\n');
	}
	handle.write('</tr>\n');	    
    }
}

function editCategories(editid,name,bgcolor,menu,message) {
    if (!(editentry=editlist[editid]))
	return;
    var editwin=editentry.win;
    var handle=editwin.document;
    
    var newpaper=editentry.data;
    editChange.mess[name]=message;
    editChange.extra[name]="";

    // Go through all catgroups and output them.
    for (var i=0; i < catgroups.length; i++) {
	var thegroup=catgroups[i];

	handle.write('<tr style="background-color:'+bgcolor+';">');
	if (i==0)
	    handle.write('<td rowspan='+(catgroups.length+1)+' align=center valign=center>'+menu+'</td>\n');
	
	// Groups of 3
	handle.write('<td colspan=2><b>'+thegroup.name+':</b><br><table width=100% style="margin:0;"><tbody>');
	for (var j=0; j<thegroup.catdef.length+2; j+=3) {
	    handle.write('<tr>');
	    for (var k=j; k<j+3; k++) {
		handle.write('<td width=33% align=left style="white-space:nowrap;">');
		if (k<thegroup.catdef.length) {
		    handle.write('<input type="checkbox" name="'+name+'" value="'+thegroup.catdef[k].id+'" ');
		    handle.write('onclick="opener.editChange(\''+name+'\',\''+editid+'\');" ');
		    handle.write((newpaper.hasCategory(thegroup.catdef[k].id)?" checked":"")+'>');
		    handle.write(thegroup.catdef[k].name);
		} else
		    handle.write('&nbsp;');
	    }
	    handle.write('</td></tr>\n');
	}
	handle.write('</tbody></table></td>\n');
	if (i==0)
	    handle.write('<td rowspan='+(catgroups.length+1)+' align="center" id="'+name+'-status">'+message+'</td></tr>\n');
	handle.write('</tr>\n');
    }

    // Additional categories
    handle.write('<tr><td colspan=2><b>User-defined Properties</b> (no spaces, comma-delimited):<br><input type="text" name="'+name+'extra" size=70 value="');
    handle.write(newpaper.othercat.join(', ')+'" onchange="opener.editChange(\''+name+'\',\''+editid+'\');"></td></tr>\n');

}

/*
 * This function is called on changes to the edit window.  It is
 * called with a class name (such as "abstract" or "source", the
 * identifier of the edit window, and an index (when needed). It
 * proceeds to extract information from the edit form and place it
 * into the Paper() structure.  It finishes by calling validateField()
 * in the non-finalvalid form.
 */
function editChange(name,editid,index) {
    var editentry=editlist[editid];
    if (!editentry)
	return;
    var editwin=editentry.win;
    var newpaper=editentry.data;
    var theform=editwin.document.datainput;


    // Acquire the data from form.
    switch(name) {
    default:
	// Default action is to grab text field
	newpaper[name]=normWS(theform[name].value);
	break;
	
    case 'authorlist':
	// Note that the authorlist field used for display
	newpaper.authorlist=normWS(theform[name].value);

	// Split into individual authors.
	newpaper.author=newpaper.authorlist.split(/\s*,\s*/);
	for (i=0; i<newpaper.author.length; i++) 
	    // If they enter 'and' into author, remove
	    newpaper.author[i]=newpaper.author[i].replace(/^\s*and\s*/,"");
	
	break;
    case 'date':
	newpaper.year=normWS(theform.year.value);
	newpaper.month=normalizeMonth(normWS(theform.month.value));
	newpaper.month2=normalizeMonth(normWS(theform.month2.value));
	newpaper.day=normWS(theform.day.value);
	break;
    case 'source':
	var tname="source-type:"+index;
	var oname="source-other:"+index;
	var slink="source-link:"+index;
	var sname="source-name:"+index;
	
	for (var i=0; i<theform[tname].length; i++)
	    if (theform[tname][i].checked) {
		var format=theform[tname][i].value;
		break;
	    }
	if (format=="other")
	    newpaper.source[index].format=theform[oname].value;
	else
	    newpaper.source[index].format=format;
	newpaper.source[index].link=theform[slink].value;
	newpaper.source[index].size=theform[sname].value;
	break;	    

    case 'xref':
	var tname="xref-type:"+index;
	var slink="xref-value:"+index;
	var sname="xref-name:"+index;
	    
	newpaper.xref[index].isxref=theform[tname][0].checked;
	newpaper.xref[index].link=normWS(theform[slink].value);
	newpaper.xref[index].name=normWS(theform[sname].value);
	break;
	
    case 'category':
	// Loop through and grab categories from check boxes.
	// temp: assoc array for uniqueness.
	var temp={};
	for (var i=0; i<theform.category.length; i++)
	    if (theform.category[i].checked) 
		temp[theform.category[i].value]=true;
	
	// Get other options.
	value=normWS(theform.categoryextra.value);
	if (value)
	    newpaper.othercat=value.split(/\s*,\s*/);
	else
	    newpaper.othercat=[];
	
	for (var i=0; i<newpaper.othercat.length; i++)
	    if (newpaper.othercat[i]) temp[newpaper.othercat[i]]=true;
	
	// Now, get complete set
	newpaper.category = [];
	for (var prop in temp) 
	    newpaper.category.push(prop);
	break;
	
    }

    // Finish by validating the new information.
    validateField(name, editid, false);
}

/*
 * This function validates the form contents for a particular class of
 * fields (like "abstract" or "source").  It has two modes: edit
 * and final validation.  Edit mode (finalvalid==false) is called
 * during edit changes and checks for simple format
 * problems. Validation mode is called during the commit process and
 * checks for more things -- such as complaining about blank entries
 * that are required...
 *
 * Problems are reflected to the status slot.
 */
function validateField(name, editid, finalvalid) {
    var errorcolor="#ff0000";	// Red
    var warningcolor="#ffff33"; // Yellow
    var editentry=editlist[editid];
    if (!editentry)
	return;
    var editwin=editentry.win;
    var newpaper=editentry.data;
    
    var messageslot=editwin.document.getElementById(name+"-status");
    var errormessage="";
    var errorarray=[];
    var warningmessage="";
    var warningarray=[];

    switch(name) {
    case 'title':
    case 'publication':
	if (finalvalid && !newpaper[name])
	    errormessage="Field cannot be blank";
	else
	    errormessage=textChars(newpaper[name]);
	break;	    
    case 'specifics':
    case 'award':
	errormessage=textChars(newpaper[name]);
	break;
    case 'abstract':
    case 'copyright':
	errormessage=textFormatChars(newpaper[name]);
	break
    case 'id':
	var value=newpaper[name];
	if (errormessage=labelChars(value))
	    break;
	if (value && paperlist.assoc[value] &&
	    !((value == editentry.ident) ||
	      (editentry.viewing && value == editentry.viewing.id)))
	    /*
	     * Value is not unique if it exists and is not equal to
	     *  the old edit value or the new (temporary) one.
	     */
	    errormessage="ID is not Unique";
	break;
    case 'authorlist':
	if (errormessage=textChars(newpaper.authorlist))
	    break;
	if (!newpaper.author.length) {
	    errormessage="No authors entered!";
	    break;
	}
	for (i=0; i<newpaper.author.length; i++) 
	    if (!newpaper.author[i]) 
		errormessage="Null Author";
	break;
    case 'date':
	if (newpaper.year) {
	    if (isNaN(parseInt(newpaper.year)))
		errorarray.push("'Year' must be numeric");
	    else if (newpaper.year < 1900)
		errorarray.push("'Year' too small");
	}
	if (newpaper.month==months.ERROR) 
	    errorarray.push("'Month' is invalid");
	if (newpaper.month2==months.ERROR) 
	    errorarray.push("'Month2' is invalid");
	if (newpaper.day) {
	    if (isNaN(parseInt(newpaper.day)))
		errorarray.push("'Day' must be numeric");
	    else if (newpaper.day < 1 || newpaper.day > 31)
		errorarray.push("'Day' out of range");
	    else if (newpaper.month && (newpaper.day > months.monthsize[newpaper.month-1]))
		errorarray.push("'Day' out of range for "+months[newpaper.month-1]);
	}
	if (finalvalid) {
	    if (!newpaper.year)
		errorarray.push("Must have 'Year'");
	    if (newpaper.day && !newpaper.month)
		errorarray.push("Cannot have 'Day' without 'Month'");
	    if (newpaper.month2) {
		if (!newpaper.month) 
		    errorarray.push("Cannot have 'Month2' without 'Month'");
		else if (newpaper.month && (newpaper.month >= newpaper.month2))
		    errorarray.push("'Month2' must follow 'Month'");
		else if (newpaper.day)
		    errorarray.push("Cannot have 'Month2' and 'Day'");
	    }
	}
	errormessage=errorarray.join(';<br>');
	break;
    case 'source':
	// If not validating, don't complain about blanks.
	for (var i=0; i<newpaper.source.length; i++) {
	    var sourceid='<span style="white-space:nowrap;">(Source '+(i+1)+')</span>';
	    if (finalvalid && !newpaper.source[i].format) 
		errorarray.push("Blank Format Name "+sourceid);
	    else {
		errormessage=textDisplayLink(newpaper.source[i].format);
		if (errormessage)
		    errorarray.push(errormessage+", in Format Name "+sourceid);
	    }
		
	    if (finalvalid && !(newpaper.source[i].link))
		errorarray.push("Blank Destination "+sourceid);
	    else {
		errormessage=hyperlinkChars(newpaper.source[i].link);
		if (errormessage)
		    errorarray.push(errormessage+" "+sourceid);
	    }

	    if (newpaper.source[i].size) {
		errormessage=textDisplayLink(newpaper.source[i].size);
		if (errormessage)
		    errorarray.push(errormessage+", in Size "+sourceid);
	    }
	}
	errormessage=errorarray.join(';<br>');
	break;
	    
    case 'xref':
	// If not validating don't complain about blanks.
	for (var i=0; i<newpaper.xref.length; i++) {
	    var xrefid='<span style="white-space:nowrap;">(Xref '+(i+1)+')</span>';
	    if (finalvalid && !(newpaper.xref[i].link))
		errorarray.push("Blank Destination "+xrefid);
	    else if (newpaper.xref[i].link) {
		if (newpaper.xref[i].isxref) {
		    errormessage=labelChars(newpaper.xref[i].link);
		    if (errormessage)
			errorarray.push(errormessage+" "+xrefid);
		    else if (!paperlist.assoc[newpaper.xref[i].link])
			warningarray.push("No document currently with label '"+newpaper.xref[i].link+"'");
		} else {
		    errormessage=hyperlinkChars(newpaper.xref[i].link);
		    if (errormessage)
			errorarray.push(errormessage+" "+xrefid);
		}
		
	    }
	    if (finalvalid && !(newpaper.xref[i].name))
		errorarray.push("Blank Link Name "+xrefid)
	    else if (newpaper.xref[i].name) {	
		errormessage=textDisplayLink(newpaper.xref[i].name);
		if (errormessage)
		    errorarray.push(errormessage+"  "+xrefid);
	    }
	}
	errormessage=errorarray.join(';<br>');
	warningmessage=warningarray.join(';<br>');
	break;
    case 'category':
	// Only need to check the user-defined categories
	for (var i=0; i<newpaper.othercat.length; i++) 
	    if (!newpaper.othercat[i]) {
		errorarray.push("Defined Null Property");
		break;
	    }
	for (var i=0; i<newpaper.othercat.length; i++) {
	    errormessage=labelChars(newpaper.othercat[i]);
	    if (errormessage) {
		errorarray.push(errormessage);
		break;
	    }
	}
	// Finally check for overlap with existing properties.
	if (!errorarray.length) {
	    for (var i=0; i<newpaper.othercat.length; i++) {
		if (catgroups.assoc[newpaper.othercat[i]]) {
		    var mycatdef=catgroups.assoc[newpaper.othercat[i]];
		    var name=mycatdef.group.name+":"+mycatdef.name;
		    errorarray.push("Property '"+newpaper.othercat[i]+"' already defined above<br>("+name+")");
		}
	    }
	}
	errormessage=errorarray.join(';<br>');
	break;
    default:
	;  // No default checking
    }
	
    if (errormessage) {
	errormessage="Error: "+errormessage+".";
	if (editChange.extra[name])
	    errormessage += editChange.extra[name];

	messageslot.style.backgroundColor=errorcolor;
	messageslot.innerHTML=errormessage;
	return "error";
    } else if (warningmessage) {
	warningmessage="Warning: "+warningmessage+".";
	if (editChange.extra[name])
	    warningmessage+= editChange.extra[name];

	messageslot.style.backgroundColor=warningcolor;
	messageslot.innerHTML=warningmessage;
	return "warning";
    } else {
	messageslot.style.backgroundColor="";
	messageslot.innerHTML=editChange.mess[name]+editChange.extra[name];
	return "";
    }
}

/*
 * This function is called to do "final verification" of the entry.
 * It pops up windows on error or warning.
 *
 * Input:	editid	  Window edit identifier
 *		operation String describing current operation
 *
 * Return	"error"   Has Errors
 *		"warning" Has warnings
 *		"ok"	  Everything ok.
 */
function verifyForm(editid,operation) {
    var editentry=editlist[editid];
    if (!editentry)
	return;
    var editwin=editentry.win;
    var newpaper=editentry.data;
    var fields=['id','title','authorlist','publication','specifics','date',
		'award','abstext','copyright','source','xref','category'];
    var errors=0;
    var warnings=0;
    
    for (var i=0; i<fields.length; i++) {
	/*
	 * Must make sure to grab all info from form..., since it
	 * appears that "onchange" handler not always called before
	 * "onclick" for buttons at top.
	 */
	switch(fields[i]) {
	case 'source':
	    for (var j=0; j<newpaper.source.length; j++)
		editChange(fields[i],editid,j);
	    break;
	case 'xref':
	    for (var j=0; j<newpaper.xref.length; j++)
		editChange(fields[i],editid,j);
	    break;
	default:
	    editChange(fields[i],editid);
	}
	
	result=validateField(fields[i],editid,true);
	switch(result) {
	case 'error':
	    errors++;
	    break;
	case 'warning':
	    warnings++;
	    break;
	}
    }

    if (errors)
	return "error";
    else if (warnings)
	return "warning";
    else
	return "ok";
}

/*
 * Verify that input string could be a label.
 */
function labelChars(instring) {
    if (!instring)
	return "";
    var pos=instring.search(/[^a-zA-Z0-9_\-\:\.]/);
    if (pos != -1) {
	if (instring.charAt(pos)==' ')
	    return "Spaces not allowed in label";
	else
	    return ("Illegal Character for Label: '"+instring.charAt(pos)+"'");
    } else if ((instring.charAt(0)).search(/[a-zA-Z_]/) == -1)
	return "Labels must start with letter or underscore";
    else
	return "";
}

	    

/*
 * Verify that input string could be basic text (without HTML).
 */

function textChars(instring) {
    var allowed="_-:?!$.\".'()[]/=, ";

    if (!instring)
	return "";
    for (var i=0; i<instring.length; i++) {
	var mychar=instring.charAt(i);
	if ((mychar.search(/[a-zA-Z0-9]/) == -1) &&
	    (allowed.indexOf(mychar) == -1)) 
	    return ("Illegal Character, '"+mychar+"'");

    }	
    return "";
}

/*
 * Verify that input string could be basic text (with HTML).
 */
function textFormatChars(instring) {
    var allowed="\n<>-+*:?!@#%$.\"'()[]/=, ";

    if (!instring)
	return "";
    for (var i=0; i<instring.length; i++) {
	var mychar=instring.charAt(i);
	if ((mychar.search(/[a-zA-Z0-9]/) == -1) &&
	    (allowed.indexOf(mychar) == -1)) 
	    return ("Illegal Character, '"+mychar+"'");
    }	
    return "";
}

/*
 * Check for valid name for display link.
 */
function textDisplayLink(instring) {
    var allowed="_-:?!.'()/= ";

    if (!instring)
	return "";
    for (var i=0; i<instring.length; i++) {
	var mychar=instring.charAt(i);
	if ((mychar.search(/[a-zA-Z0-9]/) == -1) &&
	    (allowed.indexOf(mychar) == -1)) 
	    return ("Illegal Character, '"+mychar+"'");
    }	
    return "";
}

    
/*
 * Verify that input string could be valid hyperlink.
 */
function hyperlinkChars(instring) {
    // Pass everything for now.
    return "";
}

function escapeQuote(instring) {
    return instring.replace(/\"/g,"\\");
}
    
function delSource(editid,ident) {
    var editentry=editlist[editid];
    
    var editwin=editentry.win;
    var newpaper=editentry.data;
    if (!editwin || !newpaper)
	return;

    if (ident < 0 || ident >= newpaper.source.length)
	return;
    newpaper.source.splice(ident,1);
    formatEditWin(editid);
}

function addSource(editid) {
    var editentry=editlist[editid];
    
    var editwin=editentry.win;
    var newpaper=editentry.data;
    if (!editwin || !newpaper)
	return;
    newpaper.source.push(new DocSource("","PDF",""));
    formatEditWin(editid);
}

function delXref(editid,ident) {
    var editentry=editlist[editid];
    
    var editwin=editentry.win;
    var newpaper=editentry.data;
    if (!editwin || !newpaper)
	return;

    if (ident < 0 || ident >= newpaper.xref.length)
	return;
    newpaper.xref.splice(ident,1);
    formatEditWin(editid);
}

function addXref(editid) {
    var editentry=editlist[editid];
    
    var editwin=editentry.win;
    var newpaper=editentry.data;
    if (!editwin || !newpaper)
	return;
    newpaper.xref.push(new Xref(true,"",""));
    formatEditWin(editid);
}

 function deleteEntry(ident) {
    var thePaper=paperlist.assoc[ident];
    	
    if (!thePaper)
	return;
    
    var message="You have asked to delete:\n";
    message+="    Paper ID: \""+thePaper.getID()+"\"\n";
    message+="    Title:    \""+thePaper.name+"\".\n\n";
    message+="Are you absolutely sure?";

    if (!confirm(message))
	return;

    var index=findPaperIndex(ident);
    paperlist.splice(index,1);

    // Sort things/update global info (including assoc list)
    reflowPaperlist(paperlist); 
    undolist.push({op:"delete", value: thePaper});
    
    setFocusSelect();
    content.location.reload();
}

function editUndo() {
    // Try to undo the last change.
    if (!undolist.length)
	return;

    var lastundo=undolist.pop();
    switch(lastundo.op) {
    case 'delete':
	paperlist.push(lastundo.value);
	break;
    case 'add':
	deleteObjectFromList(lastundo.value,paperlist);
	break;
    case 'edit':
	// Kill off edited version
	deleteObjectFromList(lastundo.newvalue,paperlist);
	lastundo.flag=EditState.IDLE;

	// Restore old version
	paperlist.push(lastundo.value);
    default:

	break;
    }
    reflowPaperlist(paperlist); // Sort things
    if (lastundo.op == 'delete' || lastundo.op == 'edit')
	setFocusSelect("#xref-"+lastundo.value.getID());
    content.location.reload();
}

    
    
/*
 * This function finds the index of a paper that has the specified
 * identifier 
 */
function findPaperIndex(ident) {
    for (var i=0; i<paperlist.length; i++) 
	if (paperlist[i].id == ident ||
	    paperlist[i].uniqueid == ident)
	    return i;
    return null;
}

/*
 * Return comma-separated list of non-catgroup categories.
 */
function otherCategories(catlist) {
    var result="";

    for (i=0; i<catlist.length; i++) {
	if (!(catgroups.assoc[catlist[i]])) {
	    if (result != "") result+=", ";
	    result+=catlist[i];
	}
    }
    return result;
}
		
    
/****************************************************************
 * Random helper functions					*
 ****************************************************************/

function getSchemaIndex(instring) {
    var myargv=getArgv(instring);
    var schemainfo=myargv.schema;
    var result=new Object;
	
    if (schemainfo) {
	var temp=schemainfo.split(":");
    } else {
	var temp=[];
    }
    if (!temp.length) {
	result.index=0;
	result.sortdir=sortOrder.REVERSE;
    } else {
	var index=parseInt(temp[0]);
	if (isNaN(index) || index<0 || index >= schemalist.length)
	    result.index=0;
	else
	    result.index=index;

	if (temp.length == 1 || temp[1].toLowerCase().charAt(0) != 'f')
	    result.sortdir=sortOrder.REVERSE;
	else
	    result.sortdir=sortOrder.FORWARD;
    }
    result.abs=myargv.abs;
    result.schema=myargv.schema;
    return result;
}

function schemaString(index,sortdir,absid) {
    var mysearch="?schema="+index;
    if (sortdir == sortOrder.FORWARD)
	mysearch += ":F";
    if (absid)
	mysearch += ",abs="+absid;
    if (EditMode)
	mysearch += ",Edit";

    return mysearch;
}

/*
 * get argument list from URL.  Borrowed from O'Reily book.
 */
function getArgv(query) {
    var argname,argvalue;
    var args = new Object();
    var mystring=query;
    if (mystring.charAt(0) == '?')
	mystring=mystring.substring(1);
    
    var pairs=mystring.split(",");
    for (var i=0; i<pairs.length; i++) {
	var pos=pairs[i].indexOf('=');
	if (pos==-1) {
	    argname=pairs[i]; 
	    argvalue=true;		// No value.  Just set true
	} else {
	    argname=pairs[i].substring(0,pos);
	    argvalue=unescape(pairs[i].substring(pos+1));
	}
	args[argname.toLowerCase()]=argvalue;
    }
    return args;
}

/* Remove newlines, spaces at end of line, multiple spaces */
function normWS(instring) {
    if ((typeof instring != 'string') || instring=="")
	return instring; /* Keep null around */
    var result = instring.replace(/\s+/g,' ');
    var start = (result.charAt(0) == ' ')?1:0;
    var length = result.length - start - (((result.charAt(result.length-1) == ' ')?1:0));
    return result.substr(start,length);
}
	
/* wrap lines of the input string at whitespace */
function wrapText(instring,wraplen,tolken) {
    if (!instring)
	return "";
    
    var temp=instring.replace(/\<p\>/g,'\n<p>'); // paragraph breaks=>new lines
    var tolkens=normWS(temp).split(tolken?tolken:' ');
    var cursor=0;
    var result="";

    // This is set up so that we get at least one tolken/line
    for (var i=0; i<tolkens.length;i++) {
	if (i>0) {
	    if (tolkens[i].indexOf('<p>') != -1) {
		result += '\n\n';
		cursor = 0;
	    } else if ((cursor >= wraplen) || (cursor+1+tolkens[i].length) > wraplen) {
		result += (tolken?tolken:'')+'\n';
		cursor = 0;
	    } else {
		result += (tolken?tolken:' ');
		cursor++;
	    }
	}
	result += tolkens[i];
	cursor += tolkens[i].length;
    }
    return result;
}

/*
 * Delete object from list.
 *
 * Input:	inobj	Pointer to object
 *		inlist	Pointer to list
 *
 * Output:	void -- input list is modified
 *
 * Note that the assumption is that this object occurs only once in
 * the ist.
 */
function deleteObjectFromList(inobj,inlist) {
    for (var i=0; i<inlist.length; i++)
	if (inlist[i] == inobj)
	    inlist.splice(i,1);
}

/*
 * Create a new array that is identical to the input array with
 * escaped entries.
 */
function escapeArray(inarray) {
    var newarray=Array(inarray.length);

    for (var i=0; i<inarray.length; i++) 
	newarray[i]=escape(inarray[i]);

    return newarray;
}

/*
 * This is the inverse of escapeArray().
 */
function unescapeArray(inarray) {
    var newarray=Array(inarray.length);

    for (var i=0; i<inarray.length; i++) 
	newarray[i]=unescape(inarray[i]);

    return newarray;
}

/*
 * Extract the server name from an HTTP address.
 */
function ExtractServer(url) {
    result =url.match(/^(\w+):\/\/([\w\.]+)\//);
    if (result)
	return result[2];
    else
	return "";
}

	    
/*
 * Extract the directory portion of a URL.
 */
function ExtractDirectory(url) {
    result =url.match(/^(\w+:\/\/[\w\.]+\/(\S*\/)*)/);
    if (result)
	return result[1];
    else
	return "";
}    

/*
 * Clean up a path by removing "/./" and /../ and // except at head."
 *
 * The assumption here is that we don't have to worry about the
 * argument aspect of the path.
 */
function BuildPath(head,tail) {
    // Splits into: protospec path ?args with protospec="prot:server/"
    var breakup=(head+'/'+tail).match(/^(\w+:\/\/[\w\.]+\/)([^\s\?]*)(\?\S*)?/);

    var protspec=breakup[1];
    var path=breakup[2]?breakup[2]:"";
    var args=breakup[3]?breakup[3]:"";
    
    var temp;
    
    /*
     * We do this as a loop instead of a global replace, since one
     * change may impact the next.
     */
    while (true) {
	if (temp=path.match(/^\/(\S*)/))
	    // Kill leading '/'
	    path=temp[1];
	else if (temp=path.match(/(\S*)\/\.?\/(\S*)/))
	    // Turn '//' => '/' or '/./' => '/'
	    path=temp[1]+'/'+temp[2];
	else if (temp=path.match(/^((\S*\/)?)([^\/\s]+\/\.\.)(\S*)/)) 
	    // Delete : path/..
	    path=temp[1]+temp[4];
	else
	    break;
    }
    return protspec+path+args;
}

/*
 * Find and escape web quote character.
 */
function webescape(instring) {
    return instring.replace(/&/g,"&amp;");
}

/*
 * This function normalizes a link to be absolute (if necessary)
 */
function absLink(inlink,docroot) {
    if (inlink.indexOf("http://")==-1)
	return docroot+"/"+inlink;
    else
	return inlink;
}
