// Application code for Scripted Re-Mark - Batch Editor for Bookmarks
// http://www.greg-hill.id.au  2007

// http://ghill.customer.netspace.net.au/re-mark
// This work is licensed under a Creative Commons Attribution-ShareAlike 2.1 Australia License.

// Global Variables

pos=0;
ruleID=0;
newPosts=[];
oldPosts=[];
maxPos=0;
invokeCount={};
newTags={};
oldTags={};

fields=['d','u','n','t'];

ruleSet={};

window.onload=function(){ procDetails();}; 			// process details upfront

function subLink(t)
{
// Substitute variable values into link

var del_user=document.details.del_user.value;
var anchor=document.details.anchor.value;

if (!del_user)
	del_user='joshua';

temp_href=t.href;

t.href=t.href.replace('$del_user$', del_user).replace('$anchor$',anchor);

//window.location.href=u.replace('$del_user$', del_user).replace('$anchor$',anchor);
return;
}

function procDetails()
{
// Take in del.icio.us username and anchor tags, launch fetch of JSON feed

var del_user=document.details.del_user.value;
var anchor=document.details.anchor.value;
if (anchor)
	anchor="/"+anchor;
var response=document.getElementById('response');

response.innerHTML='Please wait ... fetching sample bookmarks.';

if (!del_user)
{
	response.innerHTML='Please enter a del.icio.us username.';
	return;
}

var s=document.createElement('script');
s.type='text/javascript';
s.src='http://del.icio.us/feeds/json/'+del_user+anchor+'?count=100&callback=procFeed';
document.getElementsByTagName("head")[0].appendChild(s);

return;
}

function procFeed(obj)
{
// Callback function to process del.icio.us JSON feed

var response=document.getElementById('response');

if (!obj)
{
	response.innerHTML='Invalid response: Please check del.icio.us username';
	return;
}

if (obj.length==0)
{
	response.innerHTML='No bookmarks: Please check del.icio.us username and filter tags';
	return;
}

oldPosts=obj;
maxPos=obj.length-1;
response.innerHTML='OK: Found '+obj.length+' bookmarks.';

var stemresp=document.getElementById('stemmer');
stemresp.innerHTML='';

testRules(); 						// apply rules to bookmarks

return;
}

function addRules()
{
// Add new rule to ruleSet (check if regexp ok first) and append to table

var rule=[];

var f=document.ruleSet;

var rf=-1, rc='i', rm='g', rfunc=false;

for(var i=0; i<f.rulefield.length; i++)
	if(f.rulefield[i].selected)
		rf=i;						// get index number of field

var rs=f.rulesearch.value;
var rr=f.rulereplace.value;

if(f.rulecase[1].selected)
	rc='';

if(f.rulematch[1].selected)
		rm='';

if(f.rulemode[1].selected)
		rfunc=true;

var flags=rc+rm;

try
{
	var rexp=new RegExp(rs,flags);		// test regexp
}
catch(e)
{
	alert("Error: Couldn't process rule:\n"+rs+"\n\n(Check regular expression.)");
	return;
}

if (rfunc)
{
	try
	{
		eval("func = "+rr);				// test function definition
	}
	catch(e)
	{
		alert("Error: Couldn't process function:\n"+rr+"\n\n(Check definition.)");
		return;
	}

	rule=[rf, rexp, func];
}
else
	rule=[rf, rexp, rr];

ruleID++;
ruleSet["ruleID"+ruleID]=rule;

appendRule();						// append rule to table
return;
}

function appendRule()
{

// Append new rule to end of ruleSet table

var f=document.ruleSet;
t=document.getElementById('ruleentry');

var tr=document.createElement('tr'); 
tr.bgColor='#cccccc';

var td=[], inp=[];
for(var i=0; i<6; i++)							// create new inputs in table entries
{
	td[i]=document.createElement('td');
	inp[i]=document.createElement('input');
	inp[i].type='text'; inp[i].readOnly='true';
	td[i].appendChild(inp[i]);
	tr.appendChild(td[i]);
}

inp[0].value=getValue(f.rulefield); inp[0].size='7';		// populate values
inp[1].value=f.rulesearch.value; inp[1].size='20';
inp[2].value=f.rulereplace.value; inp[2].size='20';
inp[3].value=getValue(f.rulecase); inp[3].size='7';
inp[4].value=getValue(f.rulematch); inp[4].size='5';
inp[5].value=getValue(f.rulemode); inp[5].size='6';
inp[5].id='ruleID'+ruleID;


td[6]=document.createElement('td');					// create remove button
inp[6]=document.createElement('input');
inp[6].type='button';
inp[6].value='Remove';
inp[6].name='ruleID'+ruleID;
inp[6].onclick= function() { removeRule(this.parentNode); };
td[6].appendChild(inp[6]);

tr.appendChild(td[6]);
t.appendChild(tr);

testRules();						// apply rules

return;
}

function removeRule(r)
{
// Remove rule from ruleSet array and table

var rID = r.firstChild.name;
var t = document.getElementById('ruleentry');
row = r.parentNode;

t.removeChild(row);
delete ruleSet[rID];

testRules();						// apply rules

return;
}

function getValue(e)
{
// For a radio/select inputs, returns current selection

// Why can't IE just use element.value to return the selected option?

var opt='selected'; targ='text';			// assume select

if(typeof(e[0][opt])=='undefined')
{
	opt='checked'; targ='value';			// switch to radio
}

for(var i=0; i<e.length; i++)
	if(e[i][opt])
		return e[i][targ];

alert("Error: Can't grok value of "+e.name);

return;
}

function testRules()
{
// Go through fetched bookmarks, clone them, apply rules to each

if (typeof(oldPosts)=='undefined' || !oldPosts)
{
	alert('Error: No bookmarks. Please complete Step 1.');
	return;
}

var item={}, f='', rule=[], oldItem;

var tb = document.forms.ruleSet.touched_bool.checked;
var tv = document.forms.ruleSet.touched_name.value;

for (var i=0; i<=maxPos; i++)							// pad out "missing" notes etc
	for(var j=0; j<fields.length; j++)
		if(typeof(oldPosts[i][fields[j]])=='undefined')
			if(fields[j]=='t')
				oldPosts[i].t=[];					// if tags, use an empty array
			else
				oldPosts[i][fields[j]]='';			// otherwise, empty string

for(var j in ruleSet)								// reset the invoke count
	invokeCount[j]=0;

for (var i=0; i<=maxPos; i++)
{
	newPosts[i]=clone(oldPosts[i]);					// clone old bookmarks
	item=newPosts[i];
	for(j in ruleSet)
	{
		rule=ruleSet[j];							// apply each rule
		f=fields[rule[0]];
		oldItem=item[f];
		item.t=item.t.join(' ');
		item[f]=item[f].replace(rule[1],rule[2]);
		item.t=item.t.split(' ');					// (if tags array, join/split first)
	
		if ( (item[f].toString()!=oldItem.toString()) || tb&&rule[1].test(oldItem) )		// if changed or matched while touched-enabled ...
		{
			invokeCount[j]++;						// increment the invoke count
			if (tb&&item.t.join(' ').indexOf(tv)==-1)
				 item.t.push(tv);
		}
	}
}

var x,p,s;

for (var j in ruleSet)								// put invocation counts into ruleset
{
	x=document.getElementById(j);
	p=x.parentNode;

	if (p.lastChild==p.firstChild)
		s=document.createElement('span');
	else
		s=p.lastChild;

	s.innerHTML='&nbsp;('+parseInt(100*invokeCount[j]/(maxPos+1))+'%)';
	p.appendChild(s);
}


pos=0;
showMark();					// show first bookmark

build();					// update target code

return;
}

function clone(obj)
{
// Clones old bookmark entry
// Why can't IE have a element.toSource method?

var newObj={};

for(var j=0; j<fields.length; j++)
	newObj[fields[j]]=obj[fields[j]];

return newObj;
}

function showMark()
{
// Displays original and transformed bookmarks

var f=document.test;

f.title1.value=oldPosts[pos].d;
f.url1.value=oldPosts[pos].u;

f.notes1.value=oldPosts[pos].n;
f.tags1.value=oldPosts[pos].t.join(' ');

f.title2.value=newPosts[pos].d;
f.url2.value=newPosts[pos].u;
f.notes2.value=newPosts[pos].n;
f.tags2.value=newPosts[pos].t.join(' ');

return;
}

function testNext()
{
// Display next bookmark

if (pos++==maxPos)
	pos=0;
showMark();
return;
}

function testPrev()
{
// Display previous bookmark

if (pos--==0)
	pos=maxPos;
showMark();
return;
}

function build()
{
// Build code for user to paste into browser.
// String consists of delay, ruleSet, sharing and then code to launch external JavaScript

var r=0, buildStr='javascript:';
var delay = document.apply.delay.value;
buildStr+=' delay='+delay+'; ';

buildStr+='ruleSet=[';
for(var j in ruleSet)
{
	var rt=typeof(ruleSet[j][2]);
	var rule=ruleSet[j].toString().replace(/\n/g,'');		// get rule as array

	if (rt=="string")
	{
		r=rule.lastIndexOf(',');
		rule=rule.substring(0,r)+',"'+rule.substring(r+1);	// put quotes around replace string
		buildStr+='['+rule+'"]';
	}
	else
		buildStr+='['+rule+']';						// but not if it's a function
}
buildStr=buildStr.replace(/\]\[/g,'],[');					// put comma inbetween elements
buildStr+=']; ';

var sharing=0, s=document.ruleSet.sharing; 
sharing=getValue(s);

buildStr+='sharing='+sharing+'; ';

var touched;

if (document.ruleSet.touched_bool.checked)
	touched='"'+document.ruleSet.touched_name.value+'"';
else
	touched='""';

buildStr+='touched='+touched+'; ';

buildStr+='var s=document.createElement("script"); s.type="text/javascript"; s.src="http://ghill.customer.netspace.net.au/re-mark/re-mark-engine-v05.js"; document.getElementsByTagName("head")[0].appendChild(s); void(0);';

document.apply.code.value=buildStr;

var warning=document.getElementById('warning');
if (buildStr.length>507)
		warning.innerHTML="*** Warning *** You have a very big ruleset (code is "+buildStr.length+" characters). If you try this in Internet Explorer 6, it will fail. Either use FireFox or break up the rules into a smaller amount."

else
	warning.innerHTML="Code OK";	
return;
}

function tagTidy()
{
// fetches users' tags, applies Porter stemming and generates edit rules

// assumes porter_stemmer.js is loaded, with function stemWord(w)

// Take in del.icio.us username and anchor tags, launch fetch of JSON feed

var del_user=document.details.del_user.value;
var anchor=document.details.anchor.value;
var stemresp=document.getElementById('stemmer');

stemresp.innerHTML='Please wait ... fetching tags.';

if (!del_user)
{
	stemresp.innerHTML='Please enter a del.icio.us username.';
	return;
}

var s=document.createElement('script');
s.type='text/javascript';

// *** 10/8/2008 *** Can't use anchor tag to fetch tag feed any more :-(
//s.src='http://del.icio.us/feeds/json/tags/'+del_user+'/'+anchor+'?sort=count&callback=procTagFeed';
s.src='http://del.icio.us/feeds/json/tags/'+del_user+'?sort=count&callback=procTagFeed';

document.getElementsByTagName("head")[0].appendChild(s);

return;
}

function procTagFeed(obj)
{
// Callback function to process del.icio.us JSON feed of tags

var stemresp=document.getElementById('stemmer');

if (!obj)
{
	stemresp.innerHTML='Invalid response: Please check del.icio.us username';
	return;
}

var count=0;
for (var i in obj)
	count++;

if (count==0)
{
	stemresp.innerHTML='No tags: Please check del.icio.us username and filter tags';
	return;
}

oldTags=obj;
stemresp.innerHTML='OK: Found '+count+' tags';

reduceTags();

return;
}

function reduceTags()
{
// iterate through tags, apply stemmer and build associative array of new tags

var stemresp=document.getElementById('stemmer');

newTags={};							// reset newTags to empty

var scount=0;

var s;

for (var t in oldTags)
{
	s = stemWord(t);

	//if (t=="watch")
	//	alert('s: '+s+'\nt: '+t);
	
	if(typeof(newTags[t])=='function') 
		newTags[t]=[]; 

	if (newTags[s])					// if stem is already included
	{
		newTags[s].push([t,oldTags[t]]);	// push tag onto array
		//alert('adding '+t+'('+oldTags[t]+') to '+s);
	}

	else
	{
		newTags[s]=[[t,oldTags[t]]];		// start another array
		scount++;
		//alert('starting '+s);
	}
	
}

var stemresp=document.getElementById('stemmer');
stemresp.innerHTML+=' using '+scount+' unique stems.';

makeStemRules();

return;
}

function makeStemRules()
{
// iterate through reduced tags, load stemming rules for each and apply

var p=0;

// set rule parameters

r = document.forms.ruleSet;
r.rulefield[3].selected = true;  				// set to tags
r.rulecase[0].selected = true;				// set to ignore case
r.rulematch[0].selected = true;				// set to match all
r.rulemode[0].selected = true;				// set to use as string

for (var s in newTags)
{
	p=0;
	if (getValue(r.stem)==0)				// if stemming mode is frequency 
	{
		for(var i=0; i<newTags[s].length; i++)	// pick most-frequent tag as dominant
			if (newTags[s][i][1]>newTags[s][p][1])
				p=i;
	}
	else
	{
		for(var i=0; i<newTags[s].length; i++)	// pick lexically-shortest tag as dominant
			if (newTags[s][i][0].length<newTags[s][p][0].length)
				p=i;
	}
		
	r.rulereplace.value=newTags[s][p][0];		// set replacement value to dominant tag
	for (var i=0; i<newTags[s].length; i++)   
	{
		r.rulesearch.value=newTags[s][i][0];	// set search value to unstemmed tag

		if (r.rulesearch.value!=r.rulereplace.value)
			addRules();					// apply rule to set if different
	}
}

r.rulereplace.value='';
r.rulesearch.value='';

return;
}

function clearAll()
{
// loop through, removing any rules

var t=document.getElementById('ruleentry');

while(t.innerHTML.indexOf('ruleID')>0)
	removeRule(t.lastChild.lastChild);

return;
}


// Mass Delete Code

function showDelete()
{

//del_code_str="javascript:var lnk=document.links;var del=[];for(var i=0;i<lnk.length;i++)if(lnk[i].className=='rm')del.push(lnk[i]);del.reverse();dl=del.length;var p=prompt('WARNING: Type YES to delete all '+dl+' bookmarks on this page.','no');if(p=='YES'){alert('Proceeding');for(var i=0;i<dl;i++)del[i].onclick=rmPostYes;delPost();}else{alert('Abandoning');}void(0);function delPost(){if(!del.length){alert('Done.');return;}del.pop().onclick();setTimeout('delPost();',"+document.forms.apply.delay.value*1000+");return;}";

del_code_str="javascript:var delay="+document.forms.apply.delay.value+"; var s=document.createElement('script'); s.type='text/javascript'; s.src='http://ghill.customer.netspace.net.au/re-mark/re-mark-del-code.js'; document.getElementsByTagName('head')[0].appendChild(s); void(0);";

alert("Warning\n\nThis code will permanently and irrevocably delete bookmarks from your del.icio.us account. Please read the guidelines before proceeding.\n\nYou will be need to have a new window with your bookmarks visible. All bookmarks visible on that page will be deleted.");
var p = prompt("To confirm that you understand this and wish to delete, please type YES", "no");
if (p!="YES")
{
    alert("Abandoning ...");
    return;
}

p = prompt("Please copy and paste this code into your new window:", del_code_str);

return;
}

