User:Koopinator/vector.js

// <nowiki> // Wiki user script to help maintain {{familytree}} or {{chart}} // boxes-and-lines diagrams, by allowing you to edit the diagram // in a simp

// <nowiki>
// Wiki user script to help maintain {{familytree}} or {{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
//
// To install, add:   importScript("User:Daduxing/familytree.js");
// to your vector.js (or monobook.js) page.  This adds an option
// to the toolbox menu when editing familytrees.
//
// 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

addOnloadHook (function() {      // wraps entire script

var Summary  = "Edited {{%s}} using [[User:GregU/familytree.js|familytree.js]]";
var Special  = [ "border", "boxstyle", "colspan", "rowspan" ];
var Template;            // familytree or chart ?
var Style    = null;
var Center   = 40;       // center small diagrams on this column
var Maxwidth = 80;
var Picky    = 0;        // complain instead of self-correct?
var rows;
var boxes;


//  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 = /\{\{(familytree|chart)\/start[\S\s]*?\{\{\w+\/end}}/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 == "chart" ? 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 {{familytree/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 {{familytree|...|...}} 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?

        if (row[0].search(/^\s*(familytree|chart)\s*$/i) == -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}} 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 = /\{\{(familytree|chart)\/start[\S\s]*?\{\{\w+\/end}}/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}} 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 == "chart" && 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 == "chart" && 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
//  {{familytree/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 == "chart" && /^[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}} 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 == "chart")    // 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 {{familytree}} and {{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 == "chart") ? -5 : 0;

    for (var sym in symbols) {
        var nesw = symbols[sym].slice(start,start+4).join();
        if (! (nesw in new_symbol) || Template == "chart")
            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   + . , ' ` / \ BOX

var 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 link
window.art2wiki = art2wiki;

if (document.editform) {
    var textbox = document.editform.wpTextbox1;
    var res = textbox.value.match(/\{\{(familytree|chart)\/start[\S\s]*\{\{\w+\/end/i);
    if (res) {
        Template = res[1];
        if (res[0].search(/^\s*\{\{(familytree|chart)\s*\|/mi) > 0)
            update_menu ("wiki2art");
        else
            update_menu ("art2wiki");
    }
}

} );    // end of script and addOnloadHook() wrapper
// </nowiki>

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.