20,741
edits
m (→The script: extend firefox xpi/addon stub, prototype a GA/GP based regex/xpath solver using the genetic.js framework (just loaded, not used by default currently)) |
|||
Line 446: | Line 446: | ||
// @require https://code.jquery.com/jquery-1.10.2.js | // @require https://code.jquery.com/jquery-1.10.2.js | ||
// @require https://code.jquery.com/ui/1.11.4/jquery-ui.js | // @require https://code.jquery.com/ui/1.11.4/jquery-ui.js | ||
// @require http://subprotocol.com/js/genetic-0.1.12.min.js | |||
// @resource jQUI_CSS https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css | // @resource jQUI_CSS https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css | ||
// @resource myLogo http://wiki.flightgear.org/images/2/25/Quotes-logo-200x200.png | // @resource myLogo http://wiki.flightgear.org/images/2/25/Quotes-logo-200x200.png | ||
Line 480: | Line 481: | ||
'use strict'; | 'use strict'; | ||
// TODO: move to GreaseMonkey/UI host | // TODO: move to GreaseMonkey/UI host | ||
Line 519: | Line 521: | ||
var Environment = { | var Environment = { | ||
getHost: function(xpi=false) { | getHost: function(xpi=false) { | ||
if(xpi) { | if(xpi) { | ||
Environment.scriptEngine = 'firefox addon'; | Environment.scriptEngine = 'firefox addon'; | ||
Line 603: | Line 605: | ||
} // foreach test | } // foreach test | ||
}, // runAPITests | }, // runAPITests | ||
/* | |||
* =================================================================================================================================================== | |||
* | |||
*/ | |||
// NOTE: This mode/environment is WIP and highly experimental ... | // NOTE: This mode/environment is WIP and highly experimental ... | ||
Line 645: | Line 652: | ||
} | } | ||
}); | }); | ||
// for selection handling stuff, see: https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/selection | |||
function myListener() { | |||
console.log("A selection has been made."); | |||
} | |||
var selection = require("sdk/selection"); | |||
selection.on('select', myListener); | |||
}, //registerTrigger | }, //registerTrigger | ||
get_persistent: function(key, default_value) {return default_value;}, | get_persistent: function(key, default_value) { | ||
// https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/simple-storage | |||
var ss = require("sdk/simple-storage"); | |||
console.log("firefox mode does not yet have persistence support"); | |||
return default_value;}, | |||
set_persistent: function(key, value) { | set_persistent: function(key, value) { | ||
console.log("persistence stubs not yet filled in !"); | console.log("persistence stubs not yet filled in !"); | ||
}, | }, | ||
set_clipboard: function() { | |||
// https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/ | set_clipboard: function(content) { | ||
console.log('clipboard stub not yet filled in ...'); | // https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/clipboard | ||
} | |||
//console.log('clipboard stub not yet filled in ...'); | |||
var clipboard = require("sdk/clipboard"); | |||
clipboard.set(content); | |||
} //set_cliipboard | |||
}, // end of FireFox addon config | }, // end of FireFox addon config | ||
// placeholder for now ... | |||
Android: { | |||
// NOP | |||
}, // Android | |||
Line 890: | Line 920: | ||
[].forEach.call(CONFIG['FlightGear.wiki'].modes, function(mode) { | [].forEach.call(CONFIG['FlightGear.wiki'].modes, function(mode) { | ||
//dbLog("Checking trigger:"+mode.name); | //dbLog("Checking trigger:"+mode.name); | ||
if(mode.trigger) { | if(mode.trigger() ) { | ||
mode.handler(); | mode.handler(); | ||
} | } | ||
Line 1,199: | Line 1,229: | ||
value: article.name, // FIXME: just a placeholder for now | value: article.name, // FIXME: just a placeholder for now | ||
text : article.name | text : article.name | ||
})); | })); //append option | ||
}); | }); // foreach | ||
} | } // updateArticleList | ||
// add the article list to the corresponding dropdown menus | // add the article list to the corresponding dropdown menus | ||
Line 1,210: | Line 1,240: | ||
var article = this.value; | var article = this.value; | ||
// HACK: try to get a login token (actually not needed just for reading ...) | |||
Host.download('http://wiki.flightgear.org/api.php?action=query&prop=info|revisions&intoken=edit&rvprop=timestamp&titles=Main%20Page', function (response) { | Host.download('http://wiki.flightgear.org/api.php?action=query&prop=info|revisions&intoken=edit&rvprop=timestamp&titles=Main%20Page', function (response) { | ||
var message = 'FlightGear wiki login status (AJAX):'; | var message = 'FlightGear wiki login status (AJAX):'; | ||
var status = response.statusText; | var status = response.statusText; | ||
// populate dropdown menu with article sections | |||
if (status === 'OK') { | if (status === 'OK') { | ||
Line 1,222: | Line 1,253: | ||
try { | try { | ||
var sections = JSON.parse(response.responseText); | var sections = JSON.parse(response.responseText); | ||
$('div#options select#section_select', markup).empty(); // delete all sections | |||
$.each(sections.parse.sections, function (i, section) { | $.each(sections.parse.sections, function (i, section) { | ||
Line 1,237: | Line 1,262: | ||
})); | })); | ||
}); //foreach section | }); //foreach section | ||
} | |||
catch (e) { | |||
alert(e.message); | |||
} | |||
}); // | //alert("Finsihed downloading FAQ sections:\n"+sections.parse.sections[0].line ); | ||
}); //download sections | |||
Line 1,249: | Line 1,282: | ||
}); // on select change | }); // on select change | ||
// init the tab stuff | |||
markup.tabs(); | markup.tabs(); | ||
Line 1,274: | Line 1,308: | ||
}; | }; | ||
// actually show our tabbed dialog using the params above | |||
markup.dialog(diagParam); | markup.dialog(diagParam); | ||
Line 1,279: | Line 1,314: | ||
} // jQueryTabbed() | } // jQueryTabbed() | ||
}; | }; // output methods | ||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
Line 1,998: | Line 2,033: | ||
return str.replace(/_/g, ' '); | return str.replace(/_/g, ' '); | ||
} | } | ||
</syntaxhighlight> | |||
// IGNORE THIS: | |||
// This is an experiment to use GA/GP (genetic programming) to help procedurally evolve xpath and regex expressions if/when the underlying websites change | |||
// so that we don't have to manually update/edit the script accordingly (this would also work for mobile themes etc) | |||
// For now, this is heavily based on the genetic.js framework/examples: http://subprotocol.com/system/genetic-hello-world.html | |||
// The idea is to evolve the xpath/regex expression by evaluating its return value against the expected/desired value | |||
// the most important thing here is having a suitable fitness function | |||
// | |||
try { | |||
var genetic = Genetic.create(); | |||
// TODO: use minimizer: redundant_bytes + duration_msec + xpath.length | |||
genetic.optimize = Genetic.Optimize.Maximize; | |||
genetic.select1 = Genetic.Select1.Tournament2; | |||
genetic.select2 = Genetic.Select2.Tournament2; | |||
genetic.seed = function() { | |||
function randomString(len) { | |||
var text = ""; | |||
var charset = "\\abcdefghijklmnopqrstuvwxyz0123456789 ()<>*.,"; | |||
for(var i=0;i<len;i++) | |||
text += charset.charAt(Math.floor(Math.random() * charset.length)); | |||
return text; | |||
} | |||
// create random strings that are equal in length to solution | |||
return randomString( this.userData["solution"].length); | |||
}; | |||
genetic.mutate = function(entity) { | |||
function replaceAt(str, index, character) { | |||
return str.substr(0, index) + character + str.substr(index+character.length); | |||
} | |||
// chromosomal drift | |||
var i = Math.floor(Math.random()*entity.length); | |||
return replaceAt(entity, i, String.fromCharCode(entity.charCodeAt(i) + (Math.floor(Math.random()*2) ? 1 : -1))); | |||
}; | |||
genetic.crossover = function(mother, father) { | |||
// two-point crossover | |||
var len = mother.length; | |||
var ca = Math.floor(Math.random()*len); | |||
var cb = Math.floor(Math.random()*len); | |||
if (ca > cb) { | |||
var tmp = cb; | |||
cb = ca; | |||
ca = tmp; | |||
} | |||
var son = father.substr(0,ca) + mother.substr(ca, cb-ca) + father.substr(cb); | |||
var daughter = mother.substr(0,ca) + father.substr(ca, cb-ca) + mother.substr(cb); | |||
return [son, daughter]; | |||
}; | |||
genetic.determineExcessBytes = function (text, needle) { | |||
return text.length - needle.length; | |||
}; | |||
genetic.containsText = function (text, needle) { | |||
return text.search(needle); | |||
}; | |||
genetic.isValid = function(exp) { | |||
}; | |||
/* myFitness: | |||
* - must be a valid xpath/regex expression (try/call) | |||
* - must containsText the needle | |||
* - low relative offset in text (begin/end) | |||
* - excessBytes | |||
* - short expression (expression length) | |||
* - expression footprint (runtime) | |||
*/ | |||
// TODO: the fitness function should validate each xpath/regex first | |||
genetic.fitness = function(entity) { | |||
var fitness = 0; | |||
var result; | |||
var validExp = 0.1; | |||
var hasToken = 0.1; | |||
var searchSpace = this.userData.searchSpace; | |||
var expect = this.userData.expect; | |||
// promote regex expressions that don't throw an exception | |||
if (0) | |||
try { | |||
var regex = new RegExp(entity); | |||
searchSpace.match(regex); | |||
validExp=10; | |||
} | |||
catch(e) { | |||
} | |||
//if (searchSpace.match(regex).match(expect)) hasToken = 20; | |||
var i; | |||
for (i=0;i<entity.length;++i) { | |||
// increase fitness for each character that matches | |||
if (entity[i] == this.userData["solution"][i]) | |||
fitness += 1; | |||
// award fractions of a point as we get warmer | |||
fitness += (127-Math.abs(entity.charCodeAt(i) - this.userData["solution"].charCodeAt(i)))/50; | |||
} | |||
return fitness + (1*validExp + 1* hasToken); | |||
}; | |||
genetic.generation = function(pop, generation, stats) { | |||
// stop running once we've reached the solution | |||
return pop[0].entity != this.userData["solution"]; | |||
}; | |||
genetic.notification = function(pop, generation, stats, isFinished) { | |||
function lerp(a, b, p) { | |||
return a + (b-a)*p; | |||
} | |||
var value = pop[0].entity; | |||
this.last = this.last||value; | |||
if (pop != 0 && value == this.last) | |||
return; | |||
var solution = []; | |||
var i; | |||
for (i=0;i<value.length;++i) { | |||
solution.push(value[i]); | |||
} | |||
console.log("Generation:"+ generation + " Fitness:" + pop[0].fitness.toPrecision(5) + " Solution:" + solution.join("")); | |||
this.last = value; | |||
}; | |||
$('document').ready(function() { | |||
var config = { | |||
"iterations": 4000 | |||
, "size": 250 | |||
, "crossover": 0.3 | |||
, "mutation": 0.4 | |||
, "skip": 20 | |||
//, "webWorkers": false | |||
}; | |||
/* | |||
var profile = CONFIG['Sourceforge Mailing list']; | |||
var posting = profile.tests[0]; | |||
var author_xpath = profile.title.xpath; | |||
*/ | |||
// the regex we want to evolve | |||
var solution = "/From: (.*) <.*@.*>/"; | |||
var searchSpace = "From: John Doe <John@do...> - 2020-07-02 17:36:03"; | |||
var expect = "John Doe"; | |||
// let's assume, we'd like to evolve a regex expression like this one | |||
var userData = { | |||
solution: solution, | |||
searchSpace: searchSpace, | |||
expect: expect | |||
}; | |||
if(0) // disabled by default | |||
genetic.evolve(config, userData); | |||
}); // document.ready() | |||
console.log("genetic.js is loaded and working, but disabled for now"); | |||
} // try | |||
catch (e) { | |||
console.log("genetic.js error:\n" +e.message); | |||
} // catch</syntaxhighlight> | |||
{{Appendix}} | {{Appendix}} |