FlightGear wiki:Instant-Refs: Difference between revisions

Jump to navigation Jump to search
m
→‎The script: Script beautified
m (→‎The script: Use Genetic.js instead of dist.js (which is meant to be used by NPM only))
m (→‎The script: Script beautified)
Line 442: Line 442:
// @license    public domain
// @license    public domain
// @version    0.39
// @version    0.39
// @date        2016-05-05
// @date        2016-05-20
// @description Automatically converts selected FlightGear mailing list and forum quotes into post-processed MediaWiki markup (i.e. cquotes).
// @description Automatically converts selected FlightGear mailing list and forum quotes into post-processed MediaWiki markup (i.e. cquotes).
// @description:it Converte automaticamente citazioni dalla mailing list e dal forum di FlightGear in marcatori MediaWiki (cquote).
// @description:it Converte automaticamente citazioni dalla mailing list e dal forum di FlightGear in marcatori MediaWiki (cquote).
Line 470: Line 470:
// @noframes
// @noframes
// ==/UserScript==
// ==/UserScript==
//
 
// This work has been released into the public domain by their authors. This
// This work has been released into the public domain by their authors. This
// applies worldwide.
// applies worldwide.
Line 476: Line 476:
// The authors grant anyone the right to use this work for any purpose, without
// The authors grant anyone the right to use this work for any purpose, without
// any conditions, unless such conditions are required by law.
// any conditions, unless such conditions are required by law.
// This script has a number of dependencies that are implicitly satisfied when
// run as a user script via GreaseMonkey/TamperMonkey; however, these need to
// be explicitly handled when using a different mode (e.g. Firefox/Android):
//
//
// This script has a number of dependencies that are implicitly satisfied when run as a user script
// via GreaseMonkey/TamperMonkey; however, these need to be explicitly handled when using a different mode (e.g. firefox/android):
//
// - jQuery - user interface (REQUIRED)
// - jQuery - user interface (REQUIRED)
// - genetic-js - genetic programming (OPTIONAL/EXPERIMENTAL)
// - genetic-js - genetic programming (OPTIONAL/EXPERIMENTAL)
// - synaptic - neural networks (OPTIONAL/EXPERIMENTAL)
// - synaptic - neural networks (OPTIONAL/EXPERIMENTAL)
//
//


/* Here are some TODOs
/* Here are some TODOs
Line 493: Line 492:
  * - add helpers for [].forEach.call, map, apply and call
  * - add helpers for [].forEach.call, map, apply and call
  * - replace for/in, for/of, let statements for better compatibility (dont require ES6)
  * - replace for/in, for/of, let statements for better compatibility (dont require ES6)
  * - for the same reason, replace use of functions with default params  
  * - for the same reason, replace use of functions with default params
  * - isolate UI (e.g. JQUERY) code in UserInterface hash
  * - isolate UI (e.g. JQUERY) code in UserInterface hash
  * - expose regex/transformations via the UI
  * - expose regex/transformations via the UI
*
  */
  */
/*jslint
    devel
*/


'use strict';
'use strict';


 
// prevent conflicts with jQuery used on webpages:
// https://wiki.greasespot.net/Third-Party_Libraries#jQuery
// http://stackoverflow.com/a/5014220
// TODO: move to GreaseMonkey/UI host
// TODO: move to GreaseMonkey/UI host
// prevent conflicts with jQuery used on webpages: https://wiki.greasespot.net/Third-Party_Libraries#jQuery
// http://stackoverflow.com/a/5014220
this.$ = this.jQuery = jQuery.noConflict(true);
this.$ = this.jQuery = jQuery.noConflict(true);


// this hash is just intended to help isolate UI specifics
// this hash is just intended to help isolate UI specifics so that we don't
// so that we don't need to maintain/port tons of code  
// need to maintain/port tons of code
var UserInterface = {
    get: function () {
        return UserInterface.DEFAULT;
    },
 
    CONSOLE: {
    }, // CONSOLE (shell, mainly useful for testing)
 
    DEFAULT: {
        alert: function (msg) {
            return window.alert(msg);
        },
        prompt: function (msg) {
            return window.prompt(msg);
        },
        confirm: function (msg) {
            return window.confirm(msg);
        },
        dialog: null,
        selection: null,
        populateWatchlist: function () {
        },
        populateEditSections: function () {
        }
    }, // default UI mapping (Browser/User script)


var UserInterface = {
     JQUERY: {
  get: function() {
     } // JQUERY
     return UserInterface.DEFAULT;
  },
 
CONSOLE: {
 
}, // CONSOLE (shell, mainly useful for testing)
 
DEFAULT: {
  alert: function(msg) {return window.alert(msg);    },
  prompt: function(msg) {return window.prompt(msg);  },
  confirm: function(msg) {return window.confirm(msg); },
  dialog: null,
  selection: null,
  populateWatchlist: function() {
   
  },
  populateEditSections: function() {
   
  }
}, // default UI mapping (Browser/User script)
 
  JQUERY: {
      
  } // JQUERY  
 
}; // UserInterface
}; // UserInterface


var UI = UserInterface.get(); // DEFAULT for now
var UI = UserInterface.get(); // DEFAULT for now


// This hash is intended to help encapsulate platform specifics (browser/
// scripting host). Ideally, all APIs that are platform specific should be
// kept here. This should make it much easier to update/port and maintain the
// script in the future.
var Environment = {
    getHost: function (xpi = false) {
        if (xpi) {
            Environment.scriptEngine = 'firefox addon';
            console.log('in firefox xpi/addon mode');
            return Environment.FirefoxAddon; // HACK for testing the xpi mode (firefox addon)
        }
        // This will determine the script engine in use: http://stackoverflow.com/questions/27487828/how-to-detect-if-a-userscript-is-installed-from-the-chrome-store
        if (typeof (GM_info) === 'undefined') {
            Environment.scriptEngine =
                "plain Chrome (Or Opera, or scriptish, or Safari, or rarer)";
            // See http://stackoverflow.com/a/2401861/331508 for optional browser sniffing code.
        } else {
            Environment.scriptEngine = GM_info.scriptHandler ||
                "Greasemonkey";
        }
        console.log('Instant cquotes is running on ' + Environment.scriptEngine +
            '.');
        // console.log("not in firefox addon mode...");
        // See also: https://wiki.greasespot.net/Cross-browser_userscripting
        return Environment.GreaseMonkey; // return the only/default host (for now)
    },
    validate: function (host) {
        if (host.get_persistent('startup.disable_validation', false))
            return;
        if (Environment.scriptEngine !== "Greasemonkey")
            console.log(
                "NOTE: This script has not been tested with script engines"
                + " other than GreaseMonkey recently!"
            );
        var dependencies = [{
            name: 'jQuery',
            test: function () {}
        }, {
            name: 'genetic.js',
            test: function () {}
        }, {
            name: 'synaptic',
            test: function () {}
        }, ];
        [].forEach.call(dependencies, function (dep) {
            console.log("Checking for dependency:" + dep.name);
            var status = false;
            try {
                dep.test.call(undefined);
                status = true;
            } catch (e) {
                status = false;
            } finally {
                var success = (status) ? '==> success' :
                    '==> failed';
                console.log(success);
                return status;
            }
        });
    }, // validate
    // this contains unit tests for checking crucial APIs that must work for
    // the script to work correctly
    // for the time being, most of these are stubs waiting to be filled in
    // for a working example, refer to the JSON test at the end
    // TODO: add jQuery tests
    APITests: [{
            name: 'download',
            test: function (recipient) {
                recipient(true);
            }
        }, {
            name: 'make_doc',
            test: function (recipient) {
                recipient(true);
            }
        }, {
            name: 'eval_xpath',
            test: function (recipient) {
                recipient(true);
            }
        }, {
            name: 'JSON de/serialization',
            test: function (recipient) {
                    //console.log("running json test");
                    var identifier = 'unit_tests.json_serialization';
                    var hash1 = {
                        x: 1,
                        y: 2,
                        z: 3
                    };
                    Host.set_persistent(identifier, hash1, true);
                    var hash2 = Host.get_persistent(identifier, null,
                        true);
                    recipient(JSON.stringify(hash1) === JSON.stringify(
                        hash2));
                } // callback
        },
        // downloads a posting and tries to transform it to 3rd person speech ...
        // TODO: add another test to check forum postings
        {
            name: 'text/speech transformation',
            test: function (recipient) {
                    // the posting we want to download
                    var url =
                        'https://sourceforge.net/p/flightgear/mailman/message/35066974/';
                    Host.downloadPosting(url, function (result) {
                        // only process the first sentence by using comma/dot as
                        // delimiter
                        var firstSentence = result.content.substring(
                            result.content.indexOf(',') + 1,
                            result.content.indexOf('.'));
                        var transformed = transformSpeech(
                            firstSentence, result.author,
                            null, speechTransformations);
                        console.log(
                            "3rd person speech transformation:\n" +
                            transformed);
                        recipient(true);
                    }); // downloadPosting()
                } // test()
        }, // end of speech transform test
        {
            name: "download $FG_ROOT/options.xml",
            test: function (recipient) {
                    downloadOptionsXML();
                    recipient(true);
                } // test
        }
    ], // end of APITests
    runAPITests: function (host, recipient) {
        console.log("Running API tests");
        for (let test of Environment.APITests) {
            //var test = Environment.APITests[t];
            // invoke the callback passed, with the hash containing the test
            // specs, so that the console/log or a div can be updated showing
            // the test results
            recipient.call(undefined, test);
        } // foreach test
    }, // runAPITests
    /*
    * ========================================================================
    */
    // NOTE: This mode/environment is WIP and highly experimental ...
    // To see this working, you need to package up the whole file as a Firefox
    // XPI using "jpm xpi" and then start the whole thing via "jpm run", to do
    // that, you also need a matching package.json (i.e. via jpm init)
    // ALSO: you will have to explicitly install any dependencies using jpm
    FirefoxAddon: {
        init: function () {
            console.log("Firefox addon mode ...");
        },
        getScriptVersion: function () {
            return '0.36'; // FIXME
        },
        dbLog: function (msg) {
            console.log(msg);
        },
        addEventListener: function (ev, cb) {
            require("sdk/tabs").on("ready", logURL);
            function logURL(tab) {
                console.log("URL loaded:" + tab.url);
            }
        },
        registerConfigurationOption: function (name, callback, hook) {
            // https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Add_a_Context_Menu_Item
            console.log("config menu support n/a in firefox mode");
            // https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Using_third-party_modules_%28jpm%29
            var menuitems = require("menuitem");
            var menuitem = menuitems.Menuitem({
                id: "clickme",
                menuid: "menu_ToolsPopup",
                label: name,
                onCommand: function () {
                    console.log("menuitem clicked:");
                    callback();
                },
                insertbefore: "menu_pageInfo"
            });
        },


// This hash is intended to help encapsulate platform specifics (browser/scripting host)
        registerTrigger: function () {
// Ideally, all APIs that are platform specific should be kept here
            // https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Add_a_Context_Menu_Item
// This should make it much easier to update/port and maintain the script in the future
            // https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/context-menu#Item%28options%29
var Environment = {
            var contextMenu = require("sdk/context-menu");
  getHost: function(xpi=false) {
            var menuItem = contextMenu.Item({
                label: "Instant Cquote",
    if(xpi) {
                context: contextMenu.SelectionContext(),
      Environment.scriptEngine = 'firefox addon';
                // https://developer.mozilla.org/en/Add-ons/SDK/Guides/Two_Types_of_Scripts
      console.log('in firefox xpi/addon mode');
                // https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts
      return Environment.FirefoxAddon; // HACK for testing the xpi mode (firefox addon)
                contentScript: 'self.on("click", function () {' +
    }
                    ' var text = window.getSelection().toString();' +
   
                    '  self.postMessage(text);' +
    // This will determine the script engine in use: http://stackoverflow.com/questions/27487828/how-to-detect-if-a-userscript-is-installed-from-the-chrome-store
                    '});',
    if (typeof(GM_info) === 'undefined') {
                onMessage: function (selectionText) {
    Environment.scriptEngine = "plain Chrome (Or Opera, or scriptish, or Safari, or rarer)";
                    console.log(selectionText);
    // See http://stackoverflow.com/a/2401861/331508 for optional browser sniffing code.
                    instantCquote(selectionText);
  }
                }
  else {
            });
    Environment.scriptEngine = GM_info.scriptHandler  ||  "Greasemonkey";
 
    }
            // for selection handling stuff, see: https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/selection
  console.log ('Instant cquotes is running on ' + Environment.scriptEngine + '.');
 
   
            function myListener() {
  //console.log("not in firefox addon mode...");
                console.log("A selection has been made.");
    // See also: https://wiki.greasespot.net/Cross-browser_userscripting
            }
    return Environment.GreaseMonkey; // return the only/default host (for now)
            var selection = require("sdk/selection");
  },
            selection.on('select', myListener);
 
 
  validate: function(host) {
        }, //registerTrigger
    if (host.get_persistent('startup.disable_validation',false)) return;
 
   
        get_persistent: function (key, default_value) {
    if(Environment.scriptEngine !== "Greasemonkey")  
            // https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/simple-storage
      console.log("NOTE: This script has not been tested with script engines other than GreaseMonkey recently!");
            var ss = require("sdk/simple-storage");
   
 
    var dependencies = [
            console.log(
      {name:'jQuery', test: function() {} },
                "firefox mode does not yet have persistence support"
      {name:'genetic.js', test: function() {} },
            );
      {name:'synaptic', test: function() {} },
            return default_value;
    ];
        },
      
        set_persistent: function (key, value) {
    [].forEach.call(dependencies, function(dep) {
            console.log("firefox persistence stubs not yet filled in !");
      console.log("Checking for dependency:"+dep.name);
        },
      var status=false;
        set_clipboard: function (content) {
      try {
            // https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/clipboard
      dep.test.call(undefined);
 
      status=true;
            //console.log('clipboard stub not yet filled in ...');
      }
            var clipboard = require("sdk/clipboard");
      catch(e) {
            clipboard.set(content);
      status=false;     
        } //set_cliipboard
      }
 
      finally {
    }, // end of FireFox addon config
        var success = (status)?'==> success':'==> failed';
 
        console.log(success);
     // placeholder for now ...
        return status;
    Android: {
      }
        // NOP
    });
    }, // Android
  }, // validate
 
 
 
  // this contains unit tests for checking crucial APIs that must work for the script to work correctly
    ///////////////////////////////////////
  // for the time being, most of these are stubs waiting to be filled in
    // supported  script engines:
  // for a working example, refer to the JSON test at the end
    ///////////////////////////////////////
  // TODO: add jQuery tests
 
  APITests: [
    GreaseMonkey: {
    {name:'download', test: function(recipient) {recipient(true);}  },
        // TODO: move environment specific initialization code here
    {name:'make_doc', test: function(recipient) { recipient(true);}  },
        init: function () {
    {name:'eval_xpath', test: function(recipient) { recipient(true);} },
            // Check if Greasemonkey/Tampermonkey is available
    {name:'JSON de/serialization', test: function(recipient) {
            try {
      //console.log("running json test");
                // TODO: add version check for clipboard API and check for TamperMonkey/Scriptish equivalents?
      var identifier = 'unit_tests.json_serialization';
                GM_addStyle(GM_getResourceText('jQUI_CSS'));
      var hash1 = {x:1,y:2,z:3};
            } // try
      Host.set_persistent(identifier, hash1, true);
            catch (error) {
      var hash2 = Host.get_persistent(identifier,null,true);
                console.log(
     
                    'Could not add style or determine script version'
      recipient(JSON.stringify(hash1) === JSON.stringify(hash2));
                );
    } // callback
            } // catch
    },
 
   
            var commands = [{
    // downloads a posting and tries to transform it to 3rd person speech ...
                name: 'Setup quotes',
    // TODO: add another test to check forum postings
                callback: setupDialog,
    {name:'text/speech transformation', test: function(recipient) {
                hook: 'S'
   
            }, {
    // the posting we want to download
                name: 'Check quotes',
    var url='https://sourceforge.net/p/flightgear/mailman/message/35066974/';
                callback: selfCheckDialog,
    Host.downloadPosting(url, function (result) {
                hook: 'C'
     
            }];
    // only process the first sentence by using comma/dot as delimiter
 
    var firstSentence = result.content.substring(result.content.indexOf(',')+1, result.content.indexOf('.'));
            for (let c of commands) {
     
                this.registerConfigurationOption(c.name, c.callback, c.hook);
    var transformed = transformSpeech(firstSentence, result.author, null, speechTransformations );
            }
    console.log("3rd person speech transformation:\n"+transformed); 
 
   
        }, // init()
    recipient(true);
 
    }); // downloadPosting()
        getScriptVersion: function () {
       
            return GM_info.script.version;
  }// test()
        },
    }, // end of speech transform test
 
    {
        dbLog: function (message) {
      name:"download $FG_ROOT/options.xml", test: function(recipient) {
            if (Boolean(DEBUG)) {
        downloadOptionsXML();
                console.log('Instant cquotes:' + message);
        recipient(true);
            }
      } // test
        }, // dbLog()
    }
 
   
        registerConfigurationOption: function (name, callback, hook) {
  ], // end of APITests
            // https://wiki.greasespot.net/GM_registerMenuCommand
 
            // https://wiki.greasespot.net/Greasemonkey_Manual:Monkey_Menu#The_Menu
  runAPITests: function(host, recipient) {
            GM_registerMenuCommand(name, callback, hook);
    console.log("Running API tests");
        }, //registerMenuCommand()
    for(let test of Environment.APITests ) {
 
      //var test = Environment.APITests[t];
        registerTrigger: function () {
      // invoke the callback passed, with the hash containing the test specs, so that the console/log or a div can be updated showing the test results
 
     
            // TODO: we can use the following callback non-interactively, i.e. to trigger background tasks
      recipient.call(undefined, test);
            // http://javascript.info/tutorial/onload-ondomcontentloaded
     
            document.addEventListener("DOMContentLoaded", function (
    } // foreach test
                event) {
  }, // runAPITests
                console.log(
 
                    "Instant Cquotes: DOM fully loaded and parsed"
  /*
                );
  * ===================================================================================================================================================
            });
  *
 
  */
            window.addEventListener('load', init); // page fully loaded
 
            Host.dbLog('Instant Cquotes: page load handler registered');
  // NOTE: This mode/environment is WIP and highly experimental ...
 
  // To see this working, you need to package up the whole file as a firefox xpi using "jpm xpi"
 
  // and then start the whole thing via "jpm run", to do that, you also need a matching package.json (i.e. via jpm init)  
            // Initialize (matching page loaded)
  // ALSO: you will have to explicitly install any dependencies using jpm
            function init() {
  FirefoxAddon: {
                console.log(
  init: function() {
                    'Instant Cquotes: page load handler invoked');
console.log("Firefox addon mode ...");
                var profile = getProfile();
  },
 
getScriptVersion: function() {
                Host.dbLog("Profile type is:" + profile.type);
return '0.36'; // FIXME
 
},
                // Dispatch to correct event handler (depending on website/URL)
dbLog: function(msg) {
                // TODO: this stuff could/should be moved into the config hash itself
console.log(msg);
 
},
                if (profile.type == 'wiki') {
addEventListener: function(ev, cb) {
                    profile.event_handler(); // just for testing
                    return;
                }
 
                Host.dbLog('using default mode');
                document.onmouseup = instantCquote;
                // HACK: preparations for moving the the event/handler logic also into the profile hash, so that the wiki (edit mode) can be handled equally
                //eval(profile.event+"=instantCquote");
 
            } // init()
 
        }, // registerTrigger
 
 
        download: function (url, callback, method = 'GET') {
            // http://wiki.greasespot.net/GM_xmlhttpRequest
            try {
                GM_xmlhttpRequest({
                    method: method,
                    url: url,
                    onload: callback
                });
            } catch (e) {
                console.log("download did not work");
            }
        }, // download()
 
        // is only intended to work with archives supported by the  hash
        downloadPosting: function (url, EventHandler) {
 
            Host.download(url, function (response) {
                var profile = getProfile(url);
                var blob = response.responseText;
                var doc = Host.make_doc(blob, 'text/html');
                var result = {}; // hash to be returned
 
                [].forEach.call(['author', 'date', 'title',
                    'content'
                ], function (field) {
                    var xpath_query = '//' + profile[
                        field].xpath;
                    try {
                        var value = Host.eval_xpath(doc,
                            xpath_query).stringValue;
                        //UI.alert("extracted field value:"+value);
 
                        // now apply all transformations, if any
                        value = applyTransformations(
                            value, profile[field].transform
                        );
 
                        result[field] = value; // store the extracted/transormed value in the hash that we pass on
                    } // try
                    catch (e) {
                        UI.alert(
                            "downloadPosting failed:\n" +
                            e.message);
                    } // catch
                }); // forEach field
 
                EventHandler(result); // pass the result to the handler
            }); // call to Host.download()
 
        }, // downloadPosting()
 
        // TODO: add makeAJAXCall, and makeWikiCall here
 
 
        // turn a string/text blob into a DOM tree that can be queried (e.g. for xpath expressions)
        // FIXME: this is browser specific not GM specific ...
        make_doc: function (text, type = 'text/html') {
            // to support other browsers, see: https://developer.mozilla.org/en/docs/Web/API/DOMParser
            return new DOMParser().parseFromString(text, type);
        }, // make DOM document
 
        // xpath handling may be handled separately depending on browser/platform, so better encapsulate this
        // FIXME: this is browser specific not GM specific ...
        eval_xpath: function (doc, xpath, type = XPathResult.STRING_TYPE) {
            return doc.evaluate(xpath, doc, null, type, null);
        }, // eval_xpath
 
        set_persistent: function (key, value, json = false) {
            // transparently stringify to json
            if (json) {
                // http://stackoverflow.com/questions/16682150/store-a-persistent-list-between-sessions
                value = JSON.stringify(value);
            }


require("sdk/tabs").on("ready", logURL);
            // https://wiki.greasespot.net/GM_setValue
function logURL(tab) {
            GM_setValue(key, value);
  console.log("URL loaded:" + tab.url);
            //UI.alert('Saved value for key\n'+key+':'+value);
}
         }, // set_persistent
},
   
registerConfigurationOption: function(name, callback, hook) {
// https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Add_a_Context_Menu_Item
console.log("config menu support n/a in firefox mode");
// https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Using_third-party_modules_%28jpm%29 
var menuitems = require("menuitem");
var menuitem = menuitems.Menuitem({
  id: "clickme",
  menuid: "menu_ToolsPopup",
  label: name,
  onCommand: function() {
    console.log("menuitem clicked:");
    callback();
  },
  insertbefore: "menu_pageInfo"
});
},
   
registerTrigger: function() {
// https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Add_a_Context_Menu_Item
// https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/context-menu#Item%28options%29
var contextMenu = require("sdk/context-menu");
var menuItem = contextMenu.Item({
  label: "Instant Cquote",
  context: contextMenu.SelectionContext(),
      // https://developer.mozilla.org/en/Add-ons/SDK/Guides/Two_Types_of_Scripts
      // https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts
  contentScript: 'self.on("click", function () {' +
                '  var text = window.getSelection().toString();' +
                ' self.postMessage(text);' +
                '});',
  onMessage: function (selectionText) {
    console.log(selectionText);
         instantCquote(selectionText);
  }
});
 
    // 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
   
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) {
console.log("firefox persistence stubs not yet filled in !");
},
   
 
set_clipboard: function(content) {
// 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
 
  // placeholder for now ...
  Android: {
    // NOP
  }, // Android


 
        get_persistent: function (key, default_value, json = false) {
  ///////////////////////////////////////
            // https://wiki.greasespot.net/GM_getValue
  // supported  script engines:
  ///////////////////////////////////////
 
  GreaseMonkey: {
  // TODO: move environment specific initialization code here 
  init: function() {
  // Check if Greasemonkey/Tampermonkey is available
  try {
  // TODO: add version check for clipboard API and check for TamperMonkey/Scriptish equivalents ?
  GM_addStyle(GM_getResourceText('jQUI_CSS'));
  } // try
  catch (error) {
  console.log('Could not add style or determine script version');
  } // catch


  var commands = [
            var value = GM_getValue(key, default_value);
  {name:'Setup quotes',callback:setupDialog, hook:'S' },
            // transparently support JSON: http://stackoverflow.com/questions/16682150/store-a-persistent-list-between-sessions
  {name:'Check quotes',callback:selfCheckDialog, hook:'C' }
            if (json) {
  ];
                value = JSON.parse(value) || {};
     
            }
  for (let c of commands ) {
            return value;
  this.registerConfigurationOption(c.name, c.callback, c.hook);
        }, // get_persistent
  } 
   
  }, // init()
   
  getScriptVersion: function() {
  return GM_info.script.version; 
  },
   
  dbLog: function (message) {
  if (Boolean(DEBUG)) {
    console.log('Instant cquotes:' + message);
  }
  }, // dbLog()
   
  registerConfigurationOption: function(name,callback,hook) {
  // https://wiki.greasespot.net/GM_registerMenuCommand
  // https://wiki.greasespot.net/Greasemonkey_Manual:Monkey_Menu#The_Menu
    GM_registerMenuCommand(name, callback, hook);
  }, //registerMenuCommand()
   
  registerTrigger: function() {
   
    // TODO: we can use the following callback non-interactively, i.e. to trigger background tasks
// http://javascript.info/tutorial/onload-ondomcontentloaded
document.addEventListener("DOMContentLoaded", function(event) {
    console.log("Instant Cquotes: DOM fully loaded and parsed");
});


window.addEventListener('load', init); // page fully loaded
        setClipboard: function (msg) {
Host.dbLog('Instant Cquotes: page load handler registered');
            // this being a greasemonkey user-script, we are not
            // subject to usual browser restrictions
            // http://wiki.greasespot.net/GM_setClipboard
            GM_setClipboard(msg);
        }, // setClipboard()


   
        getTemplate: function () {
    // Initialize (matching page loaded)
function init() {
  console.log('Instant Cquotes: page load handler invoked');
  var profile = getProfile();
 
  Host.dbLog("Profile type is:"+profile.type);
 
  // Dispatch to correct event handler (depending on website/URL)
  // TODO: this stuff could/should be moved into the config hash itself
 
  if (profile.type=='wiki') {
    profile.event_handler(); // just for testing
    return;
  }
 
    Host.dbLog('using default mode');
    document.onmouseup = instantCquote;
    // HACK: preparations for moving the the event/handler logic also into the profile hash, so that the wiki (edit mode) can be handled equally
    //eval(profile.event+"=instantCquote");
   
} // init()


                // hard-coded default template
                var template = '$CONTENT<ref>{{cite web\n' +
                    '  |url    =  $URL \n' +
                    '  |title  =  <nowiki> $TITLE </nowiki> \n' +
                    '  |author =  <nowiki> $AUTHOR </nowiki> \n' +
                    '  |date  =  $DATE \n' +
                    '  |added  =  $ADDED \n' +
                    '  |script_version = $SCRIPT_VERSION \n' +
                    '  }}</ref>\n';


   
                // return a saved template if found, fall back to hard-coded one above otherwise
  }, // registerTrigger
                return Host.get_persistent('default_template', template);


   
            } // getTemplate
  download: function (url, callback, method='GET') {
  // http://wiki.greasespot.net/GM_xmlhttpRequest
    try {
  GM_xmlhttpRequest({
    method: method,
    url: url,
    onload: callback
  });
    }catch(e) {
      console.log("download did not work");
    }
  }, // download()
   
    // is only intended to work with archives supported by the  hash
    downloadPosting: function (url, EventHandler) {
     
    Host.download(url, function (response) {
    var profile = getProfile(url);
    var blob = response.responseText;
    var doc = Host.make_doc(blob,'text/html');
    var result = {}; // hash to be returned
   
    [].forEach.call(['author','date','title','content'], function(field) {
      var xpath_query = '//' + profile[field].xpath;
      try {
      var value = Host.eval_xpath(doc, xpath_query).stringValue;
      //UI.alert("extracted field value:"+value);
       
        // now apply all transformations, if any
      value = applyTransformations(value, profile[field].transform );
       
      result[field]=value; // store the extracted/transormed value in the hash that we pass on
      } // try
      catch(e) {
        UI.alert("downloadPosting failed:\n"+ e.message);
      } // catch
    }); // forEach field
   
    EventHandler(result); // pass the result to the handler
    }); // call to Host.download()
     
    }, // downloadPosting()
   
    // TODO: add makeAJAXCall, and makeWikiCall here


 
    // turn a string/text blob into a DOM tree that can be queried (e.g. for xpath expressions)
    // FIXME: this is browser specific not GM specific ...
    make_doc: function(text, type='text/html') {
      // to support other browsers, see: https://developer.mozilla.org/en/docs/Web/API/DOMParser
      return new DOMParser().parseFromString(text,type);
    }, // make DOM document
   
    // xpath handling may be handled separately depending on browser/platform, so better encapsulate this
    // FIXME: this is browser specific not GM specific ...
    eval_xpath: function(doc, xpath, type=XPathResult.STRING_TYPE) {
      return doc.evaluate(xpath, doc, null, type, null);
    }, // eval_xpath
   
    set_persistent: function(key, value, json=false)
    {
      // transparently stringify to json
      if(json) {
        // http://stackoverflow.com/questions/16682150/store-a-persistent-list-between-sessions
        value = JSON.stringify (value);
      }
     
      // https://wiki.greasespot.net/GM_setValue
      GM_setValue(key, value);
      //UI.alert('Saved value for key\n'+key+':'+value);
    }, // set_persistent
   
    get_persistent: function(key, default_value, json=false) {
    // https://wiki.greasespot.net/GM_getValue
   
      var value=GM_getValue(key, default_value);
      // transparently support JSON: http://stackoverflow.com/questions/16682150/store-a-persistent-list-between-sessions
      if(json) {
        value = JSON.parse (value)  ||  {};
      }
      return value;
    }, // get_persistent


  setClipboard: function(msg) {
     } // end of GreaseMonkey environment, add other environments below
  // this being a greasemonkey user-script, we are not
  // subject to usual browser restrictions
  // http://wiki.greasespot.net/GM_setClipboard
  GM_setClipboard(msg);
  }, // setClipboard()
   
     getTemplate: function() {
   
    // hard-coded default template
    var template = '$CONTENT<ref>{{cite web\n' +
  '  |url    =  $URL \n' +
  '  |title  =  <nowiki> $TITLE </nowiki> \n' +
  '  |author =  <nowiki> $AUTHOR </nowiki> \n' +
  '  |date  =  $DATE \n' +
  '  |added  =  $ADDED \n' +
  '  |script_version = $SCRIPT_VERSION \n' +
  '  }}</ref>\n';
   
    // return a saved template if found, fall back to hard-coded one above otherwise
    return Host.get_persistent('default_template', template);
   
  } // getTemplate


   
  } // end of GreaseMonkey environment, add other environments below
 
}; // Environment hash - intended to help encapsulate host specific stuff (APIs)
}; // Environment hash - intended to help encapsulate host specific stuff (APIs)


Line 961: Line 1,022:




// move DEBUG handling to a persistent configuration flag so that we can configure this using a jQuery dialog (defaulted to false)
// move DEBUG handling to a persistent configuration flag so that we can
// configure this using a jQuery dialog (defaulted to false)
// TODO: move DEBUG variable to Environment hash / init() routine
// TODO: move DEBUG variable to Environment hash / init() routine
var DEBUG = Host.get_persistent('debug_mode_enabled', false);
var DEBUG = Host.get_persistent('debug_mode_enabled', false);
Host.dbLog("Debug mode is:"+DEBUG);
Host.dbLog("Debug mode is:" + DEBUG);
 
function DEBUG_mode() {
function DEBUG_mode() {
  // reset script invocation counter for testing purposes
    // reset script invocation counter for testing purposes
  Host.dbLog('Resetting script invocation counter');
    Host.dbLog('Resetting script invocation counter');
  Host.set_persistent(GM_info.script.version, 0);
    Host.set_persistent(GM_info.script.version, 0);
}
}




if (DEBUG)
if (DEBUG)
DEBUG_mode();
    DEBUG_mode();


// hash with supported websites/URLs,  includes xpath and regex expressions to extract certain fields, and a vector with optional transformations for post-processing each field
// hash with supported websites/URLs,  includes xpath and regex expressions to
// extract certain fields, and a vector with optional transformations for
// post-processing each field


var CONFIG = {
var CONFIG = {
  // WIP: the first entry is special, i.e. it's not an actual list archive (source), but only added here so that the same script can be used
    // WIP: the first entry is special, i.e. it's not an actual list archive (source), but only added here so that the same script can be used
  // for editing the FlightGear wiki
    // for editing the FlightGear wiki
 
 
  'FlightGear.wiki': {
    'FlightGear.wiki': {
    type: 'wiki',
        type: 'wiki',
    enabled: false,
        enabled: false,
    event: 'document.onmouseup', // when to invoke the event handler
        event: 'document.onmouseup', // when to invoke the event handler
    // TODO: move downloadWatchlist() etc here
        // TODO: move downloadWatchlist() etc here
    event_handler: function () {
        event_handler: function () {
      console.log('FlightGear wiki handler active (waiting to be populated)');
            console.log(
      // this is where the logic for a wiki mode can be added over time (for now, it's a NOP)
                'FlightGear wiki handler active (waiting to be populated)'
   
            );
    //for each supported mode, invoke the trigger and call the corresponding handler
            // this is where the logic for a wiki mode can be added over time (for now, it's a NOP)
    [].forEach.call(CONFIG['FlightGear.wiki'].modes, function(mode) {
 
      //dbLog("Checking trigger:"+mode.name);
            //for each supported mode, invoke the trigger and call the corresponding handler
      if(mode.trigger() ) {
            [].forEach.call(CONFIG['FlightGear.wiki'].modes, function (
        mode.handler();
                mode) {
      }
                //dbLog("Checking trigger:"+mode.name);
    });
                if (mode.trigger()) {
     
                    mode.handler();
    }, // the event handler to be invoked
                }
    url_reg: '^(http|https)://wiki.flightgear.org', // ignore for now: not currently used by the wiki mode
            });
   
 
    modes: [
        }, // the event handler to be invoked
      { name:'process-editSections',
        url_reg: '^(http|https)://wiki.flightgear.org', // ignore for now: not currently used by the wiki mode
        trigger: function() {return true;}, // match URL regex - return true for always match
 
     
        modes: [{
        // the code implementing the mode
                    name: 'process-editSections',
        handler: function() {
                    trigger: function () {
               
                        return true;
    var editSections = document.getElementsByClassName('mw-editsection');
                    }, // match URL regex - return true for always match
    console.log('FlightGear wiki article, number of edit sections: '+editSections.length);
 
 
                    // the code implementing the mode
    // for now, just rewrite edit sections and add a note to them
                    handler: function () {
 
 
    [].forEach.call(editSections, function (sec) {
                            var editSections = document.getElementsByClassName(
      sec.appendChild(
                                'mw-editsection');
        document.createTextNode(' (instant-cquotes is lurking) ')
                            console.log(
      );
                                'FlightGear wiki article, number of edit sections: ' +
    }); //forEach section
                                editSections.length);
        } // handler
 
     
                            // for now, just rewrite edit sections and add a note to them
     
 
      } // process-editSections
                            [].forEach.call(editSections, function (sec) {
      // TODO: add other wiki modes below  
                                sec.appendChild(
     
                                    document.createTextNode(
    ] // modes
                                        ' (instant-cquotes is lurking) '
      
                                    )
  }, // end of wiki profile
                                );
 
                            }); //forEach section
  'Sourceforge Mailing list': {
                        } // handler
    enabled: true,
 
    type: 'archive',
 
    event: 'document.onmouseup', // when to invoke the event handler
                } // process-editSections
    event_handler: instantCquote, // the event handler to be invoked
                // TODO: add other wiki modes below
    url_reg: '^(http|https)://sourceforge.net/p/flightgear/mailman/.*/',
 
    content: {
            ] // modes
      xpath: 'tbody/tr[2]/td/pre/text()', // NOTE this is only used by the downloadPosting  helper to retrieve the posting without having a selection (TODO:add content xpath to forum hash)
 
      selection: getSelectedText,
     }, // end of wiki profile
      idStyle: /msg[0-9]{8}/,
 
      parentTag: [
    'Sourceforge Mailing list': {
        'tagName',
        enabled: true,
        'PRE'
        type: 'archive',
      ],
        event: 'document.onmouseup', // when to invoke the event handler
      transform: [],
        event_handler: instantCquote, // the event handler to be invoked
    }, // content recipe
        url_reg: '^(http|https)://sourceforge.net/p/flightgear/mailman/.*/',
    // vector with tests to be executed for sanity checks (unit testing)
        content: {
    tests: [
            xpath: 'tbody/tr[2]/td/pre/text()', // NOTE this is only used by the downloadPosting  helper to retrieve the posting without having a selection (TODO:add content xpath to forum hash)
      {
            selection: getSelectedText,
        url: 'https://sourceforge.net/p/flightgear/mailman/message/35059454/',
            idStyle: /msg[0-9]{8}/,
        author: 'Erik Hofman',
            parentTag: [
        date: 'May 3rd, 2016', // NOTE: using the transformed date here  
                'tagName',
        title: 'Re: [Flightgear-devel] Auto altimeter setting at startup (?)'
                'PRE'
      },
            ],
      {
            transform: [],
        url: 'https://sourceforge.net/p/flightgear/mailman/message/35059961/',
        }, // content recipe
        author: 'Ludovic Brenta',
        // vector with tests to be executed for sanity checks (unit testing)
        date: 'May 3rd, 2016',
        tests: [{
        title: 'Re: [Flightgear-devel] dual-control-tools and the limit on packet size'
                url: 'https://sourceforge.net/p/flightgear/mailman/message/35059454/',
      },
                author: 'Erik Hofman',
      {
                date: 'May 3rd, 2016', // NOTE: using the transformed date here
        url: 'https://sourceforge.net/p/flightgear/mailman/message/20014126/',
                title: 'Re: [Flightgear-devel] Auto altimeter setting at startup (?)'
        author: 'Tim Moore',
            }, {
        date: 'Aug 4th, 2008',
                url: 'https://sourceforge.net/p/flightgear/mailman/message/35059961/',
        title: 'Re: [Flightgear-devel] Cockpit displays (rendering, modelling)'
                author: 'Ludovic Brenta',
      },
                date: 'May 3rd, 2016',
      {
                title: 'Re: [Flightgear-devel] dual-control-tools and the limit on packet size'
        url: 'https://sourceforge.net/p/flightgear/mailman/message/23518343/',
            }, {
        author: 'Tim Moore',
                url: 'https://sourceforge.net/p/flightgear/mailman/message/20014126/',
        date: 'Sep 10th, 2009',
                author: 'Tim Moore',
        title: '[Flightgear-devel] Atmosphere patch from John Denker'
                date: 'Aug 4th, 2008',
      } // add other tests below
                title: 'Re: [Flightgear-devel] Cockpit displays (rendering, modelling)'
            }, {
                url: 'https://sourceforge.net/p/flightgear/mailman/message/23518343/',
                author: 'Tim Moore',
                date: 'Sep 10th, 2009',
                title: '[Flightgear-devel] Atmosphere patch from John Denker'
            } // add other tests below


    ], // end of vector with self-tests
        ], // end of vector with self-tests
    // regex/xpath and transformations for extracting various required fields
        // regex/xpath and transformations for extracting various required fields
    author: {
        author: {
      xpath: 'tbody/tr[1]/td/div/small/text()',
            xpath: 'tbody/tr[1]/td/div/small/text()',
      transform: [extract(/From: (.*) <.*@.*>/)]
            transform: [extract(/From: (.*) <.*@.*>/)]
    },
        },
    title: {
        title: {
      xpath: 'tbody/tr[1]/td/div/div[1]/b/a/text()',
            xpath: 'tbody/tr[1]/td/div/div[1]/b/a/text()',
      transform:[]
            transform: []
    },
        },
    date: {
        date: {
      xpath: 'tbody/tr[1]/td/div/small/text()',
            xpath: 'tbody/tr[1]/td/div/small/text()',
      transform: [extract(/- (.*-.*-.*) /)]
            transform: [extract(/- (.*-.*-.*) /)]
    },
        },
    url: {
        url: {
      xpath: 'tbody/tr[1]/td/div/div[1]/b/a/@href',
            xpath: 'tbody/tr[1]/td/div/div[1]/b/a/@href',
      transform: [prepend('https://sourceforge.net')]
            transform: [prepend('https://sourceforge.net')]
    }
        }
  }, // end of mailing list profile
    }, // end of mailing list profile
  // next website/URL (forum)
    // next website/URL (forum)
  'FlightGear forum': {
    'FlightGear forum': {
    enabled: true,
        enabled: true,
    type: 'archive',
        type: 'archive',
    event: 'document.onmouseup', // when to invoke the event handler (not used atm)
        event: 'document.onmouseup', // when to invoke the event handler (not used atm)
    event_handler: null, // the event handler to be invoked (not used atm)
        event_handler: null, // the event handler to be invoked (not used atm)
    url_reg: /https:\/\/forum\.flightgear\.org\/.*/,
        url_reg: /https:\/\/forum\.flightgear\.org\/.*/,
    content: {
        content: {
      xpath: '', //TODO: this must be added for downloadPosting() to work, or it cannot extract contents
            xpath: '', //TODO: this must be added for downloadPosting() to work, or it cannot extract contents
      selection: getSelectedHtml,
            selection: getSelectedHtml,
      idStyle: /p[0-9]{6}/,
            idStyle: /p[0-9]{6}/,
      parentTag: [
            parentTag: [
        'className',
                'className',
        'content',
                'content',
        'postbody'
                'postbody'
      ],
            ],
      transform: [
            transform: [
        removeComments,
                removeComments,
        forum_quote2cquote,
                forum_quote2cquote,
        forum_smilies2text,
                forum_smilies2text,
        forum_fontstyle2wikistyle,
                forum_fontstyle2wikistyle,
        forum_code2syntaxhighlight,
                forum_code2syntaxhighlight,
        img2link,
                img2link,
        a2wikilink,
                a2wikilink,
        vid2wiki,
                vid2wiki,
        list2wiki,
                list2wiki,
        forum_br2newline
                forum_br2newline
      ]
            ]
    },
        },
    // vector with tests to be executed for sanity checks (unit testing)
        // vector with tests to be executed for sanity checks (unit testing)
    // postings will be downloaded using the URL specified, and then the author/title  
        // postings will be downloaded using the URL specified, and then the author/title
    // fields extracted using the outer regex and matched against what is expected
        // fields extracted using the outer regex and matched against what is expected
    // NOTE: forum postings can be edited, so that these tests would fail - thus, it makes sense to pick locked topics/postings for such tests
        // NOTE: forum postings can be edited, so that these tests would fail - thus, it makes sense to pick locked topics/postings for such tests
    tests: [
        tests: [{
      {
                url: 'https://forum.flightgear.org/viewtopic.php?f=18&p=284108#p284108',
        url: 'https://forum.flightgear.org/viewtopic.php?f=18&p=284108#p284108',
                author: 'mickybadia',
        author: 'mickybadia',
                date: 'May 3rd, 2016',
        date: 'May 3rd, 2016',
                title: 'OSM still PNG maps'
        title: 'OSM still PNG maps'
            }, {
      },
                url: 'https://forum.flightgear.org/viewtopic.php?f=19&p=284120#p284120',
      {
                author: 'Thorsten',
        url: 'https://forum.flightgear.org/viewtopic.php?f=19&p=284120#p284120',
                date: 'May 3rd, 2016',
        author: 'Thorsten',
                title: 'Re: FlightGear\'s Screenshot Of The Month MAY 2016'
        date: 'May 3rd, 2016',
            }, {
        title: 'Re: FlightGear\'s Screenshot Of The Month MAY 2016'
                url: 'https://forum.flightgear.org/viewtopic.php?f=71&t=29279&p=283455#p283446',
      },
                author: 'Hooray',
      {
                date: 'Apr 25th, 2016',
        url: 'https://forum.flightgear.org/viewtopic.php?f=71&t=29279&p=283455#p283446',
                title: 'Re: Best way to learn Canvas?'
        author: 'Hooray',
            }, {
        date: 'Apr 25th, 2016',
                url: 'https://forum.flightgear.org/viewtopic.php?f=4&t=1460&p=283994#p283994',
        title: 'Re: Best way to learn Canvas?'
                author: 'bugman',
      },
                date: 'May 2nd, 2016',
      {
                title: 'Re: eurofighter typhoon'
        url: 'https://forum.flightgear.org/viewtopic.php?f=4&t=1460&p=283994#p283994',
            } // add other tests below
        author: 'bugman',
        date: 'May 2nd, 2016',
        title: 'Re: eurofighter typhoon'
      } // add other tests below


    ], // end of vector with self-tests
        ], // end of vector with self-tests
    author: {
        author: {
      xpath: 'div/div[1]/p/strong/a/text()',
            xpath: 'div/div[1]/p/strong/a/text()',
      transform: [] // no transformations applied
            transform: [] // no transformations applied
    },
        },
    title: {
        title: {
      xpath: 'div/div[1]/h3/a/text()',
            xpath: 'div/div[1]/h3/a/text()',
      transform: [] // no transformations applied
            transform: [] // no transformations applied
    },
        },
    date: {
        date: {
      xpath: 'div/div[1]/p/text()[2]',
            xpath: 'div/div[1]/p/text()[2]',
      transform: [extract(/» (.*?[0-9]{4})/)]
            transform: [extract(/» (.*?[0-9]{4})/)]
    },
        },
    url: {
        url: {
      xpath: 'div/div[1]/p/a/@href',
            xpath: 'div/div[1]/p/a/@href',
      transform: [
            transform: [
        extract(/\.(.*)/),
                    extract(/\.(.*)/),
        prepend('https://forum.flightgear.org')
                    prepend('https://forum.flightgear.org')
      ] // transform vector
                ] // transform vector
    } // url
        } // url
  } // forum  
    } // forum
}; // CONFIG has
}; // CONFIG has


// hash to map URLs (wiki article, issue tracker, sourceforge link, forum thread etc) to existing wiki templates
// hash to map URLs (wiki article, issue tracker, sourceforge link, forum thread etc) to existing wiki templates
var MatchURL2Templates = [
var MatchURL2Templates = [
  // placeholder for now
    // placeholder for now
{
    {
  name: 'rewrite sourceforge code links',
        name: 'rewrite sourceforge code links',
  url_reg: '',
        url_reg: '',
  handler: function() {
        handler: function () {
 
 
} // handler
            } // handler
 
 
} // add other templates below
    } // add other templates below
 
 
]; // MatchURL2Templates
]; // MatchURL2Templates


Line 1,191: Line 1,258:
// output methods (alert and jQuery for now)
// output methods (alert and jQuery for now)
var OUTPUT = {
var OUTPUT = {
  // Shows a window.prompt() message box
    // Shows a window.prompt() message box
  msgbox: function (msg) {
    msgbox: function (msg) {
    UI.prompt('Copy to clipboard ' + Host.getScriptVersion(), msg);
        UI.prompt('Copy to clipboard ' + Host.getScriptVersion(), msg);
    Host.setClipboard(msg);
        Host.setClipboard(msg);
  }, // msgbox
    }, // msgbox
 
 
  // this is currently work-in-progress, and will need to be refactored sooner or later
    // this is currently work-in-progress, and will need to be refactored sooner or later
  // for now, functionality matters more than elegant design/code :)
    // for now, functionality matters more than elegant design/code :)
  jQueryTabbed: function(msg, original) {
    jQueryTabbed: function (msg, original) {
  // FIXME: using backtics here makes the whole thing require ES6  ....
            // FIXME: using backtics here makes the whole thing require ES6  ....
  var markup = $(`<div id="tabs">
            var markup = $(
                `<div id="tabs">
   <ul>
   <ul>
     <li><a href="#selection">Selection</a></li>
     <li><a href="#selection">Selection</a></li>
Line 1,245: Line 1,313:
     <optgroup id="develop" label="Development"/>
     <optgroup id="develop" label="Development"/>
     <optgroup id="release" label="Release"/>
     <optgroup id="release" label="Release"/>
     <!-- the watchlist is retrieved dynamically, so omit it here  
     <!-- the watchlist is retrieved dynamically, so omit it here
     <optgroup id="watchlist" label="Watchlist"/>
     <optgroup id="watchlist" label="Watchlist"/>
     -->
     -->
Line 1,296: Line 1,364:
   <tbody>
   <tbody>
   </tbody>
   </tbody>
</table>  
</table>


   <!--
   <!--
Line 1,310: Line 1,378:
   <div id="about">show some  script related information here
   <div id="about">show some  script related information here
   </div>
   </div>
</div>`); // tabs div
</div>`
   
            ); // tabs div
  var evolve_regex = $('div#development button#evolve_regex', markup);
 
  evolve_regex.click(function() {
            var evolve_regex = $('div#development button#evolve_regex',
    //alert("Evolve regex");
                markup);
    evolve_expression_test();
            evolve_regex.click(function () {
  });
                //alert("Evolve regex");
   
                evolve_expression_test();
  var test_perceptron = $('div#development button#test_perceptron', markup);
            });
  test_perceptron.click(function() {
 
    alert("Test perceptron");
            var test_perceptron = $(
  });
                'div#development button#test_perceptron', markup);
 
            test_perceptron.click(function () {
   
                alert("Test perceptron");
    // add dynamic elements to each tab
            });
   
 
  // NOTE: this affects all template selectors, on all tabs
 
  $('select#template_select', markup).change(function() {
            // add dynamic elements to each tab
    UI.alert("Sorry, templates are not yet fully implemented (WIP)");
 
  });
            // NOTE: this affects all template selectors, on all tabs
   
            $('select#template_select', markup).change(function () {
  var help = $('#helpButton', markup);
                UI.alert(
  help.button();
                    "Sorry, templates are not yet fully implemented (WIP)"
  help.click(function() {
                );
    window.open("http://wiki.flightgear.org/FlightGear_wiki:Instant-Cquotes");
            });
  });
 
   
            var help = $('#helpButton', markup);
  // rows="10"cols="80" style=" width: 420px; height: 350px"
            help.button();
  var textarea = $('<textarea id="quotedtext" rows="20" cols="70"/>');
            help.click(function () {
  textarea.val(msg);
                window.open(
  $('#selection #content', markup).append(textarea);
                    "http://wiki.flightgear.org/FlightGear_wiki:Instant-Cquotes"
 
                );
  var templateArea = $('<textarea id="template-edit" rows="20" cols="70"/>');
            });
  templateArea.val( Host.getTemplate() );
 
  $('div#templates div#template_area', markup).append(templateArea);
            // rows="10"cols="80" style=" width: 420px; height: 350px"
 
            var textarea = $(
  //$('#templates', markup).append($('<button>'));
                '<textarea id="quotedtext" rows="20" cols="70"/>');
    $('div#templates div#template_controls button#template_save',markup).button().click(function() {
            textarea.val(msg);
      //UI.alert("Saving template:\n"+templateArea.val() );
            $('#selection #content', markup).append(textarea);
     
 
      Host.set_persistent('default_template',templateArea.val() );
            var templateArea = $(
    }); // save template
                '<textarea id="template-edit" rows="20" cols="70"/>');
   
            templateArea.val(Host.getTemplate());
  // TODO: Currently, this is hard-coded, but should be made customizable via the "articles" tab at some point ...
            $('div#templates div#template_area', markup).append(
  var articles = [
                templateArea);
    // NOTE: category must match an existing <optgroup> above, title must match an existing wiki article
 
    {category:'support', name:'Frequently asked questions', url:''},
            //$('#templates', markup).append($('<button>'));
    {category:'support', name:'Asking for help', url:''},
            $('div#templates div#template_controls button#template_save',
    {category:'news', name:'Next newsletter', url:''},
                markup).button().click(function () {
    {category:'news', name:'Next changelog', url:''},
                //UI.alert("Saving template:\n"+templateArea.val() );
    {category:'release', name:'Release plan/Lessons learned', url:''}, // TODO: use wikimedia template
 
    {category:'develop', name:'Nasal library', url:''},
                Host.set_persistent('default_template',
    {category:'develop', name:'Canvas Snippets', url:''},
                    templateArea.val());
   
            }); // save template
  ];
 
   
            // TODO: Currently, this is hard-coded, but should be made customizable via the "articles" tab at some point ...
    // TODO: this should be moved elsewhere
            var articles = [
    function updateArticleList(selector) {
                // NOTE: category must match an existing <optgroup> above, title must match an existing wiki article
    $.each(articles, function (i, article) {
                {
    $(selector+ ' optgroup#'+article.category, markup).append($('<option>', {  
                    category: 'support',
        value: article.name, // FIXME: just a placeholder for now
                    name: 'Frequently asked questions',
        text : article.name  
                    url: ''
    })); //append option
                }, {
  }); // foreach
                    category: 'support',
    } // updateArticleList
                    name: 'Asking for help',
   
                    url: ''
    // add the article list to the corresponding dropdown menus
                }, {
    updateArticleList('select#article_select');
                    category: 'news',
       
                    name: 'Next newsletter',
    // populate watchlist (prototype for now)
                    url: ''
    // TODO: generalize & refactor: url, format
                }, {
     
                    category: 'news',
    // https://www.mediawiki.org/wiki/API:Watchlist
                    name: 'Next changelog',
    // http://wiki.flightgear.org/api.php?action=query&list=watchlist
                    url: ''
      var watchlist_url = 'http://wiki.flightgear.org/api.php?action=query&list=watchlist&format=json';
                }, {
      Host.download(watchlist_url, function(response) {
                    category: 'release',
        try {
                    name: 'Release plan/Lessons learned',
      var watchlist = JSON.parse(response.responseText);
                    url: ''
           
                }, // TODO: use wikimedia template
      //$('div#options select#section_select', markup).empty(); // delete all sections
                {
     
                    category: 'develop',
      $.each(watchlist.query.watchlist, function (i, article) {
                    name: 'Nasal library',
      $('div#options select#article_select optgroup#watchlist', markup).append($('<option>', {  
                    url: ''
        value: article.title, //FIXME just a placeholder for now
                }, {
        text : article.title  
                    category: 'develop',
    }));
                    name: 'Canvas Snippets',
  }); //foreach section
                    url: ''
                },
 
            ];
 
            // TODO: this should be moved elsewhere
            function updateArticleList(selector) {
                $.each(articles, function (i, article) {
                    $(selector + ' optgroup#' + article.category,
                        markup).append($('<option>', {
                        value: article.name, // FIXME: just a placeholder for now
                        text: article.name
                    })); //append option
                }); // foreach
            } // updateArticleList
 
            // add the article list to the corresponding dropdown menus
            updateArticleList('select#article_select');
 
            // populate watchlist (prototype for now)
            // TODO: generalize & refactor: url, format
 
            // https://www.mediawiki.org/wiki/API:Watchlist
            // http://wiki.flightgear.org/api.php?action=query&list=watchlist
            var watchlist_url =
                'http://wiki.flightgear.org/api.php?action=query&list=watchlist&format=json';
            Host.download(watchlist_url, function (response) {
                try {
                    var watchlist = JSON.parse(response.responseText);
 
                    //$('div#options select#section_select', markup).empty(); // delete all sections
 
                    $.each(watchlist.query.watchlist, function (i,
                        article) {
                        $(
                            'div#options select#article_select optgroup#watchlist',
                            markup).append($('<option>', {
                            value: article.title, //FIXME just a placeholder for now
                            text: article.title
                        }));
                    }); //foreach section
 
                } catch (e) {
                    UI.alert(e.message);
                }
            }); // download & populate watchlist
 
 
            // register an event handler for the main tab, so that article specific sections can be retrieved
            $('div#options select#article_select', markup).change(function () {
                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) {
                        var message =
                            'FlightGear wiki login status (AJAX):';
                        var status = response.statusText;
 
                        // populate dropdown menu with article sections
                        if (status === 'OK') {
 
                            // Resolve redirects: https://www.mediawiki.org/wiki/API:Query#Resolving_redirects
                            var section_url =
                                'http://wiki.flightgear.org/api.php?action=parse&page=' +
                                encodeURIComponent(article) +
                                '&prop=sections&format=json&redirects';
                            Host.download(section_url, function (
                                response) {
                                try {
                                    var sections = JSON
                                        .parse(response
                                            .responseText
                                        );
 
                                    $(
                                        'div#options select#section_select',
                                        markup).empty(); // delete all sections
 
                                    $.each(sections.parse
                                        .sections,
                                        function (i,
                                            section
                                        ) {
                                            $(
                                                'div#options select#section_select',
                                                markup
                                            ).append(
                                                $(
                                                    '<option>', {
                                                        value: section
                                                            .line, //FIXME just a placeholder for now
                                                        text: section
                                                            .line
                                                    }
                                                )
                                            );
                                        }); //foreach section
 
                                } catch (e) {
                                    UI.alert(e.message);
                                }
 
                            }); //download sections
 
 
 
                        } // login status is OK
 
 
                    }); // Host.download() call, i.e. we have a login token
 
            }); // on select change
 
            // init the tab stuff
            markup.tabs();
 
            var diagParam = {
                title: 'Instant Cquotes ' + Host.getScriptVersion(),
                modal: true,
                width: 700,
                buttons: [{
                        text: 'reported speech',
                        click: function () {
                            textarea.val(createCquote(original,
                                true));
                        }
                    },
 
                    {
                        text: 'Copy',
                        click: function () {
                            Host.setClipboard(msg);
                            $(this).dialog('close');
                        }
                    }
 
                ]
            };
 
            // actually show our tabbed dialog using the params above
            markup.dialog(diagParam);


        }
        catch (e) {
          UI.alert("Could not download wiki watchlist\n"+watchlist.error.info);
        }
      }); // download & populate watchlist
     
   
    // register an event handler for the main tab, so that article specific sections can be retrieved
    $('div#options select#article_select', markup).change(function() {
      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) {
    var message = 'FlightGear wiki login status (AJAX):';
    var status = response.statusText;
   
    // populate dropdown menu with article sections
    if (status === 'OK') {
   
      // Resolve redirects: https://www.mediawiki.org/wiki/API:Query#Resolving_redirects
      var section_url = 'http://wiki.flightgear.org/api.php?action=parse&page='+encodeURIComponent(article)+'&prop=sections&format=json&redirects';
      Host.download(section_url, function(response) {
        try {
      var sections = JSON.parse(response.responseText);
           
      $('div#options select#section_select', markup).empty(); // delete all sections
     
      $.each(sections.parse.sections, function (i, section) {
      $('div#options select#section_select', markup).append($('<option>', {
        value: section.line, //FIXME just a placeholder for now
        text : section.line
    }));
  }); //foreach section


         }
         } // jQueryTabbed()
        catch (e) {
          UI.alert(e.message);
        }
           
      }); //download sections
   
     
     
    } // login status is OK


     
  }); // Host.download() call, i.e. we have a login token
     
    }); // on select change
   
  // init the tab stuff
  markup.tabs();
 
  var diagParam = {
      title: 'Instant Cquotes ' + Host.getScriptVersion(),
      modal: true,
      width: 700,
      buttons: [
        {
          text:'reported speech',
          click: function() {
            textarea.val(createCquote(original,true));
          }
        },
       
        {
          text: 'Copy',
          click: function () {
            Host.setClipboard(msg);
            $(this).dialog('close');
          }
        }
       
      ]
  };
   
  // actually show our tabbed dialog using the params above
  markup.dialog(diagParam);
   
   
  } // jQueryTabbed()
 
}; // output methods
}; // output methods


Line 1,494: Line 1,625:


var speechTransformations = [
var speechTransformations = [
// TODO: support aliasing using vectors: would/should  
    // TODO: support aliasing using vectors: would/should
// ordering is crucial here (most specific first, least specific/most generic last)
    // ordering is crucial here (most specific first, least specific/most generic last)
 
// first, we start off  by expanding short forms: http://www.learnenglish.de/grammar/shortforms.html
    // first, we start off  by expanding short forms: http://www.learnenglish.de/grammar/shortforms.html
// http://www.macmillandictionary.com/thesaurus-category/british/short-forms
    // http://www.macmillandictionary.com/thesaurus-category/british/short-forms
 
  {query:/couldn\'t/gi, replacement:'could not'},
    {
  {query:/I could not/gi, replacement:'$author could not'},
        query: /couldn\'t/gi,
 
        replacement: 'could not'
  {query:/I\'m/gi, replacement:'I am'},
    }, {
  {query:/I am/gi, replacement:'$author is'},
        query: /I could not/gi,
 
        replacement: '$author could not'
  {query:/I\'ve/, replacement:'I have'},
    },
  {query:/I have had/, replacement:'$author had'},
 
 
    {
 
        query: /I\'m/gi,
  {query:/can(\'|\’)t/gi, replacement:'cannot'},
        replacement: 'I am'
 
    }, {
  {query:/I(\'|\’)ll/gi, replacement:'$author will'},
        query: /I am/gi,
  {query:/I(\'|\’)d/gi, replacement:'$author would'},
        replacement: '$author is'
 
    },
  {query:/I have done/gi, replacement:'$author has done'},
 
  {query:/I\'ve done/gi, replacement:'$author has done'}, //FIXME. queries should really be vectors ...
    {
 
        query: /I\'ve/,
  {query:/I believe/gi, replacement:'$author suggested'},
        replacement: 'I have'
  {query:/I think/gi, replacement:'$author suggested'},
    }, {
  {query:/I guess/gi, replacement:'$author believes'},
        query: /I have had/,
 
        replacement: '$author had'
  {query:/I can see that/gi, replacement:'$author suggested that'},
    },
 
 
 
 
  {query:/I have got/gi, replacement:'$author has got'},
    {
  {query:/I\'ve got/gi, replacement:'$author has got'},
        query: /can(\'|\’)t/gi,
 
        replacement: 'cannot'
  {query:/I\'d suggest/gi, replacement:'$author would suggest'},
    },
 
 
  {query:/I\’m prototyping/gi, replacement:'$author is prototyping'},
    {
 
        query: /I(\'|\’)ll/gi,
  {query:/I myself/gi, replacement:'$author himself'},
        replacement: '$author will'
  {query:/I am/gi, replacement:' $author is'},
    }, {
 
        query: /I(\'|\’)d/gi,
  {query:/I can see/gi, replacement:'$author can see'},
        replacement: '$author would'
  {query:/I can/gi, replacement:'$author can'},
    },
  {query:/I have/gi, replacement:'$author has'},
 
  {query:/I should/g, replacement:'$author should'},
    {
  {query:/I shall/gi, replacement:'$author shall'},
        query: /I have done/gi,
  {query:/I may/gi, replacement:'$author may'},
        replacement: '$author has done'
  {query:/I will/gi, replacement:'$author will'},
    }, {
  {query:/I would/gi, replacement:'$author would'},
        query: /I\'ve done/gi,
  {query:/by myself/gi, replacement:'by $author'},
        replacement: '$author has done'
  {query:/and I/gi, replacement:'and $author'},
    }, //FIXME. queries should really be vectors ...
  {query:/and me/gi, replacement:'and $author'},
 
  {query:/and myself/gi, replacement:'and $author'}
    {
 
        query: /I believe/gi,
 
        replacement: '$author suggested'
  // least specific stuff last (broad/generic stuff is kept as is, with author clarification added in parentheses)
    }, {
  /*
        query: /I think/gi,
  {query:/I/, replacement:'I ($author)'},
        replacement: '$author suggested'
 
    }, {
  {query:/me/, replacement:'me ($author)'},
        query: /I guess/gi,
  {query:/my/, replacement:'my ($author)'},
        replacement: '$author believes'
  {query:/myself/, replacement:'myself ($author)'},
    },
  {query:/mine/, replacement:'$author'}
 
  */
    {
        query: /I can see that/gi,
        replacement: '$author suggested that'
    },
 
 
    {
        query: /I have got/gi,
        replacement: '$author has got'
    }, {
        query: /I\'ve got/gi,
        replacement: '$author has got'
    },
 
    {
        query: /I\'d suggest/gi,
        replacement: '$author would suggest'
    },
 
    {
        query: /I\’m prototyping/gi,
        replacement: '$author is prototyping'
    },
 
    {
        query: /I myself/gi,
        replacement: '$author himself'
    }, {
        query: /I am/gi,
        replacement: ' $author is'
    },
 
    {
        query: /I can see/gi,
        replacement: '$author can see'
    }, {
        query: /I can/gi,
        replacement: '$author can'
    }, {
        query: /I have/gi,
        replacement: '$author has'
    }, {
        query: /I should/g,
        replacement: '$author should'
    }, {
        query: /I shall/gi,
        replacement: '$author shall'
    }, {
        query: /I may/gi,
        replacement: '$author may'
    }, {
        query: /I will/gi,
        replacement: '$author will'
    }, {
        query: /I would/gi,
        replacement: '$author would'
    }, {
        query: /by myself/gi,
        replacement: 'by $author'
    }, {
        query: /and I/gi,
        replacement: 'and $author'
    }, {
        query: /and me/gi,
        replacement: 'and $author'
    }, {
        query: /and myself/gi,
        replacement: 'and $author'
    }
 
 
    // least specific stuff last (broad/generic stuff is kept as is, with author clarification added in parentheses)
    /*
    {query:/I/, replacement:'I ($author)'},
    {query:/me/, replacement:'me ($author)'},
    {query:/my/, replacement:'my ($author)'},
    {query:/myself/, replacement:'myself ($author)'},
    {query:/mine/, replacement:'$author'}
    */
];
];


Line 1,563: Line 1,772:
// still needs to be exposed via the UI
// still needs to be exposed via the UI
function transformSpeech(text, author, gender, transformations) {
function transformSpeech(text, author, gender, transformations) {
  // WIP: foreach transformation in vector, replace the search pattern with the matched string (replacing author/gender as applicable)
    // WIP: foreach transformation in vector, replace the search pattern with the matched string (replacing author/gender as applicable)
  //alert("text to be transformed:\n"+text);
    //alert("text to be transformed:\n"+text);
  for(var i=0;i< transformations.length; i++) {
    for (var i = 0; i < transformations.length; i++) {
    var token = transformations[i];
        var token = transformations[i];
    // patch the replacement string using the correct author name  
        // patch the replacement string using the correct author name
    var replacement = token.replacement.replace(/\$author/gi, author);
        var replacement = token.replacement.replace(/\$author/gi, author);
    text = text.replace(token.query, replacement);
        text = text.replace(token.query, replacement);
  } // end of token transformation
    } // end of token transformation
  console.log("transformed text is:"+text);
    console.log("transformed text is:" + text);
  return text;
    return text;
} // transformSpeech
} // transformSpeech


// run a self-test
// run a self-test


(function() {
(function () {
var author ="John Doe";
    var author = "John Doe";
var transformed = transformSpeech("I have decided to commit a new feature", author, null, speechTransformations );
    var transformed = transformSpeech(
if (transformed !== author+" has decided to commit a new feature")
        "I have decided to commit a new feature", author, null,
  Host.dbLog("FIXME: Speech transformations are not working correctly");
        speechTransformations);
}) ();
    if (transformed !== author + " has decided to commit a new feature")
        Host.dbLog(
            "FIXME: Speech transformations are not working correctly");
})();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


var MONTHS = [
var MONTHS = [
  'Jan',
    'Jan',
  'Feb',
    'Feb',
  'Mar',
    'Mar',
  'Apr',
    'Apr',
  'May',
    'May',
  'Jun',
    'Jun',
  'Jul',
    'Jul',
  'Aug',
    'Aug',
  'Sep',
    'Sep',
  'Oct',
    'Oct',
  'Nov',
    'Nov',
  'Dec'
    'Dec'
];
];
// Conversion for forum emoticons
// Conversion for forum emoticons
var EMOTICONS = [
var EMOTICONS = [
  [/:shock:/g,
    [/:shock:/g,
  'O_O'],
        'O_O'
  [
    ],
    /:lol:/g,
    [
    '(lol)'
        /:lol:/g,
  ],
        '(lol)'
  [
    ],
    /:oops:/g,
    [
    ':$'
        /:oops:/g,
  ],
        ':$'
  [
    ],
    /:cry:/g,
    [
    ';('
        /:cry:/g,
  ],
        ';('
  [
    ],
    /:evil:/g,
    [
    '>:)'
        /:evil:/g,
  ],
        '>:)'
  [
    ],
    /:twisted:/g,
    [
    '3:)'
        /:twisted:/g,
  ],
        '3:)'
  [
    ],
    /:roll:/g,
    [
    '(eye roll)'
        /:roll:/g,
  ],
        '(eye roll)'
  [
    ],
    /:wink:/g,
    [
    ';)'
        /:wink:/g,
  ],
        ';)'
  [
    ],
    /:!:/g,
    [
    '(!)'
        /:!:/g,
  ],
        '(!)'
  [
    ],
    /:\?:/g,
    [
    '(?)'
        /:\?:/g,
  ],
        '(?)'
  [
    ],
    /:idea:/g,
    [
    '(idea)'
        /:idea:/g,
  ],
        '(idea)'
  [
    ],
    /:arrow:/g,
    [
    '(->)'
        /:arrow:/g,
  ],
        '(->)'
  [
    ],
    /:mrgreen:/g,
    [
    'xD'
        /:mrgreen:/g,
  ]
        'xD'
    ]
];
];
// ##################
// ##################
Line 1,658: Line 1,871:


// the required trigger is host specific (userscript vs. addon vs. android etc)
// the required trigger is host specific (userscript vs. addon vs. android etc)
// for now, this merely wraps window.load mapping to the instantCquotoe callback below
// for now, this merely wraps window.load mapping to the instantCquote callback
// below
Host.registerTrigger();
Host.registerTrigger();




// FIXME: function is currently referenced in CONFIG hash - event_handler, so cannot be easily moved across
// FIXME: function is currently referenced in CONFIG hash - event_handler, so
// cannot be easily moved across
// The main function
// The main function
// TODO: split up, so that we can reuse the code elsewhere
// TODO: split up, so that we can reuse the code elsewhere
function instantCquote(sel) {
function instantCquote(sel) {
  var profile = getProfile();
    var profile = getProfile();
 
 
  // TODO: use config hash here
    // TODO: use config hash here
  var selection = document.getSelection(),
    var selection = document.getSelection(),
  post_id=0;
        post_id = 0;
 
 
  try {
    try {
    post_id = getPostId(selection, profile);
        post_id = getPostId(selection, profile);
  }  
    } catch (error) {
  catch (error) {
        Host.dbLog('Failed extracting post id\nProfile:' + profile);
    Host.dbLog('Failed extracting post id\nProfile:' + profile);
        return;
    return;
    }
  }
    if (selection.toString() === '') {
  if (selection.toString() === '') {
        Host.dbLog('No text is selected, aborting function');
    Host.dbLog('No text is selected, aborting function');
        return;
    return;
    }
  }
    if (!checkValid(selection, profile)) {
  if (!checkValid(selection, profile)) {
        Host.dbLog('Selection is not valid, aborting function');
    Host.dbLog('Selection is not valid, aborting function');
        return;
    return;
    }
  }
    try {
  try {
        transformationLoop(profile, post_id);
    transformationLoop(profile, post_id);
    } catch (e) {
  }
        UI.alert("Transformation loop:\n" + e.message);
  catch(e) {
    }
    UI.alert("Transformation loop:\n"+e.message);
  }
} // instantCquote
} // instantCquote


  // TODO: this needs to be refactored so that it can be also reused by the async/AJAX mode
// TODO: this needs to be refactored so that it can be also reused by the async/AJAX mode
  // to extract fields in the background (i.e. move to a separate function)
// to extract fields in the background (i.e. move to a separate function)
function transformationLoop(profile, post_id) {
function transformationLoop(profile, post_id) {
  var output = {}, field;
    var output = {},
  Host.dbLog("Starting extraction/transformation loop");
        field;
  for (field in profile) {
    Host.dbLog("Starting extraction/transformation loop");
    if (field === 'name') continue;
    for (field in profile) {
    if (field ==='type' || field === 'event' || field === 'event_handler') continue; // skip fields that don't contain xpath expressions
        if (field === 'name') continue;
    Host.dbLog("Extracting field using field id:"+post_id);
        if (field === 'type' || field === 'event' || field === 'event_handler')
    var fieldData = extractFieldInfo(profile, post_id, field);
            continue; // skip fields that don't contain xpath expressions
    var transform = profile[field].transform;
        Host.dbLog("Extracting field using field id:" + post_id);
    if (transform !== undefined) {
        var fieldData = extractFieldInfo(profile, post_id, field);
      Host.dbLog('Field \'' + field + '\' before transformation:\n\'' + fieldData + '\'');
        var transform = profile[field].transform;
      fieldData = applyTransformations(fieldData, transform);
        if (transform !== undefined) {
      Host.dbLog('Field \'' + field + '\' after transformation:\n\'' + fieldData + '\'');
            Host.dbLog('Field \'' + field + '\' before transformation:\n\'' +
    }
                fieldData + '\'');
    output[field] = fieldData;
            fieldData = applyTransformations(fieldData, transform);
  } // extract and transform all fields for the current profile (website)
            Host.dbLog('Field \'' + field + '\' after transformation:\n\'' +
  Host.dbLog("extraction and transformation loop finished");
                fieldData + '\'');
  output.content = stripWhitespace(output.content);
        }
 
        output[field] = fieldData;
  var outputPlain = createCquote(output);
    } // extract and transform all fields for the current profile (website)
  outputText(outputPlain, output);
    Host.dbLog("extraction and transformation loop finished");
    output.content = stripWhitespace(output.content);
 
    var outputPlain = createCquote(output);
    outputText(outputPlain, output);
} // transformationLoop()
} // transformationLoop()


Line 1,725: Line 1,942:


function runProfileTests() {
function runProfileTests() {
 
  for (var profile in CONFIG) {
    if (CONFIG[profile].type != 'archive' || !CONFIG[profile].enabled ) continue; // skip the wiki entry, because it's not an actual archive that we need to test
    // should be really moved to downloadPostign
    if (CONFIG[profile].content.xpath === '') console.log("xpath for content extraction is empty, cannot procedurally extract contents");
    for (var test in CONFIG[profile].tests) {
      var required_data = CONFIG[profile].tests[test];
      var title = required_data.title;
      //dbLog('Running test for posting titled:' + title);
      // fetch posting via getPostingDataAJAX() and compare to the fields we are looking for (author, title, date)
      //getPostingDataAJAX(profile, required_data.url);
      //alert("required title:"+title);
    } // foreach test


  } // foreach profile (website)
    for (var profile in CONFIG) {
 
        if (CONFIG[profile].type != 'archive' || !CONFIG[profile].enabled)
            continue; // skip the wiki entry, because it's not an actual archive that we need to test
        // should be really moved to downloadPostign
        if (CONFIG[profile].content.xpath === '') console.log(
            "xpath for content extraction is empty, cannot procedurally extract contents"
        );
        for (var test in CONFIG[profile].tests) {
            var required_data = CONFIG[profile].tests[test];
            var title = required_data.title;
            //dbLog('Running test for posting titled:' + title);
            // fetch posting via getPostingDataAJAX() and compare to the fields we are looking for (author, title, date)
            //getPostingDataAJAX(profile, required_data.url);
            //alert("required title:"+title);
        } // foreach test
 
    } // foreach profile (website)
 
} //runProfileTests
} //runProfileTests


function selfCheckDialog() {
function selfCheckDialog() {
  var sections = '<h3>Important APIs:</h3><div id="api_checks"></div>';
    var sections = '<h3>Important APIs:</h3><div id="api_checks"></div>';




  try {
    try {
  runProfileTests.call(undefined); // check website profiles
        runProfileTests.call(undefined); // check website profiles
  }
    } catch (e) {
  catch (e) {
        UI.alert(e.message);
      UI.alert(e.message);
  }
 
  for (var profile in CONFIG) {
    // TODO: also check if enabled or not
    if (CONFIG[profile].type != 'archive') continue; // skip the wiki entry, because it's not an actual archive that we need to test
    var test_results = '';
    for (var test in CONFIG[profile].tests) {
      // var fieldData = extractFieldInfo(profile, post_id, 'author');
      test_results += CONFIG[profile].tests[test].title + '<p/>';
     }
     }
     sections +='<h3>' + profile + ':<font color="blue">'+ CONFIG[profile].url_reg+'</font></h3><div><p>' + test_results + '</p></div>\n';
 
  } // https://jqueryui.com/accordion/
     for (var profile in CONFIG) {
 
        // TODO: also check if enabled or not
        if (CONFIG[profile].type != 'archive') continue; // skip the wiki entry, because it's not an actual archive that we need to test
  var checkDlg = $('<div id="selfCheck" title="Self Check dialog"><p><div id="accordion">' + sections + '</div></p></div>');
        var test_results = '';
 
        for (var test in CONFIG[profile].tests) {
  // run all API tests, invoke the callback to obtain the status
            // var fieldData = extractFieldInfo(profile, post_id, 'author');
  Environment.runAPITests(Host, function(meta) {
            test_results += CONFIG[profile].tests[test].title + '<p/>';
 
        }
  //console.log('Running API test '+meta.name);
        sections += '<h3>' + profile + ':<font color="blue">' + CONFIG[profile]
   
            .url_reg + '</font></h3><div><p>' + test_results + '</p></div>\n';
  meta.test(function(result) {
    } // https://jqueryui.com/accordion/
  var status = (result)?'success':'fail';
 
  var test = $("<p></p>").text('Running API test '+meta.name+':'+status);  
 
  $('#api_checks', checkDlg).append(test);
    var checkDlg = $(
  }); // update tests results
        '<div id="selfCheck" title="Self Check dialog"><p><div id="accordion">' +
      
        sections + '</div></p></div>');
  }); // runAPITests
 
 
    // run all API tests, invoke the callback to obtain the status
 
    Environment.runAPITests(Host, function (meta) {
 
 
  /*
        //console.log('Running API test '+meta.name);
  [].forEach.call(CONFIG, function(profile) {
 
    alert("profile is:"+profile);
        meta.test(function (result) {
  [].forEach.call(CONFIG[profile].tests, function(test) {
            var status = (result) ? 'success' : 'fail';
   
            var test = $("<p></p>").text('Running API test ' +
    //UI.alert(test.url);
                meta.name + ':' + status);
    Host.downloadPosting(test.url, function(downloaded) {
            $('#api_checks', checkDlg).append(test);
      alert("downloaded:");
        }); // update tests results
      //if (test.title == downloaded.title) alert("titles match:"+test.title);
 
    }); //downloadPosting
     }); // runAPITests
  }); //forEach test
 
  }); //forEach profile
 
  */
 
 
    /*
  //$('#accordion',checkDlg).accordion();
    [].forEach.call(CONFIG, function(profile) {
  checkDlg.dialog({
      alert("profile is:"+profile);
    width: 700,
    [].forEach.call(CONFIG[profile].tests, function(test) {
    height: 500,
 
    open: function () {
      //UI.alert(test.url);
      // http://stackoverflow.com/questions/2929487/putting-a-jquery-ui-accordion-in-a-jquery-ui-dialog
      Host.downloadPosting(test.url, function(downloaded) {
      $('#accordion').accordion({
        alert("downloaded:");
        autoHeight: true
        //if (test.title == downloaded.title) alert("titles match:"+test.title);
      });
      }); //downloadPosting
    }
    }); //forEach test
  }); // show dialog
    }); //forEach profile
    */
 
    //$('#accordion',checkDlg).accordion();
    checkDlg.dialog({
        width: 700,
        height: 500,
        open: function () {
            // http://stackoverflow.com/questions/2929487/putting-a-jquery-ui-accordion-in-a-jquery-ui-dialog
            $('#accordion').accordion({
                autoHeight: true
            });
        }
    }); // show dialog
} // selfCheckDialog
} // selfCheckDialog


Line 1,813: Line 2,036:
// show a simple configuration dialog (WIP)
// show a simple configuration dialog (WIP)
function setupDialog() {
function setupDialog() {
  //alert("configuration dialog is not yet implemented");
    //alert("configuration dialog is not yet implemented");
  var checked = (Host.get_persistent('debug_mode_enabled', false) === true) ? 'checked' : '';
    var checked = (Host.get_persistent('debug_mode_enabled', false) === true) ?
  //dbLog("value is:"+get_persistent("debug_mode_enabled"));
        'checked' : '';
  //dbLog("persistent debug flag is:"+checked);
    //dbLog("value is:"+get_persistent("debug_mode_enabled"));
  var setupDiv = $('<div id="setupDialog" title="Setup dialog">NOTE: this configuration dialog is still work-in-progress</p><label><input id="debugcb" type="checkbox"' + checked + '>Enable Debug mode</label><p/><div id="progressbar"></div></div>');
    //dbLog("persistent debug flag is:"+checked);
  setupDiv.click(function () {
    var setupDiv = $(
    //alert("changing persistent debug state");
        '<div id="setupDialog" title="Setup dialog">NOTE: this configuration dialog is still work-in-progress</p><label><input id="debugcb" type="checkbox"' +
    Host.set_persistent('debug_mode_enabled', $('#debugcb').is(':checked'));
        checked +
  });
        '>Enable Debug mode</label><p/><div id="progressbar"></div></div>');
  //MediaWiki editing stub, based on: https://www.mediawiki.org/wiki/API:Edit#Editing_via_Ajax
    setupDiv.click(function () {
  //only added here to show some status info in the setup dialog
        //alert("changing persistent debug state");
  Host.download('http://wiki.flightgear.org/api.php?action=query&prop=info|revisions&intoken=edit&rvprop=timestamp&titles=Main%20Page', function (response) {
        Host.set_persistent('debug_mode_enabled', $('#debugcb').is(
    var message = 'FlightGear wiki login status (AJAX):';
            ':checked'));
    var status = response.statusText;
    });
    var color = (status == 'OK') ? 'green' : 'red';
    //MediaWiki editing stub, based on: https://www.mediawiki.org/wiki/API:Edit#Editing_via_Ajax
    Host.dbLog(message + status);
    //only added here to show some status info in the setup dialog
    var statusDiv = $('<p>' + message + status + '</p>').css('color', color);
    Host.download(
    setupDiv.append(statusDiv);
        'http://wiki.flightgear.org/api.php?action=query&prop=info|revisions&intoken=edit&rvprop=timestamp&titles=Main%20Page',
  });
        function (response) {
  setupDiv.dialog();
            var message = 'FlightGear wiki login status (AJAX):';
            var status = response.statusText;
            var color = (status == 'OK') ? 'green' : 'red';
            Host.dbLog(message + status);
            var statusDiv = $('<p>' + message + status + '</p>').css(
                'color', color);
            setupDiv.append(statusDiv);
        });
    setupDiv.dialog();
} // setupDialog
} // setupDialog


Line 1,840: Line 2,071:
function downloadOptionsXML() {
function downloadOptionsXML() {


  // download $FG_ROOT/options.xml
    // download $FG_ROOT/options.xml
          Host.download("https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/options.xml?format=raw", function(response) {
    Host.download(
        "https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/options.xml?format=raw",
        function (response) {
             var xml = response.responseText;
             var xml = response.responseText;
             var doc = Host.make_doc(xml, 'text/xml');
             var doc = Host.make_doc(xml, 'text/xml');
             // https://developer.mozilla.org/en-US/docs/Web/API/XPathResult
             // https://developer.mozilla.org/en-US/docs/Web/API/XPathResult
             var options = Host.eval_xpath(doc, '//*/option', XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
             var options = Host.eval_xpath(doc, '//*/option', XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
           
 
             // http://help.dottoro.com/ljgnejkp.php
             // http://help.dottoro.com/ljgnejkp.php
             Host.dbLog("Number of options found in options.xml:"+options.snapshotLength);
             Host.dbLog("Number of options found in options.xml:" + options.snapshotLength);
           
 
             // http://help.dottoro.com/ljtfvvpx.php
             // http://help.dottoro.com/ljtfvvpx.php
           
              // https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/options.xml
             
           
          }); // end of options.xml download


 
            // https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/options.xml
 
 
        }); // end of options.xml download
 
 
} // downloadOptionsXML
} // downloadOptionsXML


function getProfile(url=undefined) {
function getProfile(url = undefined) {
 
 
  if(url === undefined)  
    if (url === undefined)
    url=window.location.href;
        url = window.location.href;
  else
    else
    url=url;
        url = url;
 
 
  Host.dbLog("getProfile call URL is:"+url);
    Host.dbLog("getProfile call URL is:" + url);
 
 
  for (var profile in CONFIG) {
    for (var profile in CONFIG) {
    if (url.match(CONFIG[profile].url_reg) !== null) {
        if (url.match(CONFIG[profile].url_reg) !== null) {
      Host.dbLog('Matching website profile found');
            Host.dbLog('Matching website profile found');
      var invocations = Host.get_persistent(Host.getScriptVersion(), 0);
            var invocations = Host.get_persistent(Host.getScriptVersion(), 0);
      Host.dbLog('Number of script invocations for version ' + Host.getScriptVersion() + ' is:' + invocations);
            Host.dbLog('Number of script invocations for version ' + Host.getScriptVersion() +
                ' is:' + invocations);
 
            // determine if we want to show a config dialog
            if (invocations === 0) {
                Host.dbLog("ask for config dialog to be shown");
                var response = UI.confirm(
                    'This is your first time running version ' + Host.getScriptVersion() +
                    '\nConfigure now?');
                if (response) {
 
                    // show configuration dialog (jQuery)
                    setupDialog();
                } else {} // don't configure


      // determine if we want to show a config dialog
            }
      if (invocations === 0) {
        Host.dbLog("ask for config dialog to be shown");
        var response = UI.confirm('This is your first time running version ' + Host.getScriptVersion() + '\nConfigure now?');
        if (response) {
                 
          // show configuration dialog (jQuery)
          setupDialog();
        }  
        else {
        } // don't configure


      }     
            // increment number of invocations, use the script's version number as the key, to prevent the config dialog from showing up again (except for updated scripts)
     
            // FIXME: this is triggered/incremented by each click ...
      // increment number of invocations, use the script's version number as the key, to prevent the config dialog from showing up again (except for updated scripts)
            Host.dbLog("increment number of script invocations");
      // FIXME: this is triggered/incremented by each click ...
            Host.set_persistent(Host.getScriptVersion(), invocations + 1);
      Host.dbLog("increment number of script invocations");
            return CONFIG[profile];
      Host.set_persistent(Host.getScriptVersion(), invocations + 1);
        } // matched website profile
      return CONFIG[profile];
        Host.dbLog('Could not find matching URL in getProfile() call!');
    } // matched website profile
    } // for each profile
    Host.dbLog('Could not find matching URL in getProfile() call!');
} // Get the HTML code that is selected
  } // for each profile
}// Get the HTML code that is selected


function getSelectedHtml() {
function getSelectedHtml() {
  // From http://stackoverflow.com/a/6668159
    // From http://stackoverflow.com/a/6668159
  var html = '',
    var html = '',
  selection = document.getSelection();
        selection = document.getSelection();
  if (selection.rangeCount) {
    if (selection.rangeCount) {
    var container = document.createElement('div');
        var container = document.createElement('div');
    for (var i = 0; i < selection.rangeCount; i++) {
        for (var i = 0; i < selection.rangeCount; i++) {
      container.appendChild(selection.getRangeAt(i).cloneContents());
            container.appendChild(selection.getRangeAt(i).cloneContents());
        }
        html = container.innerHTML;
     }
     }
     html = container.innerHTML;
     Host.dbLog('instantCquote(): Unprocessed HTML\n\'' + html + '\'');
  }
    return html;
  Host.dbLog('instantCquote(): Unprocessed HTML\n\'' + html + '\'');
} // Gets the selected text
  return html;
}// Gets the selected text


function getSelectedText() {
function getSelectedText() {
  return document.getSelection().toString();
    return document.getSelection().toString();
}// Get the ID of the post
} // Get the ID of the post
// (this needs some work so that it can be used by the AJAX mode, without an actual selection)
// (this needs some work so that it can be used by the AJAX mode, without an actual selection)


function getPostId(selection, profile, focus) {
function getPostId(selection, profile, focus) {
  if (focus !== undefined) {
    if (focus !== undefined) {
    Host.dbLog("Trying to get PostId with defined focus");
        Host.dbLog("Trying to get PostId with defined focus");
    selection = selection.focusNode.parentNode;
        selection = selection.focusNode.parentNode;
  } else {
    } else {
    Host.dbLog("Trying to get PostId with undefined focus");
        Host.dbLog("Trying to get PostId with undefined focus");
    selection = selection.anchorNode.parentNode;
        selection = selection.anchorNode.parentNode;
  }
    }
  while (selection.id.match(profile.content.idStyle) === null) {
    while (selection.id.match(profile.content.idStyle) === null) {
    selection = selection.parentNode;
        selection = selection.parentNode;
  }
    }
  Host.dbLog("Selection id is:"+selection.id);
    Host.dbLog("Selection id is:" + selection.id);
  return selection.id;
    return selection.id;
}
}


// Checks that the selection is valid
// Checks that the selection is valid
function checkValid(selection, profile) {
function checkValid(selection, profile) {
  var ret = true,
    var ret = true,
  selection_cp = {
        selection_cp = {},
  },
        tags = profile.content.parentTag;
  tags = profile.content.parentTag;
    for (var n = 0; n < 2; n++) {
  for (var n = 0; n < 2; n++) {
        if (n === 0) {
    if (n === 0) {
            selection_cp = selection.anchorNode.parentNode;
      selection_cp = selection.anchorNode.parentNode;
        } else {
    } else {
            selection_cp = selection.focusNode.parentNode;
      selection_cp = selection.focusNode.parentNode;
    }
    while (true) {
      if (selection_cp.tagName === 'BODY') {
        ret = false;
        break;
      } else {
        var cont = false;
        for (var i = 0; i < tags.length; i++) {
          if (selection_cp[tags[0]] === tags[i]) {
            cont = true;
            break;
          }
         }
         }
         if (cont) {
         while (true) {
          break;
            if (selection_cp.tagName === 'BODY') {
        } else {
                ret = false;
          selection_cp = selection_cp.parentNode;
                break;
            } else {
                var cont = false;
                for (var i = 0; i < tags.length; i++) {
                    if (selection_cp[tags[0]] === tags[i]) {
                        cont = true;
                        break;
                    }
                }
                if (cont) {
                    break;
                } else {
                    selection_cp = selection_cp.parentNode;
                }
            }
         }
         }
      }
     }
     }
  }
    ret = ret && (getPostId(selection, profile) === getPostId(selection,
  ret = ret && (getPostId(selection, profile) === getPostId(selection, profile, 1));
        profile, 1));
  return ret;
    return ret;
}// Extracts the raw text from a certain place, using an XPath
} // Extracts the raw text from a certain place, using an XPath


function extractFieldInfo(profile, id, field) {
function extractFieldInfo(profile, id, field) {
 
  if (field === 'content') {
    Host.dbLog("Returning content (selection)");
    return profile[field].selection();
  } else {
    Host.dbLog("Extracting field via xpath:"+field);
    var xpath = '//*[@id="' + id + '"]/' + profile[field].xpath;
    return Host.eval_xpath(document, xpath).stringValue; // document.evaluate(xpath, document, null, XPathResult.STRING_TYPE, null).stringValue;
  }
}// Change the text using specified transformations


function applyTransformations(fieldInfo, trans) {  
    if (field === 'content') {
        Host.dbLog("Returning content (selection)");
        return profile[field].selection();
    } else {
        Host.dbLog("Extracting field via xpath:" + field);
        var xpath = '//*[@id="' + id + '"]/' + profile[field].xpath;
        return Host.eval_xpath(document, xpath).stringValue; // document.evaluate(xpath, document, null, XPathResult.STRING_TYPE, null).stringValue;
    }
} // Change the text using specified transformations
 
function applyTransformations(fieldInfo, trans) {
     for (var i = 0; i < trans.length; i++) {
     for (var i = 0; i < trans.length; i++) {
      fieldInfo = trans[i](fieldInfo);
        fieldInfo = trans[i](fieldInfo);
      Host.dbLog('applyTransformations(): Multiple transformation, transformation after loop #' + (i + 1) + ':\n\'' + fieldInfo + '\'');
        Host.dbLog(
            'applyTransformations(): Multiple transformation, transformation after loop #' +
            (i + 1) + ':\n\'' + fieldInfo + '\'');
     }
     }
     return fieldInfo;
     return fieldInfo;
 
 
} //applyTransformations
} //applyTransformations


// Formats the quote
// Formats the quote


function createCquote(data, indirect_speech=false) {
function createCquote(data, indirect_speech = false) {
if(!indirect_speech)
    if (!indirect_speech)
  return nonQuotedRef(data); // conventional/verbatim selection
        return nonQuotedRef(data); // conventional/verbatim selection
  else {  
    else {
    // pattern match the content using a vector of regexes
        // pattern match the content using a vector of regexes
    data.content = transformSpeech(data.content, data.author, null, speechTransformations );
        data.content = transformSpeech(data.content, data.author, null,
    return nonQuotedRef(data);
            speechTransformations);
  }
        return nonQuotedRef(data);
    }
}
}


function nonQuotedRef(data) { //TODO: rename  
function nonQuotedRef(data) { //TODO: rename
  var template = Host.getTemplate();
    var template = Host.getTemplate();
 
 
  var substituted = template
    var substituted = template
  .replace('$CONTENT', data.content)
        .replace('$CONTENT', data.content)
  .replace('$URL',data.url)
        .replace('$URL', data.url)
  .replace('$TITLE',data.title)
        .replace('$TITLE', data.title)
  .replace('$AUTHOR',data.author)
        .replace('$AUTHOR', data.author)
  .replace('$DATE',datef(data.date))
        .replace('$DATE', datef(data.date))
  .replace('$ADDED',datef(data.date))
        .replace('$ADDED', datef(data.date))
  .replace('$SCRIPT_VERSION', Host.getScriptVersion() );
        .replace('$SCRIPT_VERSION', Host.getScriptVersion());
 
 
  return substituted;  
    return substituted;
}//  
} //  


// Output the text.
// Output the text.
Line 2,022: Line 2,259:


function outputText(msg, original) {
function outputText(msg, original) {
  try {
    try {
    OUTPUT.jQueryTabbed(msg, original);  
        OUTPUT.jQueryTabbed(msg, original);
  }  
    } catch (err) {
  catch (err) {
        msg = msg.replace(/&lt;\/syntaxhighligh(.)>/g, '</syntaxhighligh$1');
    msg = msg.replace(/&lt;\/syntaxhighligh(.)>/g, '</syntaxhighligh$1');
        OUTPUT.msgbox(msg);
    OUTPUT.msgbox(msg);
    }
  }
}
}


Line 2,036: Line 2,272:


function extract(regex) {
function extract(regex) {
  return function (text) {
    return function (text) {
    return text.match(regex) [1];
        return text.match(regex)[1];
  };
    };
}
}
function prepend(prefix) {
function prepend(prefix) {
  return function (text) {
    return function (text) {
    return prefix + text;
        return prefix + text;
  };
    };
}
}
function removeComments(html) {
function removeComments(html) {
  return html.replace(/<!--.*?-->/g, '');
    return html.replace(/<!--.*?-->/g, '');
}// Not currently used (as of June 2015), but kept just in case
} // Not currently used (as of June 2015), but kept just in case




// currently unused
// currently unused
function escapePipes(html) {
function escapePipes(html) {
  html = html.replace(/\|\|/g, '{{!!}n}');
    html = html.replace(/\|\|/g, '{{!!}n}');
  html = html.replace(/\|\-/g, '{{!-}}');
    html = html.replace(/\|\-/g, '{{!-}}');
  return html.replace(/\|/g, '{{!}}');
    return html.replace(/\|/g, '{{!}}');
}// Converts HTML <a href="...">...</a> tags to wiki links, internal if possible.
} // Converts HTML <a href="...">...</a> tags to wiki links, internal if possible.


function a2wikilink(html) {
function a2wikilink(html) {
  // Links to wiki images, because
    // Links to wiki images, because
  // they need special treatment, or else they get displayed.
    // they need special treatment, or else they get displayed.
  html = html.replace(/<a.*?href="http:\/\/wiki\.flightgear\.org\/File:(.*?)".*?>(.*?)<\/a>/g, '[[Media:$1|$2]]');
    html = html.replace(
  // Wiki links without custom text.
        /<a.*?href="http:\/\/wiki\.flightgear\.org\/File:(.*?)".*?>(.*?)<\/a>/g,
  html = html.replace(/<a.*?href="http:\/\/wiki\.flightgear\.org\/(.*?)".*?>http:\/\/wiki\.flightgear\.org\/.*?<\/a>/g, '[[$1]]');
        '[[Media:$1|$2]]');
  // Links to the wiki with custom text
    // Wiki links without custom text.
  html = html.replace(/<a.*?href="http:\/\/wiki\.flightgear\.org\/(.*?)".*?>(.*?)<\/a>/g, '[[$1|$2]]');
    html = html.replace(
  // Remove underscores from all wiki links
        /<a.*?href="http:\/\/wiki\.flightgear\.org\/(.*?)".*?>http:\/\/wiki\.flightgear\.org\/.*?<\/a>/g,
  var list = html.match(/\[\[.*?\]\]/g);
        '[[$1]]');
  if (list !== null) {
    // Links to the wiki with custom text
    for (var i = 0; i < list.length; i++) {
    html = html.replace(
      html = html.replace(list[i], underscore2Space(list[i]));
        /<a.*?href="http:\/\/wiki\.flightgear\.org\/(.*?)".*?>(.*?)<\/a>/g,
    }
        '[[$1|$2]]');
  } // Convert non-wiki links
    // Remove underscores from all wiki links
  // TODO: identify forum/devel list links, and use the AJAX/Host.download helper to get a title/subject for unnamed links (using the existing xpath/regex helpers for that)
    var list = html.match(/\[\[.*?\]\]/g);
    if (list !== null) {
        for (var i = 0; i < list.length; i++) {
            html = html.replace(list[i], underscore2Space(list[i]));
        }
    } // Convert non-wiki links
    // TODO: identify forum/devel list links, and use the AJAX/Host.download helper to get a title/subject for unnamed links (using the existing xpath/regex helpers for that)


  html = html.replace(/<a.*?href="(.*?)".*?>(.*?)<\/a>/g, '[$1 $2]');
    html = html.replace(/<a.*?href="(.*?)".*?>(.*?)<\/a>/g, '[$1 $2]');
  // Remove triple dots from external links.
    // Remove triple dots from external links.
  // Replace with raw URL (MediaWiki converts it to a link).
    // Replace with raw URL (MediaWiki converts it to a link).
  list = html.match(/\[.*?(\.\.\.).*?\]/g);
    list = html.match(/\[.*?(\.\.\.).*?\]/g);
  if (list !== null) {
    if (list !== null) {
    for (var i = 0; i < list.length; i++) {
        for (var i = 0; i < list.length; i++) {
      html = html.replace(list[i], list[i].match(/\[(.*?) .*?\]/) [1]);
            html = html.replace(list[i], list[i].match(/\[(.*?) .*?\]/)[1]);
        }
     }
     }
  }
    return html;
  return html;
} // Converts images, including images in <a> links
}// Converts images, including images in <a> links


function img2link(html) {
function img2link(html) {
  html = html.replace(/<a[^<]*?href="([^<]*?)"[^<]*?><img.*?src="http:\/\/wiki\.flightgear\.org\/images\/.*?\/.*?\/(.*?)".*?><\/a>/g, '[[File:$2|250px|link=$1]]');
    html = html.replace(
  html = html.replace(/<img.*?src="http:\/\/wiki\.flightgear\.org\/images\/.*?\/.*?\/(.*?)".*?>/g, '[[File:$1|250px]]');
        /<a[^<]*?href="([^<]*?)"[^<]*?><img.*?src="http:\/\/wiki\.flightgear\.org\/images\/.*?\/.*?\/(.*?)".*?><\/a>/g,
  html = html.replace(/<a[^<]*?href="([^<]*?)"[^<]*?><img.*?src="(.*?)".*?><\/a>/g, '(see [$2 image], links to [$1 here])');
        '[[File:$2|250px|link=$1]]');
  return html.replace(/<img.*?src="(.*?)".*?>/g, '(see the [$1 linked image])');
    html = html.replace(
}// Converts smilies
        /<img.*?src="http:\/\/wiki\.flightgear\.org\/images\/.*?\/.*?\/(.*?)".*?>/g,
        '[[File:$1|250px]]');
    html = html.replace(
        /<a[^<]*?href="([^<]*?)"[^<]*?><img.*?src="(.*?)".*?><\/a>/g,
        '(see [$2 image], links to [$1 here])');
    return html.replace(/<img.*?src="(.*?)".*?>/g,
        '(see the [$1 linked image])');
} // Converts smilies


function forum_smilies2text(html) {
function forum_smilies2text(html) {
  html = html.replace(/<img src="\.\/images\/smilies\/icon_.*?\.gif" alt="(.*?)".*?>/g, '$1');
    html = html.replace(
  for (var i = 0; i < EMOTICONS.length; i++) {
        /<img src="\.\/images\/smilies\/icon_.*?\.gif" alt="(.*?)".*?>/g,
    html = html.replace(EMOTICONS[i][0], EMOTICONS[i][1]);
        '$1');
  }
    for (var i = 0; i < EMOTICONS.length; i++) {
  return html;
        html = html.replace(EMOTICONS[i][0], EMOTICONS[i][1]);
}// Converts font formatting
    }
    return html;
} // Converts font formatting


function forum_fontstyle2wikistyle(html) {
function forum_fontstyle2wikistyle(html) {
  html = html.replace(/<span style="font-weight: bold">(.*?)<\/span>/g, '\'\'\'$1\'\'\'');
    html = html.replace(/<span style="font-weight: bold">(.*?)<\/span>/g,
  html = html.replace(/<span style="text-decoration: underline">(.*?)<\/span>/g, '<u>$1</u>');
        '\'\'\'$1\'\'\'');
  html = html.replace(/<span style="font-style: italic">(.*?)<\/span>/g, '\'\'$1\'\'');
    html = html.replace(
  return html.replace(/<span class="posthilit">(.*?)<\/span>/g, '$1');
        /<span style="text-decoration: underline">(.*?)<\/span>/g,
}// Converts code blocks
        '<u>$1</u>');
    html = html.replace(/<span style="font-style: italic">(.*?)<\/span>/g,
        '\'\'$1\'\'');
    return html.replace(/<span class="posthilit">(.*?)<\/span>/g, '$1');
} // Converts code blocks


function forum_code2syntaxhighlight(html) {
function forum_code2syntaxhighlight(html) {
  var list = html.match(/<dl class="codebox">.*?<code>(.*?)<\/code>.*?<\/dl>/g),
    var list = html.match(
  data = [
            /<dl class="codebox">.*?<code>(.*?)<\/code>.*?<\/dl>/g),
  ];
        data = [];
  if (list === null) return html;
    if (list === null) return html;
  for (var n = 0; n < list.length; n++) {
    for (var n = 0; n < list.length; n++) {
    data = html.match(/<dl class="codebox">.*?<code>(.*?)<\/code>.*?<\/dl>/);
        data = html.match(/<dl class="codebox">.*?<code>(.*?)<\/code>.*?<\/dl>/);
    html = html.replace(data[0], processCode(data));
        html = html.replace(data[0], processCode(data));
  }
    }
  return html;
    return html;
}// Strips any whitespace from the beginning and end of a string
} // Strips any whitespace from the beginning and end of a string


function stripWhitespace(html) {
function stripWhitespace(html) {
  html = html.replace(/^\s*?(\S)/, '$1');
    html = html.replace(/^\s*?(\S)/, '$1');
  return html.replace(/(\S)\s*?\z/, '$1');
    return html.replace(/(\S)\s*?\z/, '$1');
}// Process code, including basic detection of language
} // Process code, including basic detection of language


function processCode(data) {
function processCode(data) {
  var lang = '',
    var lang = '',
  code = data[1];
        code = data[1];
  code = code.replace(/&nbsp;/g, ' ');
    code = code.replace(/&nbsp;/g, ' ');
  if (code.match(/=?.*?\(?.*?\)?;/) !== null) lang = 'nasal';
    if (code.match(/=?.*?\(?.*?\)?;/) !== null) lang = 'nasal';
  if (code.match(/&lt;.*?&gt;.*?&lt;\/.*?&gt;/) !== null || code.match(/&lt;!--.*?--&gt;/) !== null) lang = 'xml';
    if (code.match(/&lt;.*?&gt;.*?&lt;\/.*?&gt;/) !== null || code.match(
  code = code.replace(/<br\/?>/g, '\n');
            /&lt;!--.*?--&gt;/) !== null) lang = 'xml';
  return '<syntaxhighlight lang="' + lang + '" enclose="div">\n' + code + '\n&lt;/syntaxhighlight>';
    code = code.replace(/<br\/?>/g, '\n');
}// Converts quote blocks to Cquotes
    return '<syntaxhighlight lang="' + lang + '" enclose="div">\n' + code +
        '\n&lt;/syntaxhighlight>';
} // Converts quote blocks to Cquotes


function forum_quote2cquote(html) {
function forum_quote2cquote(html) {
  html = html.replace(/<blockquote class="uncited"><div>(.*?)<\/div><\/blockquote>/g, '{{cquote|$1}}');
    html = html.replace(
  if (html.match(/<blockquote>/g) === null) return html;
        /<blockquote class="uncited"><div>(.*?)<\/div><\/blockquote>/g,
  var numQuotes = html.match(/<blockquote>/g).length;
        '{{cquote|$1}}');
  for (var n = 0; n < numQuotes; n++) {
    if (html.match(/<blockquote>/g) === null) return html;
    html = html.replace(/<blockquote><div><cite>(.*?) wrote.*?:<\/cite>(.*?)<\/div><\/blockquote>/, '{{cquote|$2|$1}}');
    var numQuotes = html.match(/<blockquote>/g).length;
  }
    for (var n = 0; n < numQuotes; n++) {
  return html;
        html = html.replace(
}// Converts videos to wiki style
            /<blockquote><div><cite>(.*?) wrote.*?:<\/cite>(.*?)<\/div><\/blockquote>/,
            '{{cquote|$2|$1}}');
    }
    return html;
} // Converts videos to wiki style


function vid2wiki(html) {
function vid2wiki(html) {
  // YouTube
    // YouTube
  html = html.replace(/<div class="video-wrapper">\s.*?<div class="video-container">\s*?<iframe class="youtube-player".*?width="(.*?)" height="(.*?)" src="http:\/\/www\.youtube\.com\/embed\/(.*?)".*?><\/iframe>\s*?<\/div>\s*?<\/div>/g, '{{#ev:youtube|$3|$1x$2}}');
    html = html.replace(
  // Vimeo
        /<div class="video-wrapper">\s.*?<div class="video-container">\s*?<iframe class="youtube-player".*?width="(.*?)" height="(.*?)" src="http:\/\/www\.youtube\.com\/embed\/(.*?)".*?><\/iframe>\s*?<\/div>\s*?<\/div>/g,
  html = html.replace(/<iframe src="http:\/\/player\.vimeo\.com\/video\/(.*?)\?.*?" width="(.*?)" height="(.*?)".*?>.*?<\/iframe>/g, '{{#ev:vimeo|$1|$2x$3}}');
        '{{#ev:youtube|$3|$1x$2}}');
  return html.replace(/\[.*? Watch on Vimeo\]/g, '');
    // Vimeo
}// Not currently used (as of June 2015), but kept just in case
    html = html.replace(
        /<iframe src="http:\/\/player\.vimeo\.com\/video\/(.*?)\?.*?" width="(.*?)" height="(.*?)".*?>.*?<\/iframe>/g,
        '{{#ev:vimeo|$1|$2x$3}}');
    return html.replace(/\[.*? Watch on Vimeo\]/g, '');
} // Not currently used (as of June 2015), but kept just in case


// currently unused
// currently unused
function escapeEquals(html) {
function escapeEquals(html) {
  return html.replace(/=/g, '{{=}}');
    return html.replace(/=/g, '{{=}}');
}// <br> to newline.
} // <br> to newline.


function forum_br2newline(html) {
function forum_br2newline(html) {
  html = html.replace(/<br\/?><br\/?>/g, '\n');
    html = html.replace(/<br\/?><br\/?>/g, '\n');
  return html.replace(/<br\/?>/g, '\n\n');
    return html.replace(/<br\/?>/g, '\n\n');
}// Forum list to wiki style
} // Forum list to wiki style


function list2wiki(html) {
function list2wiki(html) {
  var list = html.match(/<ul>(.*?)<\/ul>/g);
    var list = html.match(/<ul>(.*?)<\/ul>/g);
  if (list !== null) {
    if (list !== null) {
    for (var i = 0; i < list.length; i++) {
        for (var i = 0; i < list.length; i++) {
      html = html.replace(/<li>(.*?)<\/li>/g, '* $1\n');
            html = html.replace(/<li>(.*?)<\/li>/g, '* $1\n');
        }
     }
     }
  }
    list = html.match(/<ol.*?>(.*?)<\/ol>/g);
  list = html.match(/<ol.*?>(.*?)<\/ol>/g);
    if (list !== null) {
  if (list !== null) {
        for (var i = 0; i < list.length; i++) {
    for (var i = 0; i < list.length; i++) {
            html = html.replace(/<li>(.*?)<\/li>/g, '# $1\n');
      html = html.replace(/<li>(.*?)<\/li>/g, '# $1\n');
        }
     }
     }
  }
    html = html.replace(/<\/?[uo]l>/g, '');
  html = html.replace(/<\/?[uo]l>/g, '');
    return html;
  return html;
}
}
function nowiki(text) {
function nowiki(text) {
  return '<nowiki>' + text + '</nowiki>';
    return '<nowiki>' + text + '</nowiki>';
}// Returns the correct ordinal adjective
} // Returns the correct ordinal adjective


function ordAdj(date) {
function ordAdj(date) {
  date = date.toString();
    date = date.toString();
  if (date == '11' || date == '12' || date == '13') {
    if (date == '11' || date == '12' || date == '13') {
    return 'th';
        return 'th';
  } else if (date.substr(1) == '1' || date == '1') {
    } else if (date.substr(1) == '1' || date == '1') {
    return 'st';
        return 'st';
  } else if (date.substr(1) == '2' || date == '2') {
    } else if (date.substr(1) == '2' || date == '2') {
    return 'nd';
        return 'nd';
  } else if (date.substr(1) == '3' || date == '3') {
    } else if (date.substr(1) == '3' || date == '3') {
    return 'rd';
        return 'rd';
  } else {
    } else {
    return 'th';
        return 'th';
  }
    }
}
}


// Formats the date to this format: Apr 26th, 2015
// Formats the date to this format: Apr 26th, 2015
function datef(text) {
function datef(text) {
  var date = new Date(text);
    var date = new Date(text);
  return MONTHS[date.getMonth()] + ' ' + date.getDate() + ordAdj(date.getDate()) + ', ' + date.getFullYear();
    return MONTHS[date.getMonth()] + ' ' + date.getDate() + ordAdj(date.getDate()) +
        ', ' + date.getFullYear();
}
}
function underscore2Space(str) {
function underscore2Space(str) {
  return str.replace(/_/g, ' ');
    return str.replace(/_/g, ' ');
}
}


Line 2,218: Line 2,488:


function evolve_expression_test() {
function evolve_expression_test() {
 
try { 
var genetic = Genetic.create();


// TODO: use minimizer: redundant_bytes + duration_msec + xpath.length
    try {
genetic.optimize = Genetic.Optimize.Maximize;
        var genetic = Genetic.create();
genetic.select1 = Genetic.Select1.Tournament2;
 
genetic.select2 = Genetic.Select2.Tournament2;
        // TODO: use minimizer: redundant_bytes + duration_msec + xpath.length
   
        genetic.optimize = Genetic.Optimize.Maximize;
 
        genetic.select1 = Genetic.Select1.Tournament2;
genetic.seed = function() {
        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 t = this.userData.tests[0].haystack;
            //var regex = new RegExp(this.userData.solution);
            //var output = t.match( new RegExp("From: (.*) <.*@.*>"))[1]; 
            // TODO: use search & match for improving the fitness
 
            if (0)
                try {
                    var regex = new RegExp(entity);
                    var output = t.search(regex);
                    validExp = 10;
                }
            catch (e) {
                validExp = 2;
            }
 
 
 
            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;
            }


    function randomString(len) {
            var value = pop[0].entity;
        var text = "";
             this.last = this.last || value;
        var charset = "\\abcdefghijklmnopqrstuvwxyz0123456789[] ()<>*.,";
        for(var i=0;i<len;i++)
             text += charset.charAt(Math.floor(Math.random() * charset.length));
       
        return text; // "From:&(.*)$<.*8.*>"
    }
   
    // create random strings that are equal in length to solution
    return randomString( this.userData["solution"].length);
};
 


genetic.mutate = function(entity) {
            if (pop != 0 && value == this.last)
   
                return;
    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 solution = [];
    var len = mother.length;
            var i;
    var ca = Math.floor(Math.random()*len);
            for (i = 0; i < value.length; ++i) {
    var cb = Math.floor(Math.random()*len);    
                var diff = value.charCodeAt(i) - this.last.charCodeAt(i);
    if (ca > cb) {
                var style = "background: transparent;";
        var tmp = cb;
                if (diff > 0) {
        cb = ca;
                    style = "background: rgb(0,200,50); color: #fff;";
        ca = tmp;
                } else if (diff < 0) {
    }
                    style = "background: rgb(0,100,50); color: #fff;";
       
                }
    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) {


};
                solution.push("<span style=\"" + style + "\">" + value[i] +
   
                    "</span>");
/* 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
            var t = this.userData.tests[0].haystack;
   
            //console.log("haystack is:"+t);
   
            // "From: John Doe <John@do...> - 2020-07-02 17:36:03", needle: "John Doe"}, /From: (.*) <.*@.*>/
genetic.fitness = function(entity) {
            var regex = new RegExp(this.userData.solution);
    var fitness = 0;
            //var output = t.match( new RegExp("From: (.*) <.*@.*>"))[1];   
    var result;
            // TODO: use search & match for improving the fitness
    var validExp = 0.1;
            var output = t.search(new RegExp(value));
    var hasToken = 0.1;
 
 
    var t = this.userData.tests[0].haystack;
    //var regex = new RegExp(this.userData.solution);
    //var output = t.match( new RegExp("From: (.*) <.*@.*>"))[1];   
    // TODO: use search & match for improving the fitness
 
    if (0) 
    try {
    var regex = new RegExp(entity);
    var output = t.search( regex);
    validExp = 5;
    //if (output) validExp = 50;
    }
    catch(e) {
    //validExp = 2;   
    }
 
 
   
    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) {
            var buf = "";
    // stop running once we've reached the solution
            buf += "<tr>";
    return pop[0].entity != this.userData["solution"];
            buf += "<td>" + generation + "</td>";
};
            buf += "<td>" + pop[0].fitness.toPrecision(5) + "</td>";
            buf += "<td>" + solution.join("") + "</td>";
            buf += "<td>" + output + "</td>";
            buf += "</tr>";
            $("#results tbody").prepend(buf);


genetic.notification = function(pop, generation, stats, isFinished) {
            this.last = value;
        };


    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) {
        var diff = value.charCodeAt(i) - this.last.charCodeAt(i);
        var style = "background: transparent;";
        if (diff > 0) {
            style = "background: rgb(0,200,50); color: #fff;";
        } else if (diff < 0) {
            style = "background: rgb(0,100,50); color: #fff;";
        }


         solution.push("<span style=\"" + style + "\">" + value[i] + "</span>");
         /*
    }
 
    var t = this.userData.tests[0].haystack;
    //console.log("haystack is:"+t);
    // "From: John Doe <John@do...> - 2020-07-02 17:36:03", needle: "John Doe"}, /From: (.*) <.*@.*>/
    var regex = new RegExp(this.userData.solution);
    //var output = t.match( new RegExp("From: (.*) <.*@.*>"))[1]; 
    // TODO: use search & match for improving the fitness
    var output = t.search( new RegExp(value));
   
   
    var buf = "";
    buf += "<tr>";
    buf += "<td>" + generation + "</td>";
    buf += "<td>" + pop[0].fitness.toPrecision(5) + "</td>";
    buf += "<td>" + solution.join("") + "</td>";
    buf += "<td>" + output + "</td>";
    buf += "</tr>";
    $("#results tbody").prepend(buf);
   
    this.last = value;
};
 
 
  /*
genetic.notification2 = function(pop, generation, stats, isFinished) {
genetic.notification2 = function(pop, generation, stats, isFinished) {


Line 2,415: Line 2,692:
};
};
   */
   */
   
 
     
 
var config = {
        var config = {
             "iterations": 4000
             "iterations": 4000,
             , "size": 250
             "size": 250,
             , "crossover": 0.3
             "crossover": 0.3,
             , "mutation": 0.4
             "mutation": 0.4,
             , "skip": 30 // notifications
             "skip": 30 // notifications
            //, "webWorkers": false
                //, "webWorkers": false
         };
         };




/*
        /*
var profile = CONFIG['Sourceforge Mailing list'];
        var profile = CONFIG['Sourceforge Mailing list'];
var posting = profile.tests[0];
        var posting = profile.tests[0];
var author_xpath = profile.title.xpath;
        var author_xpath = profile.title.xpath;
*/
        */
 
        var regexTests = [{
            haystack: "From: John Doe <John@do...> - 2020-07-02 17:36:03",
            needle: "John Doe"
        }, {
            haystack: "From: Marc Twain <Marc@ta...> - 2010-01-03 07:36:03",
            needle: "Marc Twain"
        }, {
            haystack: "From: George W. Bush <GWB@wh...> - 2055-11-11 17:33:13",
            needle: "George W. Bush"
        }];


var regexTests = [
        // the regex we want to evolve
  {haystack: "From: John Doe <John@do...> - 2020-07-02 17:36:03", needle: "John Doe"},
        var solution = "From: (.*) <.*@.*>";
  {haystack: "From: Marc Twain <Marc@ta...> - 2010-01-03 07:36:03", needle: "Marc Twain"},
  {haystack: "From: George W. Bush <GWB@wh...> - 2055-11-11 17:33:13", needle: "George W. Bush"}
];
 
// the regex we want to evolve
var solution = "From: (.*) <.*@.*>";


// let's assume, we'd like to evolve a regex expression like this one
        // let's assume, we'd like to evolve a regex expression like this one
var userData = {
        var userData = {
             solution: solution,
             solution: solution,
             tests: regexTests                        
             tests: regexTests
};  
        };
   
 
genetic.evolve(config, userData);
        genetic.evolve(config, userData);
 
 
        //console.log("genetic.js is loaded and working, but disabled for now");   
 
 
    } // try
    catch (e) {
        console.log("genetic.js error:\n" + e.message);
    } // catch


   
//console.log("genetic.js is loaded and working, but disabled for now");   
   
 
} // try
catch (e) {
  console.log("genetic.js error:\n" +e.message);
} // catch
 
} // evolveExpression_test()
} // evolveExpression_test()




if(0) //TODO: expose via development tab
if (0) //TODO: expose via development tab
try {
    try {
  // https://github.com/cazala/synaptic
    // https://github.com/cazala/synaptic
  var Neuron = synaptic.Neuron,
    var Neuron = synaptic.Neuron,
    Layer = synaptic.Layer,
        Layer = synaptic.Layer,
    Network = synaptic.Network,
        Network = synaptic.Network,
    Trainer = synaptic.Trainer,
        Trainer = synaptic.Trainer,
    Architect = synaptic.Architect;
        Architect = synaptic.Architect;
 
 
  function Perceptron(input, hidden, output)
    function Perceptron(input, hidden, output) {
{
        // create the layers
    // create the layers
        var inputLayer = new Layer(input);
    var inputLayer = new Layer(input);
        var hiddenLayer = new Layer(hidden);
    var hiddenLayer = new Layer(hidden);
        var outputLayer = new Layer(output);
    var outputLayer = new Layer(output);


    // connect the layers
        // connect the layers
    inputLayer.project(hiddenLayer);
        inputLayer.project(hiddenLayer);
    hiddenLayer.project(outputLayer);
        hiddenLayer.project(outputLayer);


    // set the layers
        // set the layers
    this.set({
        this.set({
        input: inputLayer,
            input: inputLayer,
        hidden: [hiddenLayer],
            hidden: [hiddenLayer],
        output: outputLayer
            output: outputLayer
    });
        });
}
    }


// extend the prototype chain
    // extend the prototype chain
Perceptron.prototype = new Network();
    Perceptron.prototype = new Network();
Perceptron.prototype.constructor = Perceptron;
    Perceptron.prototype.constructor = Perceptron;
 
var myPerceptron = new Perceptron(2,3,1);
var myTrainer = new Trainer(myPerceptron);


myTrainer.XOR(); // { error: 0.004998819355993572, iterations: 21871, time: 356 }
    var myPerceptron = new Perceptron(2, 3, 1);
    var myTrainer = new Trainer(myPerceptron);


myPerceptron.activate([0,0]); // 0.0268581547421616
    myTrainer.XOR(); // { error: 0.004998819355993572, iterations: 21871, time: 356 }
myPerceptron.activate([1,0]); // 0.9829673642853368
myPerceptron.activate([0,1]); // 0.9831714267395621
myPerceptron.activate([1,1]); // 0.02128894618097928
 
 
console.log("Syntaptic loaded");
} catch(e) {
  UI.alert(e.message);
}


    myPerceptron.activate([0, 0]); // 0.0268581547421616
    myPerceptron.activate([1, 0]); // 0.9829673642853368
    myPerceptron.activate([0, 1]); // 0.9831714267395621
    myPerceptron.activate([1, 1]); // 0.02128894618097928


    console.log("Syntaptic loaded");
} catch (e) {
    UI.alert(e.message);
}
</syntaxhighlight>
</syntaxhighlight>


{{Appendix}}
{{Appendix}}
329

edits

Navigation menu