User:Daduxing/familytree.js

// <nowiki>// Wiki user script to help maintain {{family *tree}},{{chart}} or {{tree chart}}// boxes-and-lines diagrams, by allowing you to edit the diagram// in a simpler and more standard ASCII art format.// Greg Ubben, 1 Dec 2008// User:PBS , 31 Jan 2020 added code to parse "family tree" and "tree chart"//// To install, add:   importScript("User:Daduxing/familytree.js");// to your common.js page.  This adds an option [Templates → Art]// to the toolbox menu when editing familytrees.// // It is a two pass operation.// 1. Press [Templates → Art] -- the template code is changed into an intermediate//                               format and the menu option changes to [Art → Templates]// 2. Press Art → Templates] --  converts the "Art" back in to cleaned up temlate code////// IE may work better than Firefox since it supports typeover mode.//// TODO:// - Anything we can do to improve [[WP:ACCESSIBILITY]]// - Some smarts with border/boxstyle//// Advanced ideas:// - Draw line between start and end of selection// - Cut/copy/paste rectangular selections (no existing library??)//   - include overwrite/typeover mode emulation for Firefox// - Java GUI version where you drag boxes and lines on a grid$ (function() {      // wraps entire scriptvar Summary  = "Edited {{%s}} using [[User:Daduxing/familytree.js|familytree.js]]";var Special  = [ "border", "boxstyle", "colspan", "rowspan" ];var Template;            // family *tree, chart or tree chart?var Style    = null;var Center   = 40;       // center small diagrams on this columnvar Maxwidth = 80;var Picky    = 0;        // complain instead of self-correct?var rows;var boxes;//-----------------------------------------------// New code 2020-01-31// This function returns the possible template names// It is in the form of a regular expression match. // The parenethasis represents "select"// The bar character ("|") represents "or"// so (Foo|Bar) means true if the string contains either Foo or Bar// and (Foo|Bar|Chart) means true if the string contains either Foo or Bar or Chart.// In this case there is a fourth option represented by a " *" which means 0 or more// spaces. In this case it covers "familytree" and "family tree".// The case of the first letter (is covered by another function so there is no need// to add cases to these options. //-----------------------------------------------function template_names(){    return "(family *tree|chart|tree chart)";}//-----------------------------------------------// New code 2020-01-31// This function returns a pattern for the whole of a family tree.// Because the string is passed into another function for processing it is necessary to double// escape the escape character "\", The first one is stripped in this creation leaving// the second one for the patter match ie "\\{\\{" becomes "\{\{" //-----------------------------------------------function tree_pattern(){    return "\\{\\{" + template_names() + "\\/start[\\S\\s]*?\\{\\{" + template_names() + "\\/end\\}\\}";}//  Add/replace convert option at top of toolbox menu on sidebar.//function update_menu (item){    var node = document.getElementById("t-diagram");    if (node)        node.parentNode.removeChild(node);    node = document.getElementById("t-whatlinkshere");    if (item == "wiki2art")        addPortletLink ("p-tb", "javascript:wiki2art()",                        "Templates → Art", "t-diagram",           "Convert {{" + Template + "}}... to ASCII art", "", node);    if (item == "art2wiki")        addPortletLink ("p-tb", "javascript:art2wiki()",                        "Art → Templates", "t-diagram",           "Convert ASCII art back to {{" + Template + "}}...", "", node);}function wiki2art(){    try {        Style = null;        var textarea = document.editform.wpTextbox1;        var scroll_pos = textarea.scrollTop;        var pattern = new RegExp(tree_pattern(), "ig");        textarea.value = textarea.value.replace(pattern, wiki2art_replace);        textarea.setAttribute("wrap", "off");        // work around problem with Firefox ignoring wrap (bug 302710)        textarea.style.display = "block";        textarea.scrollTop = scroll_pos;      // Mozilla only?        update_menu ("art2wiki");        document.editform.wpSave.disabled = true;    }    catch (e) {        alert ("Could not convert to ASCII art because:\n\n" + e);    }}function wiki2art_replace (text, tmpl){    var rows  = [];    var parts = {};    if (text.indexOf("\n") == -1)        return text;                // don't convert a 1-line legend    //  Sanity check, if non-empty but no lines begin with {{.    //    if (text.search(/\n\s*\{\{.*\n/)   == -1 &&        text.search(/\n\s*[^\s<].*\n/) != -1) {            toss ("Out of sync; looks like this already is art.");            return text;    }    Template = tmpl.toLowerCase();    Maxwidth = (Template.match(/^(chart|tree chart)/i) ? 50 : 80);    Style    = Style || new MarkupStyle(text);    parse_templates (text, rows);    var start = "{{" + rows.shift().join("|") + "}}\n";    var end   = "{{" + rows.pop().join("|")   + "}}";        layout_tiles (rows, parts);    var art = pad_text( touchup( parts.art ));    var width = art.indexOf("\n") / 2;    width = (width > 50 && Maxwidth > 50) ? Maxwidth : 50;    var ruler = Array(11).join("0-1-2-3-4-5-6-7-8-9-")                         .slice(0, width*2-1);    return start + "\n" + ruler + "\n" + art +           "\n" + parts.list + "\n" + end;}//  Remember markup spacing styles based on first occurrences.//  So to change the markup style, just change the first one//  then toggle twice to "refresh".//function MarkupStyle (text){    this.initial = "";    this.lead    = " ";    this.equal   = "=";    var res;    text = text || "";    text = text.replace(/^.*\n/, "");   // strip {{family *tree/start}}    res = text.match(/\w( *)\|/);    if (res) {        this.initial = res[1];   // space after template name?    }    res = text.match(/\|(\s*)\w{2,5}(\s*=\s*)[^\s=|}]/);    if (res) {        this.lead  = res[1];     // params indented on new lines?        this.equal = res[2];    }    this.trail = (/\n/.test(this.lead) ? " " : "");    this.trim  = (text.search(/\| \| (\|?}}|\|\s*\w+\s*=)/) == -1);    this.param = function(name,value) {        return this.lead + name + this.equal + value + this.trail;    }}//  Parse textual series of {{family *tree|...|...}} templates//  into a list of parameter lists. The parameters can contain//  arbitrarily complex nested wiki syntax like [[foo|bar]] and//  {{foo|bar|{{{1|baz}}}}} but this simple strategy of just//  counting double brackets and braces should be good enough.//function parse_templates (text, rows){    var pattern = /([[\]{}])\1|\||<!--[\S\s]*?-->|<nowiki>[\S\s]*?<\/nowiki>/ig;    var level = 0;    var row, start, res;    while ((res = pattern.exec(text)) != null) {        if (res[1]) {            (res[1]=="[" || res[1]=="{") ? level++ : level--;        }        if (res[0] == "{{" && level == 1) {            row   = [];            start = res.index + 2;        }        if (res[0] == "|" && level == 1) {            row.push(text.slice(start, res.index));            start = res.index + 1;        }        if (res[0] == "}}" && level == 0) {            row.push(text.slice(start, res.index));            rows.push(row);        }    }    if (level != 0)        throw "Mismatched {{...}} or [[...]]";}function layout_tiles (rows, parts){    var art     = "";    var params  = {};    var order   = [];    var specpat = new RegExp("^((" + Special.join("|") + ")_)\\s*(\\S.*)" );    //  Tweak name so it is valid (matches namepat from map_boxes()    //  and is 2 to 5 characters long) and so it is unique if the    //  same name is used on several templates with different values.    //  Then store it in params{} and order[].    //    //  Could remember mappings in another hash, and change    //  back to original name on output (if original name not    //  already used on line).  Probably best not to though.    //    function goodname (name, value)    {        var res, prefix="", nn;        if (res = name.match(specpat)) {            prefix = res[1];            name   = res[3];        }        nn = alias[name];        if (!nn) {             // first encounter on this template            nn = name;            if (nn.search(/\w.*\w/) == -1 && value.search(/\w.*\w/) > -1)                nn = value.toUpperCase();            nn = nn.replace( /[^\w.\/&]/g,               "_");            nn = nn.replace( /_*([\W_])[\W_]*/g,        "$1");            nn = nn.replace( /^[\W_]*(.{0,4}[^\W_]).*/, "$1");            nn = nn.replace( /^.?$/,                    "A0001");            var base = nn;            var num  = 1;            while (nn in params && (params[nn] != value || prefix)) {                num++;                nn = base.slice(0, 5 - String(num).length) + num;            }            alias[name] = nn;        }        nn = prefix + nn;        if (! (nn in params)) {            order.push(nn);            params[nn] = value;        }        return nn;    }    //   FRANKLIN = Benjamin Franklin    FRANK    //   FRANKLIN = Frank N. Furter      FRAN2    boxstyle_FRANKLIN = red    //   FRANKLIN = Franklin Richards    FRAN3    //   FRANKLIN = Frank N. Furter               boxstyle_FRANKLIN = blue    for (var r=0; r < rows.length; r++) {        var row   = rows[r];        var seen  = {};        var alias = {};     // mapped to different name on this row?        var pattn = new RegExp("^\\s*" + template_names() + "\\s*$", "i");        if (row[0].search(pattn) == -1)            throw "Unrecognized template {{" + row[0] + "}}";        for (var i=0; i < Special.length; i++)            alias[Special[i]] = Special[i];    // don't truncate boxstyle        //  Pass 1:  Do only the assignments first, because if the        //  same parameter name is used on a previous row with a        //  different value, then we need to rename this parameter        //  and its boxes before they are output.        //        for (var c=1; c < row.length; c++)        {            var cell = row[c];            var i    = cell.indexOf("=");            if (i < 0 || cell == "=")                continue;            var name  = trim(cell.slice(0,i));            var value = trim(cell.slice(i+1));            if (value.indexOf("\n") >= 0)                toss ('Parameter "' + name + '" spans multiple lines.');            value = value.replace(/\n\s*/g, " ");            if (seen[name] && value != seen[name])                throw 'Parameter "' + name + '" has multiple values on template ' + (r+1);            seen[name] = value;            goodname(name, value);        }        //  Pass 2:  Now layout the tiles and boxes.        //        for (var c=1; c < row.length; c++)        {            var cell = trim(row[c]);            if (istile(cell) && ! (cell in seen))            {                art += pad(cell, 2);            }            else if (cell.indexOf("=") == -1)        // it's a BOX            {                cell = goodname(cell, cell.replace(/_/g, " ")).slice(0,5);                // Don't adjoin a {{(chart|tree chart)}} wide cell if can avoid                if (cell.length == 4 && /\w$/.test(art))                    cell = " " + cell;                art += ("  "+cell+"   ").substr(cell.length/2, 6);            }        }        art += "\n";    }    // list the parameter values, one per line    // TODO:  Styles referenced via [1], [2], etc    var param_width = 5;    for (var name in params)        if (name.length > 8)            param_width = 14;       // any boxstyle_FOO ?    var param_list = "";    while (name = order.shift()) {        param_list += pad(name, param_width) + " = " + (params[name] || "") + "\n";    }    parts.art  = art;    parts.list = param_list;}//  Make the art more readable by converting some symbols.//  Mainly just fills in --- and ~~~ horizontal lines for now.//  1.  Fill in a ~ tile followed by a ~ tile or a box//  2.  Fill in a box    followed by a ~ tile//  TOM  - v -  SUE    becomes    TOM ---v--- SUE//function touchup (art){    art = art.replace( /!/g, "|");    art = art.replace( /([,`^)}*+-]|\b[Xadijqrv]) (?=[.'^({*+-]|[acijlqrv]| ?\w\w)/g, "$1-");    art = art.replace( /([~%#\]]|\b[ADFLVfhy]) (?=[~%#[]|[7ACJKVXehy]| ?\w\w)/g,      "$1~");    art = art.replace( /(\w\w ? ?) (?=[.'^({*+-]|[acijlqrv]\b)/g, "$1-");    art = art.replace( /(\w\w ? ?) (?=[~%#[]|[7ACJKVXehy]\b)/g,   "$1~");    art = art.replace( /(\w\w ) (-|~)/g, "$1$2$2");    return art;}//  Trim and pad a multi-line diagram with spaces to its maximum//  width, adding a margin on both sides and a 1-line padded//  margin above and below.  Also tweaks the alignment if most//  of the alignment indicators are mis-aligned on odd.//  If margin is not given (wiki2art), it depends on the width.//function pad_text (text, margin){    // trim trailing spaces and leading and trailing lines    text = text.replace(/\t/g, "        ");    // just in case    text = text.replace(/ *\r*$/mg, "");    text = text.replace(/^\n*/, "\n");    text = text.replace(/\n*$/, "\n");    // trim indentation if not empty    while (text.search(/(^|\n).?\S|^\s*$/) == -1) {        text = text.replace(/^  /mg, "");    }    var rows  = text.split("\n");    var width = 0;    var align = 0;    var alignpat = /[^\w\s=~&\/\[\].-]|[A-Z0-9]+([\/&._]?[A-Z0-9])+/ig;    var res;    for (var i=0; i < rows.length; i++) {        width = Math.max(width, rows[i].length);        //  Are majority of alignment indicators on odd or even?        //        while ((res = alignpat.exec(rows[i])) != null) {            var len = res[0].length;            if (len % 2)              // even boxes are ambiguous                ((res.index + len/2) & 1) ? align-- : align++;        }    }    //  If formatting for display, center diagram on column 40, but    //  at least a 4-cell left margin unless close to max width.    //  The margin gives room to draw another box on the left, and    //  you can then toggle view twice to indent another 4 cells.    //    if (margin == null) {        margin = Center - width / 2;        margin = Math.max(margin & ~1, 8);        if (width/2 + margin > Maxwidth)            margin = 0;    }    else if (align < 0)        margin++;    margin = pad("", margin);    text   = "";    for (var i=0; i < rows.length; i++) {        text += margin + pad(rows[i], width) + margin + "\n";    }    return text;}//  Pad str with spaces on right to width len, but don't truncate.//function pad (str, len){    if (str.length < len)        str += Array(len - str.length + 1).join(" ");    return str;}function trim (str){    return str.replace(/^\s+|\s+$/g, "");}function art2wiki(){    try {        var textarea = document.editform.wpTextbox1;        var scroll_pos = textarea.scrollTop;        var pattern =  new RegExp(tree_pattern(), "ig");        textarea.value = textarea.value.replace(pattern, art2wiki_replace);        textarea.removeAttribute("wrap");        textarea.style.display = "inline";    // Firefox work-around        textarea.scrollTop = scroll_pos;      // Firefox only?        document.editform.wpSave.disabled = false;        update_menu ("wiki2art");        if (document.editform.wpSummary.value.search(/^(\/\* .* \*\/)? *$/) == 0)            document.editform.wpSummary.value += Summary.replace("%s", Template);    }    catch (e) {        alert ("Could not convert ASCII art because:\n\n" + e);    }}function art2wiki_replace (text, tmpl){    var label      = {};    var param_rows = [];    Template = tmpl.toLowerCase();    rows     = [];    boxes    = [];    if (text.indexOf("\n") == -1)        return text;                // don't convert a 1-line legend    //  Sanity check, if any lines begin with {{...    //    if (text.search(/\n\s*\{\{.*\n/) != -1) {        toss ("Out of sync; looks like this already is wikitext.");        return text;    }    var res = text.match(/^(.*}})([\S\s]*)\{\{/);    if (res == null)        throw "Didn't find end of /start tag on same line";    parse_art (res[2], label,rows);    map_boxes (rows, boxes);    map_tiles (boxes,rows, param_rows);    crop_rows (param_rows);    var temps = to_wikitext (label, param_rows);    var start = summarize (res[1], boxes.count);    return start + "\n" + temps + "{{" + tmpl + "/end}}";}//  Parse the simple ASCII art, storing the diagram in//  rows[] and the labels in label{}//function parse_art (text, label,outrows){    // remove any rulers or comments (messages)    text = text.replace(/^.*1-2-3-4-5-6-7-8-9.*\n/mg, "");    text = text.replace(/^ *\/\/.*/mg, "");    // Parse the name=value definitions into label{}.    // We're as flexible as possible, allowing defs    // with no RHS, defs in multiple columns, and    // defs quickly jotted to the right of the art.    // However, a value cannot span lines. And assume    // foo===bar is part of the art, where === is ---.    // AAA=Freddy overrides AAA=AAA overrides AAA=    //    text = text.replace(/([^\s=]+) *=(?!=) *(.*?)(\t|   (?=.*\w.*=)| *$)/mg,         function (str,name,value) {            if (! /\w/.test(name))      // art                return str;            if (! label[name] || label[name] == name && value)                label[name] = value;            if (value != label[name] && value != name && value)                throw 'Parameter "' + name + '" has multiple values.';            return "";        });    // Treat ..... same as ~~~~~    text = text.replace(/\.{3,}/g, function(s){ return s.replace(/./g, "~"); });    text = pad_text(text, 4);    var a = text.slice(0,-1).split("\n");    while (a.length)        outrows.push(a.shift());    // At this point, outrows[] should contain the diagram padded    // to the maximum width with two extra blank cells on each    // side (1 box overlap + 1 neighbor) and with the vertical    // lines aligned on the even characters (assuming diagram is    // consistent in this).}//  Find which cells are occupied by boxes, even if the box//  names are real short (must be at least 2 characters) or//  real long.  Doing this first makes processing the tiles//  easier.  Returns the 2D boxes array.//function map_boxes (rows, boxes){    var namepat = /[A-Z0-9]+([\/&._]?[A-Z0-9])+/ig;    var row, map, res, name, pos;    boxes.count = 0;    for (var i=0; i < rows.length; i++) {        row = rows[i];        map = new Array(row.length);        while ((res = namepat.exec(row)) != null) {            name = res[0];            //  Handle cases where wide {{(chart|tree chart)}} tiles look like boxes.            //  If it looks like they could be tiles, then they're tiles,            //  else they're boxes.  We rely on user to not use ambiguous            //  box names like a2b2c (though names like a2 and a2b should            //  actually work as long as they remain aligned on odd).            //            if (Template.match(/^(chart|tree chart)/i) && res.index % 2 == 0)            {                while (name.search(/^[a-z]2[^\W_].../) == 0) {                    name = name.slice(2);                    res.index += 2;                }                //  Tiles: m2 m2P m2n2 m2n2P   Boxes: m2ab m2abc m2Pn2                if (name.search(/^([a-z]2)*.?$/) == 0)                    continue;                //  Also allow convenience shortcut of  SPPPRPPPPPP                //  to be used as alternative to        S P R P P P                if (name.search(/^(?=.*PPP)([bmnoPSYWMHR]P){3,}.?$/) == 0)                    continue;            }            //  Even allow on odd alignment if it's all PPPPPPPPPPPs            if (Template.match(/^(chart|tree chart)/i) && name.search(/^P{6,}.$/) == 0)                continue;            if (name.length % 2 == 1 && res.index % 2 == 0)                toss (name + " is aligned ambiguously");            pos = (res.index + name.length / 2) & ~1;            if (map[pos-2])                throw "box [" + name + "] overlaps [" + map[pos-2] + "]";            map[pos-2] = name;            map[pos]   = name;            map[pos+2] = name;            //  Blank out the name.  If it's a long name (>5) and            //  a horizontal line joins it, extend the line into            //  the extra space from shortening the name.            var before = row.slice(0, res.index);            var blank  = name.replace(/./g, " ");            var after  = row.slice(res.index + name.length);            var half   = name.length / 2;            if (res = before.match(/(-|~) ?$/))                blank = Array((half+1)|0).join(res[1]) + blank.slice(half);            if (res = after.match(/^ ?(-|~)/))                blank = blank.slice(0,half) + Array((half+1.6)|0).join(res[1]);                             row = before + blank + after;            boxes.count++;            if (row.slice(pos-2, pos+3).search(/[^\s[\]P~=_-]/) >= 0)                toss ("A tile overlaps box [" + name + "]");        }        boxes.push(map);        rows[i] = row;    }}function map_tiles (boxes,rows, param_rows){    Tile.invert_symbols();    for (var r=1; r < rows.length-1; r++)    {        var row    = rows[r];        var params = [];        var res = row.match(/^.(..)*?([^\s[\]P~=_-])/);        if (res)            toss (res[2] + " is mis-aligned on row " + r);        for (var c=2; c < row.length-2; c += 2)        {            if (boxes[r][c]) {                params.push( boxes[r][c] );                c += 4;            }            else {                var t = new Tile(r,c);                t.tweak(r-1, c, 0);                t.tweak(r+1, c, 2);                t.tweak(r, c-2, 3);                t.tweak(r, c+2, 1);                params.push( t.symbol() );            }        }        param_rows.push(params);    }}//  Crop unneeded spaces from beginnings and ends of parameter//  lists if entire columns are unused.  The rows are assumed//  to be the same virtual width.  If a margin is desired, use//  {{family *tree/start| style=margin:1em}}, not empty rows/columns.////  (In rare cases there could also be leading/trailing rows that//  are empty, but don't crop them. Should only happen if these//  lines were blank exept for character(s) in the odd cells.//  Which shouldn't happen by accident.)//function crop_rows (rows){    var min = 9999;    var max = 0;    //  Find first and last columns used    //    for (var r=0; r < rows.length; r++) {        var params = rows[r];        var col    = 0;         // virtual column / width        var first  = 9999;      // first used column        var last   = 0;         // last used column        for (var i=0; i < params.length; i++) {            var param = params[i];            if (param != ' ' && first > col)                first = col;            if (! istile(param))                col += 2;          // it's a 3-wide box            if (param != ' ')                last = col;            col++;        }        min = Math.min(min, first);        max = Math.max(max, last);    }    if (min > max)  return;        // all blank    var extra = col - max - 1;     // amount to trim on right    // Now crop leading and trailing params in blank columns.    // Though the param list lengths vary, their virtual widths    // should all be the same, and will continue to be consistent    // after shaving the same amount off of each end.    //    for (r=0; r < rows.length; r++) {        rows[r].splice(0, min);        rows[r].splice(rows[r].length - extra, extra);    }}function to_wikitext (label, rows){    var style      = Style || new MarkupStyle();    var result     = "";    var first_part = "{{" + Template + style.initial;    var label_used = {};    var i, attr;    for (i=0; i < Special.length; i++) {        attr = Special[i];        if (attr in label) {            first_part += "|" + attr + "=" + label[attr];            label_used[attr] = 1;        }    }    for (var r=0; r < rows.length; r++)    {        var params    = rows[r];        var seen      = {};        var last_part = "";        var param;        result += first_part;        while (param = params.shift()) {            result += "|";            if (istile(param) && !(param in label)) {                result += param;                continue;            }            if (! (param in seen)) {                seen[param] = 1;                if (param in label) {                    last_part += "|" + style.param(param, label[param]);                    label_used[param] = 1;                }                for (i=0; i < Special.length; i++) {                    attr = Special[i] + "_" + param;                    if (attr in label) {                        last_part += "|" + style.param(attr, label[attr]);                        label_used[attr] = 1;                        seen[param]      = 2;                    }                }            }            //  If param.length < 5, center it so it looks better.            //  Unless it's used in any per-box attributes like boxstyle_FOO,            //  in which case it must be flush left to work correctly.            if (seen[param] == 2 || param.length > 5)                result += pad(param, 5);            else                result += ("  "+param+"  ").substr(param.length/2, 5);        }        if (style.trim)            result = result.replace(/(\| )+$/g, "");    // trim empty cells        result += last_part + "}}\n";    }    var unused = "";    for (i in label) {        if (! (i in label_used) && label[i] && label[i] != i)            unused += "|" + style.param(i, label[i]);    }    if (unused)        result += "<!-- Unused parameters: -->\n" +                  "{{" + Template + style.initial + unused + "}}\n";    return result;}//  Create a slightly more useful summary than the default.//  The user is hoped to revise this to a more meaningful summary//  than can be calculated automatically. For example:////  summary = family *tree diagram for Barack Obama, connecting//            29 individuals in 4 generations.  Generations are//            arranged in rows, with Barack appearing 3rd on the//            3rd such row.//function summarize (tag, count){    if (tag.search(/\|\s*summary\s*=/) == -1)        tag = tag.replace(/}}$/,           "| summary=Boxes and lines diagram with " + count + " boxes}}");    else        tag = tag.replace(/\d+(?= (boxes|nodes|individuals))/, count);    return tag;}function istile (sym){    return sym.length <= 1 ||           Template.match(/^(chart|tree chart)/i) && /^[a-z]2$/.test(sym);}function Tile(r,c){    var a = get_tile(r,c);    this.orig_sym = a[0];    this.sides    = a[1].slice(0,4);   // copy vs ref    this.weight   = a[1][4];    // If edge is a line but next tile not same with > weight, change it    // If edge is blank  but next tile is line with >= weight, change it    //    this.tweak = function (r,c,dir)    {        var neighbor = get_tile(r,c);        var specs    = neighbor[1];        var ne_line  = specs[dir ^ 2];        var us_line  = this.sides[dir];        if (us_line > 0  && ne_line != us_line && specs[4] > this.weight ||            us_line == 0 && ne_line > 0        && specs[4] >= this.weight)                this.sides[dir] = ne_line;    }    this.symbol = function()    {        var ch = new_symbol[this.sides];        if (ch == null || /[ :~!-]/.test(ch))            ch = this.orig_sym;        return ch;    }    function get_tile(r,c)    {        if (boxes[r][c])            return ["BOX", [0, 0, 0, 0, 20]];        var ch  = rows[r].charAt(c);        var ch2 = rows[r].charAt(c+1);        if (/[ P_=~-]/.test(ch) && /[^ [\]P_=~-]/.test(ch2))    // mis-aligned?            ch = ch2;        if (/\w/.test(ch) && ch2 == '2')              // {{(chart|tree chart)}} long symbol?            ch += '2';        if (ch == '|' || ch == '1')            ch = '!';        if (ch == '_' || ch == '=')            ch = '-';        var specs = symbols[ch] || [0, 0, 0, 0, 20];        if (specs.length > 5 && Template.match(/^(chart|tree chart)/i))    // t, T, k, G            specs = specs.slice(5);        return [ch, specs];    }}//  Build reverse lookup table needed by Tile objects.//  There is some conflict between the {{family *tree}} and {{chart}} or {{tree chart}} symbols.//  A few recently-added symbols map to different specs, and some specs//  map back to different symbols.  Hence the extra logic here depending//  on the current Template family.//Tile.invert_symbols = function(){    new_symbol = {};    var start = Template.match(/^(chart|tree chart)/i) ? -5 : 0;    for (var sym in symbols) {        var nesw = symbols[sym].slice(start,start+4).join();        if (! (nesw in new_symbol) || Template.match(/^(chart|tree chart)/i) )            new_symbol[nesw] = sym;    }}function toss (msg)            // Soft throw.{    if (Picky) throw msg;}// I haven't tuned many of these weights yet.// Hopefully we won't need to go to per-edge weights.////        Doubt://        0   space//        1   ^ v ( )//        2   - ! ~ ://        3   + . , ' ` / \ BOXvar new_symbol = {};var symbols = {//              N, E, S, W, Weight        " " : [ 0, 0, 0, 0, 90 ],        "-" : [ 0, 1, 0, 1, 50 ],        "!" : [ 1, 0, 1, 0, 50 ],        "+" : [ 1, 1, 1, 1, 20 ],        "," : [ 0, 1, 1, 0, 20 ],        "." : [ 0, 0, 1, 1, 20 ],        "`" : [ 1, 1, 0, 0, 20 ],        "'" : [ 1, 0, 0, 1, 20 ],        "^" : [ 1, 1, 0, 1, 70 ],        "v" : [ 0, 1, 1, 1, 70 ],        "(" : [ 1, 0, 1, 1, 70 ],        ")" : [ 1, 1, 1, 0, 70 ],        "~" : [ 0, 2, 0, 2, 50 ],        ":" : [ 2, 0, 2, 0, 50 ],        "%" : [ 2, 2, 2, 2, 20 ],        "F" : [ 0, 2, 2, 0, 20 ],        "7" : [ 0, 0, 2, 2, 20 ],        "L" : [ 2, 2, 0, 0, 20 ],        "J" : [ 2, 0, 0, 2, 20 ],        "A" : [ 2, 2, 0, 2, 70 ],        "V" : [ 0, 2, 2, 2, 70 ],        "C" : [ 2, 0, 2, 2, 70 ],        "D" : [ 2, 2, 2, 0, 70 ],        "*" : [ 2, 1, 2, 1, 51 ],        "#" : [ 1, 2, 1, 2, 51 ],   // don't tweak ---#---        "h" : [ 1, 2, 0, 2, 33 ],        "y" : [ 0, 2, 1, 2, 33 ],        "{" : [ 2, 0, 2, 1, 33 ],        "}" : [ 2, 1, 2, 0, 33 ],        "t" : [ 2, 1, 0, 1, 33,   1, 2, 1, 2, 51 ],        "[" : [ 1, 0, 1, 2, 33 ],        "]" : [ 1, 2, 1, 0, 33 ],        "X" : [ 2, 1, 2, 2, 33 ],        "T" : [ 0, 1, 2, 2, 33,   0, 0, 3, 3, 20 ],        "K" : [ 2, 0, 1, 2, 33 ],        "k" : [ 1, 0, 2, 2, 33,   3, 1, 3, 0, 33 ],        "G" : [ 2, 2, 1, 0, 33,   3, 0, 3, 3, 70 ],              // chart        "P" : [ 0, 3, 0, 3, 50 ],        "Q" : [ 3, 0, 3, 0, 50 ],        "R" : [ 3, 3, 3, 3, 20 ],        "S" : [ 0, 3, 3, 0, 20 ],        "Y" : [ 3, 3, 0, 0, 20 ],        "Z" : [ 3, 0, 0, 3, 20 ],        "W" : [ 3, 3, 0, 3, 70 ],        "M" : [ 0, 3, 3, 3, 70 ],        "H" : [ 3, 3, 3, 0, 70 ],        "c" : [ 2, 0, 2, 1, 33 ],        "d" : [ 2, 1, 2, 0, 33 ],        "i" : [ 2, 1, 0, 1, 33 ],        "j" : [ 0, 1, 2, 1, 33 ],        "e" : [ 1, 0, 1, 2, 33 ],        "f" : [ 1, 2, 1, 0, 33 ],        "a" : [ 3, 1, 3, 1, 51 ],        "b" : [ 1, 3, 1, 3, 51 ],   // don't tweak ---b---        "l" : [ 3, 0, 3, 1, 33 ],        "m" : [ 0, 3, 1, 3, 33 ],        "n" : [ 1, 3, 0, 3, 33 ],        "o" : [ 1, 3, 1, 0, 33 ],        "p" : [ 1, 0, 1, 3, 33 ],        "q" : [ 3, 1, 0, 1, 33 ],        "r" : [ 0, 1, 3, 1, 33 ],       "a2" : [ 3, 2, 3, 2, 54 ],       "b2" : [ 2, 3, 2, 3, 54 ],       "k2" : [ 3, 2, 3, 0, 44 ],       "l2" : [ 3, 0, 3, 2, 44 ],       "m2" : [ 0, 3, 2, 3, 44 ],       "n2" : [ 2, 3, 0, 3, 44 ],       "o2" : [ 2, 3, 2, 0, 44 ],       "p2" : [ 2, 0, 2, 3, 44 ],       "q2" : [ 3, 2, 0, 2, 44 ],       "r2" : [ 0, 2, 3, 2, 44 ]};window.wiki2art = wiki2art;     // expose to HTML linkwindow.art2wiki = art2wiki;if (document.editform) {    var textbox = document.editform.wpTextbox1;    var pattn = new RegExp(tree_pattern(), "ig");    var res = textbox.value.match(pattn);    if (res) {        Template = res[1];        pattn = new RegExp("^\\s*\\{\\{" + template_names() + "\\s*\\|", "mi");        if (res[0].search(pattn) > 0)            update_menu ("wiki2art");        else            update_menu ("art2wiki");    }}} );    // end of script and addOnloadHook() wrapper// </nowiki>