|
|
Rad 1: |
Rad 1: |
− | /* JavaScript här kommer att laddas för dem som använder skalet Monobook */
| + | importScript("User:Martinbrandt/familytree.js"); |
− | // 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:GregU/familytree.js");
| |
− | // to your monobook.js file. 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 = /\{\{(släktträd|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 {{släktträd/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 {{släktträd|...|...}} 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*(släktträd|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 = /\{\{(släktträd|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
| |
− | // {{släktträd/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 {{släktträd}} 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(/\{\{(släktträd|chart)\/start[\S\s]*\{\{\w+\/end/i);
| |
− | if (res) {
| |
− | Template = res[1];
| |
− | if (res[0].search(/^\s*\{\{(släktträd|chart)\s*\|/mi) > 0)
| |
− | update_menu ("wiki2art");
| |
− | else
| |
− | update_menu ("art2wiki");
| |
− | }
| |
− | }
| |
− |
| |
− | } ); // end of script and addOnloadHook() wrapper
| |