MediaWiki:Gadget-Pikan-core.js: Difference between revisions

From Pikipedia, the Pikmin wiki
Jump to navigation Jump to search
(Made it not detect uppercase problems if it's the first word in a section header.)
No edit summary
Line 1: Line 1:
// <nowiki>
// ==UserScript==
// ==UserScript==
// @name        Pikan - Pikipedia analyzer
// @name        Pikan - Pikipedia analyzer
Line 1,161: Line 1,162:


pikan.setup();
pikan.setup();
// </nowiki>

Revision as of 17:23, June 4, 2015

// <nowiki>
// ==UserScript==
// @name        Pikan - Pikipedia analyzer
// @namespace   Pikipedia
// @description Analyzes a wiki page while editing to make sure it follows the standards.
// @include     http://www.pikminwiki.com/index.php?title=*&action=edit*
// @include     http://www.pikminwiki.com/index.php?title=*&action=submit*
// @version     1.0.0
// @author      André "Espyo" Silva
// @grant       none
// ==/UserScript==

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Configs
 * * * * * * * * * * * * * * * * * * * * * * * * */
 
var pikan = pikan || {};

pikan.wiki_url = "http://pikminwiki.com/";

pikan.help_link = "http://pikminwiki.com/User:Espyo/Pikan"
pikan.text = "";
pikan.textbox = null;
pikan.prob_total_tag = null;
pikan.prob_detail_tag = null;
pikan.prob_nr_tag = null;
pikan.cur_prob_nr = -1;
pikan.problems = [];
pikan.types = {
  PROBLEM:  "Problem",
  POLICY:   "Policy",
  STYLE:    "Style",
  QUESTION: "Question"
};
pikan.type_colors = [];
pikan.type_colors[pikan.types.PROBLEM]  = "#600";
pikan.type_colors[pikan.types.POLICY]   = "#400";
pikan.type_colors[pikan.types.STYLE]    = "#206";
pikan.type_colors[pikan.types.QUESTION] = "#420";
pikan.type_backgrounds = [];
pikan.type_backgrounds[pikan.types.PROBLEM]  = "rgba(255, 128, 128, 0.8)";
pikan.type_backgrounds[pikan.types.POLICY]   = "rgba(192, 128, 128, 0.8)";
pikan.type_backgrounds[pikan.types.STYLE]    = "rgba(160, 128, 255, 0.8)";
pikan.type_backgrounds[pikan.types.QUESTION] = "rgba(255, 192, 128, 0.8)";
pikan.type_descriptions = [];
pikan.type_descriptions[pikan.types.PROBLEM]  = "General MediaWiki source or general article problems.";
pikan.type_descriptions[pikan.types.POLICY]   = "Issues that break a policy, specific or implied.";
pikan.type_descriptions[pikan.types.STYLE]    = "Problems that do not affect the page, but when fixed, make the source easier to use.";
pikan.type_descriptions[pikan.types.QUESTION] = "Results that the analyzer cannot decide whether they're real problems or false-positives.";


/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Find problems
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * For every possible problem, the text is scanned
 * for instances of said problem. If you want to
 * customize the analyzer, you edit this function.
 */
pikan.find_problems = function() {

  //Setup.
  pikan.text = pikan.textbox.value;
  pikan.clear();
  
  
  
  //--------------------
  
  
  
  var reg;

  //Find "Wikia" -- we aren't involved with Wikia, so get rid of leftovers.
  reg = /wikia/igm;
  pikan.regex_check(reg, pikan.types.PROBLEM, "\"Wikia\" found. We moved from Wikia years ago, so make sure this isn't a leftover.");
  
  
  //Find "gamepad"
  reg = /[Gg]amepad/gm;
  pikan.regex_check(reg, pikan.types.PROBLEM, "\"Gamepad\" found. The correct spelling for the Wii U's controller is \"GamePad\".");
  
  
  //Find "Nunchuck"
  reg = /Nunchuck/igm;
  pikan.regex_check(reg, pikan.types.PROBLEM, "\"Nunchuck\" found. The correct spelling of the Wii peripheral is \"Nunchuk\".");
  
  
  //Find "Gamecube"
  reg = /Gamecube|Game [cC]ube/gm;
  pikan.regex_check(reg, pikan.types.PROBLEM, "\"Gamecube\" found. The correct spelling for the Nintendo console is \"GameCube\".");
  
  
  //Find sub-optimal names for the Smash 4 games.
  reg = /(Super Smash Bros\.? for Wii U and 3DS|Super Smash Bros\.? for 3DS & Wii U|Super Smash Bros\.? for Wii U & 3DS|Super Smash Bros. 4)/igm;
  pikan.regex_check(reg, pikan.types.PROBLEM, "Sub-optimal name for the Smash game found. For consistency's sake, you should only use these names: \"Super Smash Bros. for 3DS and Wii U\", \"Super Smash Bros. for 3DS\", \"Super Smash Bros. for Wii U\".");
  
  
  //Find bad names for the inhabitants of Hocotate/Koppai.
  reg = /(Hocotaitt?e|Kopp?aitt?i9an|Kopaitt?e|Kopp?aii?an)/igm;
  pikan.regex_check(reg, pikan.types.PROBLEM, "Bad inhabitant term found. Characters from Hocotate are \"Hocotatian(s)\", and those from Koppai are \"Koppaite(s)\".", "Category:Places");
  
  
  //Captions in thumbs that don't exist or lack a period.
  reg = /^\[\[File\:.+\|thumb.*\]\]/igm;
  pikan.regex_check(
    reg, pikan.types.POLICY, "Image thumbs must have a caption, and it must end with a period.", "Pikipedia:Policy#Frames_and_galleries",
    function(r){
      if(r[0][r[0].length - 3] != ".") return true;
      return false;
    }
  );
  
  
  //Captions in galleries that don't exist or lack a period.
  reg = /^.+$/igm;
  pikan.regex_check(
    reg, pikan.types.POLICY, "Images in galleries must have a caption, and it must end in a period.", "Pikipedia:Policy#Frames_and_galleries",
    function(r){
      if(!pikan.in_area(r, "<gallery>", "</gallery>")) return false;
      var slash_pos = r[0].indexOf("|");
      if(slash_pos == -1 || slash_pos == r[0].length - 1) return true;
      if(r[0][r[0].length - 1] != ".") return true;
      return false;
    }
  );
  
  
  //Find game names without italicization.
  reg = /(New Play Control! Pikmin 2|New Play Control! Pikmin|Pikmin 2|Pikmin 3|Pikmin Adventure|Nintendo Land|Super Smash Bros\. for 3DS and Wii U|Super Smash Bros\. (Melee|Brawl))/igm;
  pikan.regex_check(
    reg, pikan.types.POLICY, "Game names should be in italics.", "Pikipedia:Policy#Italicizing",
    function(r){
      if(pikan.is_common_exception(r)) return false;
      if(
        pikan.before(r, 2) == "''" &&
        pikan.after(r, 2)  == "''"
      ){
        return false;
      }
       
      //Check if the name is a link caption.
      //Let's cheat a bit and assume that
      //a) the link's text only contains the game name
      //(otherwise the editor would need the bold inside the link)
      //b) the italics are placed correctly before the link.
      if(pikan.after(r, 4)  == "]]''") return false;
       
      return true;
    }
  );
  
  
  //Find "Pikmin 1" in text.
  reg = /Pikmin 1/igm;
  pikan.regex_check(
    reg, pikan.types.POLICY, "\"Pikmin 1\" found; that's not what the first game is called, so change it to Pikmin.", "Pikipedia:Policy#.22Pikmin_1.22",
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //Find "Pikmin 1" in links.
  reg = /\[\[Pikmin 1(\||\]\])/igm;
  pikan.regex_check(
    reg, pikan.types.POLICY, "\"Pikmin 1\" found; that's not what the first game is called, so link the page to \"Pikmin (game)\".", "Pikipedia:Policy#.22Pikmin_1.22");
  
  
  //Find "Pikmins".
  reg = /pikmins/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "\"Pikmins\" found. The plural of \"Pikmin\" is \"Pikmin\".", "Pikmin_family#Name",
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //Find lowercase "Pikmin".
  reg = /pikmin/gm;
  pikan.regex_check(
    reg, pikan.types.POLICY, "\"pikmin\" with lowercase 'p' found. The official spelling is always with an uppercase 'P'.", "Pikipedia:Policy#Capitalization",
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //Find lowercase Pikmin names.
  reg = /(red|yellow|blue|purple|white|rock|winged) Pikmin/gm;
  pikan.regex_check(
    reg, pikan.types.POLICY, "Lowercase Pikmin name found. Pikmin type names should start in uppercase.", "Pikipedia:Policy#Capitalization",
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //Find lowercase "Onion".
  reg = /onion/gm;
  pikan.regex_check(reg, pikan.types.POLICY, "\"onion\" with a lowercase 'o' found. The official spelling is with an uppercase 'O'.", "Pikipedia:Policy#Capitalization",
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //Find words that start in uppercase needlessly.
  reg = /\b(Area|Bitter|Bomb-?[Rr]ock|Bomb [Rr]ock|Boulder|Bridge|Cardboard [Bb]ox|Cave|Climbing [Ss]tick|Clipboard|Clog|Crystal|Day|Dirt [Mm]ound|Egg|Electrode|Elevator [Pp]latform|Fire [Gg]eyser|Fragment|Gate|Leader|Nectar|Paper [Bb]ag|Spicy|Spray|Sublevel|Ultra|Wall)\b/gm;
  pikan.regex_check(
    reg, pikan.types.POLICY,
    "Common word starting in uppercase found. This word should likely start in lowercase, as there is no reason for it to start with a capital mid-sentence.",
    "Pikipedia:Policy#Capitalization",
    function(r){
      if((pikan.must_be_upper(r))) return false;
      if(pikan.is_common_exception(r)) return false;
      return true;
    }
  );
  
  
  //Find "NTSC" and "PAL".
  reg = /\b(NTSC|PAL)\b/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "\"NTSC\" or \"PAL\" found. Try \"US\" or \"Europe\" instead.", "Pikipedia:Policy#Region",
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //Find "alpha" and "beta".
  reg = /\b(alpha|beta)\b/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "\"Alpha\" or \"beta\" found. Try \"early\" or \"prototype\" instead.", "Pikipedia:Policy#Prototypes",
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //Find P1 area names without the "The".
  reg = /(Impact Site|Forest of Hope|Forest Navel|Distant Spring|Final Trial)/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "Bad area name found. Areas from the first Pikmin game should start with \"The\".", null,
    function(r){
      return !(pikan.before(r, 4) == "The ");
    }
  );
  
  
  //Find links to [[Pikmin]].
  reg = /\[\[Pikmin(\|.+)?\]\]/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "Link to \"Pikmin\" found. Make this a direct link to the family, series or game article.");
  
  
  //References not using {{refs}}.
  reg = /<references ?\/>/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "&lt;references/&gt; tag found. Use {{refs}} instead.", "Pikipedia:Policy#Miscellaneous");
  
  
  //Report if there is no "See also" section.
  reg = /= *See also *=/igm;
  if(reg.exec(pikan.text) === null) {
    pikan.save_problem(0, 0, pikan.types.POLICY, "\"See also\" section not found.", "Pikipedia:Policy#General");
  }


  //"Battle Strategy" sections should only be called "Strategy".
  reg = /^=+ +(Battle|Killing) Strategy +=+$/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "Strategy sections should be called \"Strategy\".", "Pikipedia:Policy#Order_for_common_sections");
  
  
  //"External links" sections with bad names.
  reg = /^=+ +(Web Links|External Pages) +=+$/igm;
  pikan.regex_check(reg, pikan.types.POLICY, "External link sections should be called \"External links\".", "Pikipedia:Policy#Order_for_common_sections");
  
  
  //Links in section headers.
  reg = /==.*\[\[.+\]\].*==/gm;
  pikan.regex_check(reg, pikan.types.POLICY, "Section headers shouldn't have links.", "Pikipedia:Policy#Headings");
  
  
  //Bold in section headers.
  reg = /==.*'''.+'''.*==/gm;
  pikan.regex_check(reg, pikan.types.POLICY, "Section headers shouldn't have bold text.", "Pikipedia:Policy#Headings");
  
  
  //Title case section header.
  reg = /^\=+ +[A-Z].+ +[A-Z].*\=+$/gm;
  pikan.regex_check(reg, pikan.types.QUESTION, "This section header seems to be in title case. Make sure you don't capitalize words unless they are a title.", "Pikipedia:Policy#Headings");
  
  
  //Find level 1 headers.
  reg = /^=[^=]+=$/gm;
  pikan.regex_check(reg, pikan.types.POLICY, "Level 1 section found. Only the page title should be a level 1 header, so downgrade this to level 2.");
  
  
  //Common section order.
  reg = /^==([^=]+)==$/gm;
  var sec = null;
  var secs = [];
  var order = ["Glitches", "Technical information", "Gallery", "Trivia", "Naming", "Names in other languages", "See also", "External links", "References"];
  while((sec = reg.exec(pikan.text)) !== null){
    var name = sec[1].trim();
    var is_common_section = false;
    for(var n = 0; n < order.length; n++){
      if(name == order[n]) is_common_section = true;
    }
    if(!is_common_section) continue;
    secs.push({
      name: name,
      pos:  sec.index,
      len:  sec[0].length
    });
  }
  var cur_o = -1;
  var disorderer = -1;
  for(var s = 0; s < secs.length; s++){
    do{
      cur_o++;
    }while(cur_o < order.length && order[cur_o] != secs[s].name);
    if(!(cur_o < order.length && order[cur_o] == secs[s].name)){
      disorderer = s;
      break;
    }
  }
  if(disorderer != -1){
    pikan.save_problem(secs[disorderer].pos, secs[disorderer].len, pikan.types.POLICY, "Section \"" + secs[disorderer].name + "\" appears after section \"" + secs[disorderer - 1].name + "\"; it should appear before.", "Pikipedia:Policy#Order_for_common_sections");
  }
  
  
  //Report if there are no images.
  reg = /\[\[File:/igm;
  if(reg.exec(pikan.text) === null) {
    reg = /<gallery/igm;
    if(reg.exec(pikan.text) === null) {
      reg = /\{\{image/igm;
      if(reg.exec(pikan.text) === null) {
        pikan.save_problem(0, 0, pikan.types.POLICY, "No image found; articles should have at least one image. Either add some or add the template {{image}}.", "Pikipedia:Policy#General");
      }
    }
  }
  
  
  //Mention of the subject on the first paragraph in bold.
  //Let's assume a line is a paragraph of content if it starts
  //with ', ", a letter/number, or a link.
  reg = /^('|"|[a-z0-9]|\[).+$/igm;
  var done = false;
  while(!done){
    first_paragraph_match = reg.exec(pikan.text);
    if(!first_paragraph_match) break;
    
    //If it's a template, skip this line.
    if(pikan.in_area(first_paragraph_match, "{{", "}}")) continue;
    //If it's an image, skip this line.
    if(first_paragraph_match[0].indexOf("[[File:") != -1) continue;
    
    //First line of text found.
    if(first_paragraph_match[0].indexOf("'''") == -1){
      pikan.save_problem(0, 0, pikan.types.QUESTION, "The first mention of the subject on the first paragraph should be in bold. It seems that this is not true. If it is, or this policy does not apply here, ignore this.", "Pikipedia:Policy#General");
      break;
    }
    done = true;
  }
  
  
  //"You" outside of guide sections.
  reg = /\b(you|your|you're)\b/igm;
  pikan.regex_check(
    reg, pikan.types.QUESTION, "The player/reader is referred to as \"you\". This is only okay on guide sections, and it looks like this section isn't one, as it's missing the {{guide}} template.", "Pikipedia:Policy#Perspective",
    function(r){
      var guide_reg = /\{\{guide/igm;
      var guide_tem_match = null;
      var you_section_nr = pikan.get_section_nr(r);
      while(guide_tem_match = guide_reg.exec(pikan.text)){
        if(pikan.section_in_section(
          you_section_nr,
          pikan.get_section_nr(guide_tem_match)
        )){
          return false;
        }
      }
      return true;
    }
  );
  
  
  //Find "color".
  reg = /colou?r/igm;
  pikan.regex_check(reg, pikan.types.QUESTION, "\"Color\" found. If this refers to a Pikmin type, use \"type\" instead.", null,
    function(r){
      return !pikan.is_common_exception(r);
    }
  );
  
  
  //"Artwork" sections should be called "Gallery".
  reg = /^\=+ +(Artwork|Images)\ +=+$/igm;
  pikan.regex_check(reg, pikan.types.QUESTION, "Gallery section found. For the sake of consistency, name this section \"Gallery\". However, if this particular gallery only has artwork, you may call it \"Artwork\".");
  
  
  //Find "font-family", as it is likely a Wikia visual editor leftover.
  reg = /font\-family/igm;
  pikan.regex_check(reg, pikan.types.QUESTION, "Font family tampering found. Possibly a Wikia visual editor leftover. Remove it if so.");
  
  
  //Find <p> tags, which are possible Wikia leftovers.
  reg = /<p/igm;
  pikan.regex_check(reg, pikan.types.QUESTION, "&lt;p&gt; tag found. There is normally no reason to use them, meaning this could be a Wikia visual editor leftover. Remove it if so.");
  
  
  //Find black text colors, which are possible Wikia leftovers.
  reg = /[ ;"]color: ?(black|#000[000]?)/igm;
  pikan.regex_check(reg, pikan.types.QUESTION, "Black text color style found. Because it is normally useless to force the text to be black, this is likely a Wikia visual editor leftover. Remove it if so.");
  
  
  //Report if there is no {{game icons}}.
  reg = /\{\{game icons/igm;
  if(reg.exec(pikan.text) === null) {
    pikan.save_problem(0, 0, pikan.types.QUESTION, "{{game icons}} template not found. If this page doesn't have game icons on the top right, but should, add the template manually.", "Pikipedia:Policy#General");
  }
  
  
  //Point out the use of the term "captain".
  reg = /captain/igm;
  pikan.regex_check(reg, pikan.types.QUESTION, "\"Captain\" found. Not all leaders are captains, so use the term \"leader\" whenever possible.", "Leader",
    function(r){
      if(pikan.after(r, 7) == " Olimar") return false;
      if(pikan.after(r, 8) == " Charlie") return false;
      if(pikan.is_common_exception(r)) return false;
      return true;
    }
  );
  
  
  //Find gendered nouns; enemies should be "it", and players should be "they".
  reg = /\b(his|him|her|he|she)\b/igm;
  pikan.regex_check(reg, pikan.types.QUESTION, "Gendered noun found. If this is an enemy, treat as it an \"it\", and if it's the player, treat them as \"they\". Otherwise, ignore this.", "Pikipedia:Policy#Perspective");
    

  //Find "center position" info for galleries.
  reg = /position ?= ?"?center"?/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "Center positioning found for a gallery. This is pointless, so remove it.", null,
    function(r){
      return (pikan.in_area(r, "<gallery", ">"));
    }
  );
  
  
  //Find "image:" (it should be "file:").
  reg = /\[\[Image\:/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "Image being called with \"Image:\" found. \"File:\" is better.");
  reg = /Image:/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "Image being called with \"Image:\" found. \"File:\" is better.", null,
    function(r){
      return pikan.in_area(r, "<gallery", "/gallery>");
    }
  );
  
  
  //Find redundant "Template:".
  reg = /\{\{Template\:/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "\"{{Template:abc}}\" and \"{{abc}}\" are the same, so use the latter form, which is simpler.");
  
  
  //Reference tags with a space before them.
  reg = / \<ref/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "Reference tags shouldn't have spaces before them.");
  
  
  //Find trailing spaces.
  reg = /(^ .+$|^.+ $)/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "Trailing space found.");
  
  
  //Find double spaces.
  reg = /  /igm;
  pikan.regex_check(
    reg, pikan.types.STYLE, "Double spaces found. Just one is enough.", null,
    function(r){
      return !pikan.in_area(r, "|", "="); //Template parameter -- not illegible.
    }
  );
  
  
  //Find two empty lines in a row.
  reg = /(\n\n\n|\r\r\r|\r\n\r\n\r\n)/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "Double empty lines found. Just one is enough. If you want to add vertical spacing, try the {{clear}} template. (If you can't see the selection, just press a letter key.)");
  
  
  //Empty line at the start.
  if(pikan.text.length > 2){
    if(pikan.text[0] == "\n" || pikan.text[0] == "\r"){
      pikan.save_problem(0, 1, pikan.types.STYLE, "Empty line at the start found. There's no reason for it to exist, so remove it.");
    }
  }
  
  
  //Missing empty line before section.
  reg = /([^\n=]\n|[^\r=]\r|[^(\r\n)=]\r\n)=.+=/gm;
  pikan.regex_check(reg, pikan.types.STYLE, "Add an empty line before section headers, to make it easier to read.");


  //Files share their line with other text.
  reg = /((\[\[File.+\]\][^\n\r\[\]]+$)|(^[^\n\r\[\]]+\[\[File.+\]\]))/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "Image has more than just its code on its line; add a line break between it and the text, to make it easier to read.");
  

  //Underscores in file names.
  reg = /\File:.+_.+/igm;
  pikan.regex_check(reg, pikan.types.STYLE, "File name with underscores found; spaces are more human-readable.");
  
  
  //Underscores in gallery file names.
  reg = /.+_.+\|/gm;
  pikan.regex_check(
    reg, pikan.types.STYLE, "File name with underscores found; spaces are more human-readable.", "",
    function(r){
      return pikan.in_area(r, "<gallery", "/gallery");
    }
  );
  
  
  //Underscores in links.
  reg = /\[\[[^|\]]*_[^|\]]*(\||\]\])/gm;
  pikan.regex_check(reg, pikan.types.STYLE, "Link with underscores found; spaces are more human-readable.");
  
  
  //Underscores in template names.
  reg = /\{\{[^\|\}]+_.+(\r|\n|\|\}\})/gm;
  pikan.regex_check(reg, pikan.types.STYLE, "Template name with underscores found; spaces are more human-readable.");
  
  
  //Links like [[A|A]].
  reg = /\[\[([^\|\]]+)\|\1\]\]/gm;
  pikan.regex_check(reg, pikan.types.STYLE, "Link with the same page and name found. Just simplify it like this: [[Apple|Apple]] &rarr; [[Apple]].");
  
  
  //Links like [[A|AB]].
  reg = /\[\[([^\|\]]+)\|\1[^ ]+\]\]/gm;
  pikan.regex_check(reg, pikan.types.STYLE, "Link with a name that contains the page name found. Simplify it like this: [[Apple|Apples]] &rarr; [[Apple]]s.");
  
  
  
  //--------------------
  
  
  
  pikan.update();

}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Setup
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Creates the necessary page elements and
 * initializes some values needed for the
 * analyzer to work.
 */
pikan.setup = function() {
  //Create the elements for the job.
  var editform = document.getElementById("editform");
  if(!editform) return;
  pikan.textbox = editform.wpTextbox1;
  
  //Div that encapsulates pikan.
  pikan.pikan_div = document.createElement("div");
  editform.insertBefore(pikan.pikan_div, pikan.textbox);
  
  //Button that finds problems.
  var find_button = document.createElement("input");
  pikan.pikan_div.appendChild(find_button);
  find_button.onclick = pikan.find_problems;
  find_button.type = "button";
  find_button.value = "Check for problems";
  find_button.title = "Start the analysis. [alt-shift-?]";
  find_button.setAttribute("accesskey", "?");
  
  //Help link.
  var help_link_sup = document.createElement("sup");
  pikan.pikan_div.appendChild(help_link_sup);
  var help_link = document.createElement("a");
  help_link_sup.appendChild(help_link);
  help_link.innerHTML = "[?]";
  help_link.title = "This is a tool that checks the entire page source, and reports problems, like policy breaks or style issues. You don't have to use this, unless you want to help make the page thoroughly clean. Click this link to open a help page.";
  help_link.href = pikan.help_link;
  help_link.target = "window";
  help_link.style.cursor = "help";
  
  //Span with the number of problems.
  pikan.prob_total_tag = document.createElement("span");
  pikan.pikan_div.appendChild(pikan.prob_total_tag);
  pikan.prob_total_tag.id = "pikan_prob_total";
  pikan.prob_total_tag.style.paddingLeft = "8px";
  pikan.prob_total_tag.style.fontWeight = "bold";
  
  //Span with error control.
  pikan.prob_control_tag = document.createElement("span");
  pikan.pikan_div.appendChild(pikan.prob_control_tag);
  pikan.prob_control_tag.id = "pikan_prob_control";
  pikan.prob_control_tag.style.display = "none";
  
  //Padding vertical bar.
  var bar = document.createElement("span");
  pikan.prob_control_tag.appendChild(bar);
  bar.style.fontWeight = "bold";
  bar.style.marginLeft = "12px";
  bar.innerHTML = "|";
  
  //Previous error button.
  var prev_button = document.createElement("input");
  pikan.prob_control_tag.appendChild(prev_button);
  prev_button.onclick = pikan.prev_problem;
  prev_button.type = "button";
  prev_button.style.marginLeft = "12px";
  prev_button.style.width = "4em";
  prev_button.value = "\u25C4";
  prev_button.title = "Go to the previous problem on the list. [alt-shift-minus]"
  prev_button.setAttribute("accesskey", "-");
  
  //Next error button.
  var next_button = document.createElement("input");
  pikan.prob_control_tag.appendChild(next_button);
  next_button.onclick = pikan.next_problem;
  next_button.style.marginLeft = "4px";
  next_button.style.width = "4em";
  next_button.type = "button";
  next_button.value = "\u25BA";
  next_button.title = "Go to the next problem on the list. [alt-shift-plus]"
  next_button.setAttribute("accesskey", "+");
  
  //Skip type button.
  var skip_button = document.createElement("input");
  pikan.prob_control_tag.appendChild(skip_button);
  skip_button.onclick = pikan.skip;
  skip_button.style.marginLeft = "4px";
  skip_button.style.width = "4em";
  skip_button.type = "button";
  skip_button.value = "\u25BA\u25BA";
  skip_button.title = "Skip all problems of this type, and go to the next.";
  
  //Go to error button.
  var goto_button = document.createElement("input");
  pikan.prob_control_tag.appendChild(goto_button);
  goto_button.onclick = pikan.goto_problem;
  goto_button.style.marginLeft = "4px";
  goto_button.style.width = "4em";
  goto_button.type = "button";
  goto_button.value = "Select";
  goto_button.title = "Highlight the text that is causing the current problem, if any.";
  
  //Current problem nr.
  pikan.prob_nr_tag = document.createElement("span");
  pikan.prob_control_tag.appendChild(pikan.prob_nr_tag);
  pikan.prob_nr_tag.style.marginLeft = "4px";
  pikan.prob_nr_tag.style.fontSize = "90%";
  pikan.prob_nr_tag.id = "pikan_prob_nr";
  
  //Line break before the details.
  pikan.prob_control_tag.appendChild(document.createElement("br"));
  
  //Span with details.
  pikan.prob_detail_tag = document.createElement("span");
  pikan.prob_control_tag.appendChild(pikan.prob_detail_tag);
  pikan.prob_detail_tag.style.fontStyle = "italic";
  pikan.prob_detail_tag.id = "pikan_prob_detail";
  
  document.editform.wpTextbox1.oninput = pikan.clear;
  
  pikan.clear();
  
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Show problem
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Shows information about the current problem,
 * and highlights it on the textbox.
 */
pikan.show_problem = function(){
  pikan.cur_prob_nr = Math.min(pikan.cur_prob_nr, pikan.problems.length);
  pikan.cur_prob_nr = Math.max(pikan.cur_prob_nr, 0);
  pikan.prob_detail_tag.style.display = "inline";
  var cur_prob = pikan.problems[pikan.cur_prob_nr];
  
  var prob_text = "<span style=\"color:" + pikan.type_colors[cur_prob.type];
  prob_text += "; background:" + pikan.type_backgrounds[cur_prob.type];
  prob_text += "; padding-left:1em; padding-right:1em; border-radius:8px;\"";
  prob_text += "title=\"" + pikan.type_descriptions[cur_prob.type] + "\">";
  prob_text += cur_prob.type + "</span> ";
  prob_text += cur_prob.text;
  if(cur_prob.page && cur_prob.page.length > 0){
    prob_text += " <a href=\"" + pikan.wiki_url + cur_prob.page + "\" target=\"window\" style=\"font-size:smaller;\">[More info]</a>";
  }
  pikan.prob_detail_tag.innerHTML = prob_text;
  
  pikan.prob_nr_tag.innerHTML = "(#" + (pikan.cur_prob_nr + 1) + "/" + pikan.problems.length + ")";
  
  pikan.goto_problem();
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Go to problem
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Highlights the text where the current problem
 * was found.
 */
pikan.goto_problem = function(){
  if(pikan.cur_prob_nr == -1) return;
  var prob = pikan.problems[pikan.cur_prob_nr];
  if(prob.pos == 0 && prob.len == 0) return;
  
  pikan.textbox.focus();
  
  //We need a hack for Chrome and some other browsers (I can't find a pattern),
  //as they don't scroll the textbox to the selection.
  var text_before_selection = pikan.text.substr(0, prob.pos);
  pikan.textbox.value = text_before_selection;
  pikan.textbox.scrollTop = 999999;
  pikan.textbox.value = pikan.text;
  
  //Normal selection code. For browsers that scroll to the selection,
  //the previous code will be pointless.
  pikan.textbox.selectionStart = pikan.problems[pikan.cur_prob_nr].pos;
  pikan.textbox.selectionEnd = pikan.problems[pikan.cur_prob_nr].pos + pikan.problems[pikan.cur_prob_nr].len;
  
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Previous result
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Changes to the previous result,
 * looping around to the last,
 * and then shows and highlights it.
 */
pikan.prev_problem = function(){
  if(pikan.cur_prob_nr == -1) return;
  pikan.cur_prob_nr--;
  if(pikan.cur_prob_nr == -1) pikan.cur_prob_nr = pikan.problems.length - 1;
  pikan.show_problem();
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Next result
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Changes to the next result,
 * looping around to the first,
 * and then shows and highlights it.
 */
pikan.next_problem = function(){
  if(pikan.cur_prob_nr == -1) return;
  pikan.cur_prob_nr++;
  if(pikan.cur_prob_nr == pikan.problems.length) pikan.cur_prob_nr = 0;
  pikan.show_problem();
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Skip problem
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Changes to the next result of a different
 * problem, looping back to the start if needed,
 * and then shows and highlights it.
 * If there are no different problems, it just
 * stays on the same problem.
 */
pikan.skip = function(){
  if(pikan.cur_prob_nr == -1) return;
  var prob_text = pikan.problems[pikan.cur_prob_nr].text;
  var done = false;
  var starting_nr = pikan.cur_prob_nr;
  while(!done){
    pikan.cur_prob_nr++;
    if(pikan.cur_prob_nr >= pikan.problems.length){
      pikan.cur_prob_nr = 0;
    }
    if(
      pikan.problems[pikan.cur_prob_nr].text != prob_text ||
      pikan.cur_prob_nr == starting_nr
    ){
      done = true;
    }
  }
  pikan.show_problem();
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Clear results
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Clear all results found and hides the problem
 * report text.
 */
pikan.clear = function(){
  pikan.problems = [];
  pikan.prob_detail_tag.style.display = "none";
  pikan.prob_total_tag.innerHTML = "";
  pikan.prob_control_tag.style.display = "none";
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Update number
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * This updates the number of problems found, and
 * displays it on the page.
 */
pikan.update = function(){
  //Update the number.
  pikan.prob_total_tag.innerHTML = "Problems found: " + pikan.problems.length;
  if(pikan.problems.length > 0) {
    pikan.prob_total_tag.style.color = "#600";
    pikan.prob_control_tag.style.display = "inline";
    if(pikan.cur_prob_nr >= pikan.problems.length) pikan.cur_prob_nr = pikan.problems.length - 1;
    pikan.show_problem();
  } else {
    pikan.prob_total_tag.style.color = "#363";
    pikan.cur_prob_nr = -1;
    pikan.prob_control_tag.style.display = "none";
  }
  
  pikan.pikan_div.style.minHeight = "3.5em";
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Save problem
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Saves a found problem.
 * pos:  Starting position of the text where the
 * problem was found.
 * len:  Length of the text.
 * type: Type of problem. Use pikan.types.
 * text: Description text for the problem.
 * page: Optional link to a page on the wiki,
 * explaining the problem and solutions better.
 */
pikan.save_problem = function(pos, len, type, text, page){  
  pikan.problems.push({
    pos:  pos,
    len:  len,
    type: type,
    text: text,
    page: page
  });
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Regex check
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Checks for all instances of the specified regex
 * pattern, and adds them to the list of
 * problems.
 * reg; prob_des; page:
   * Read save_problem() for an explanation.
 * cond: condition function.
   * Takes a regex pattern match.
   * Returns false if that match is to be ignored.
 */
pikan.regex_check = function(reg, type, prob_des, page, cond){

  while((found = reg.exec(pikan.text)) !== null) {
    
    var valid = true;
    if(cond){
      valid = cond(found);
    }
    
    if(valid){
      pikan.save_problem(found.index, found[0].length, type, prob_des, page);
    }
  }
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Is in area
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns whether a regex match is inside an area
 * delimited by two strings. Currently, it only
 * works as expected if the area is single-level
 * in depth.
 * r:         regex match.
 * starter:   area start delimiter.
 * ender:     area end delimiter.
 * same_line: if true, the start and end share the
   * same line.
 */
pikan.in_area = function(r, starter, ender, same_line){
  if(r[0] == starter || r[0] == ender) return false;
  
  var before_text = pikan.text.substr(0, r.index);
  
  var last_starter_before = before_text.lastIndexOf(starter);
  if(last_starter_before == -1) return false; //We couldn't even find the starter text.
  var last_ender_before = before_text.lastIndexOf(ender);
  if(last_ender_before > last_starter_before) return false; //The closest area before the text closes before the text too.
  
  var after_text = pikan.text.substr(r.index + r.length);
  
  var first_ender_after = after_text.indexOf(ender);
  if(first_ender_after == -1) return false; //We couldn't even find the ender text.
  first_ender_after += r.index + r.length;
  
  if(same_line){
    area = pikan.text.substr(last_starter_before, (first_ender_after - last_starter_before));
    if(area.indexOf("\n") == -1 && area.indexOf("\r") == -1) return true;
    else return false;
  }
  return true;  
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Get section level
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns the level of a section header.
 * str: the string containing the header's text.
 */
pikan.get_section_level = function(str){
  var section_level = 0;
  for(c = 0; c < str.length; c++){
    if(str[c] == '=') section_level++;
    else break;
  }
  return section_level;
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Text before
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns a string of text that comes before a
 * regex match.
 * r: regex match.
 * n: number of characters for the result.
   * -1: whole text before until a line break
   * is hit.
   * -2: whole text before until the start.
 */
pikan.before = function(r, n){
  if(n > 0){
    return pikan.text.substr(Math.max(0, r.index - n), n);
  }
  if(n == -1){
    var line_break = false;
    var char_n = r.index;
    while(!line_break){
      if(char_n == 0){
        line_break = true;
      }else{
        var c = pikan.text[char_n - 1];
        if(c == "\n" || c == "\r"){
          line_break = true;
        }else{
          char_n--;
        }
      }
    }
    return pikan.text.substr(char_n, r.index - char_n);
  }
  if(n == -2){
    return pikan.text.substr(0, r.index);
  }
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Text after
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns a string of text that comes after a
 * regex match.
 * r: regex match.
 * n: number of characters for the result.
   * -1: whole text after until a line break
   * is hit.
   * -2: whole text after until the end.
 */
pikan.after = function(r, n){
  var start = r.index + r[0].length;
  if(n > 0){
    return pikan.text.substr(start, n);
  }
  if(n == -1){
    var line_break = false;
    var char_n = r.index + r[0].length;
    while(!line_break){
      if(char_n == pikan.text.length){
        line_break = true;
      }else{
        var c = pikan.text[char_n + 1];
        if(c == "\n" || c == "\r"){
          line_break = true;
        }else{
          char_n++;
        }
      }
    }
    char_n++;
    return pikan.text.substr(start, char_n - start);
  }
  if(n == -2){
    return pikan.text.substr(start, pikan.text.length - start);
  }
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Must be uppercase
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns whether a regex match's word MUST be
 * in uppercase (start of a new sentence) or not.
 * reg:  regex match.
 */
pikan.must_be_upper = function(reg){
  //If it's a template parameter name or value, forget it.
  if(pikan.in_area(reg, "{{", "}}")) return true;
  
  var in_link = pikan.in_area(reg, "[[", "]]", true);
  //If it's a page name (exclusively), forget it.
  if(in_link && pikan.in_area(reg, "[[", "|", true)) return true;
  
  var in_gallery = pikan.in_area(reg, "<gallery", "/gallery");
  var before = pikan.before(reg, -1);
  
  //If it's a filename in a gallery, then forget it.
  if(in_gallery && before.indexOf("|") == -1) return true;
  
  //Check if this word belongs to a link's display text,
  //where we'll ignore the start of the link.
  if(in_link && before.substr(before.length - 1, 1) == "|"){
    while(before[before.length - 1] != "["){
      before = before.slice(0, before.length - 1);
    }
  }
  
  //Ignore link starts (if it's a link like [[Apples|bananas]], it will
  //find "Apples" further up in the function).
  if(before.substr(before.length - 2, 2) == "[["){
    before = before.slice(0, before.length - 2);
  }
  
  //Ignore bold and italics, lists, quotes, equals, spaces.
  while(
    before[before.length - 1] == "'" ||
    before[before.length - 1] == "*" ||
    before[before.length - 1] == "#" ||
    before[before.length - 1] == "\"" ||
    before[before.length - 1] == "=" ||
    before[before.length - 1] == " "
  ){
    before = before.slice(0, before.length - 1);
  }

  //Check if it's the start of a sentence, because if so, it must be upper.
  if(
    before.length == 0 ||
    (in_gallery && before.substr(before.length - 1, 1) == "|") ||
    before.substr(before.length - 1, 1) == "."
  ){
    return true;
  }
  
  return false;
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Get section number
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns the section number that some text is in.
 * This is an array, with each position being a level.
 * e.g. section 4.6.2 on the index would be an array
 * like [4][6][2]. Returns an empty array for none.
 * reg:  regex match.
 */
pikan.get_section_nr = function(reg){
  var sec_reg = /^=+[^=]+=+$/gm;
  var sec_match = null;
  var last_section = [];
  
  while(sec_match = sec_reg.exec(pikan.text)){
    if(sec_match.index > reg.index) break;
    var cur_section = [];
    var l = pikan.get_section_level(sec_match[0]);
    
    for(var x = 0; x < l; x++){
      cur_section[x] = 0;
    }
    
    if(last_section.length == 0) last_section = cur_section;
    
    for(var x = 0; x < Math.min(last_section.length, cur_section.length); x++){
      cur_section[x] = last_section[x];
    }
    
    cur_section[cur_section.length - 1]++;
    
    last_section = cur_section;
    
  }
  
  return last_section;
}

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Is same section
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns whether section nr1 is a sub-section
 * of section nr2. The numbers you feed it are
 * the arrays returned by pikan.get_section_nr.
 * nr1: first number.
 * nr2: second number.
 */
pikan.section_in_section = function(nr1, nr2){
  if(nr2.length > nr1.length) return false;
  
  for(var l = 0; l < Math.min(nr1.length, nr2.length); l++){
    if(nr1[l] != nr2[l]) return false;
  }
  return true;
}



/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Utility functions for Pikipedia.
 * * * * * * * * * * * * * * * * * * * * * * * * *

/* * * * * * * * * * * * * * * * * * * * * * * * *
 * Is common exception
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * Returns whether a regex match belongs to a
 * "common exception". Use this to ignore capitalization,
 * spelling, etc. problems on things like template
 * parameter names.
 * reg:  regex match.
 */
pikan.is_common_exception = function(reg){
  //Is it in a template?
  if(pikan.in_area(reg, "{{", "}}")) return true;
  
  //Is it a page link or image?
  var in_internal_link = (pikan.in_area(reg, "[[", "]]"));
  if(in_internal_link){
    var in_left_half = pikan.in_area(reg, "[[", "|");
    //[[A|B]] format.
    if(in_left_half) return true;
  }
  
  //Is it a categorization?
  if(pikan.in_area(reg, "[[Category:", "]]")) return true;
  if(pikan.in_area(reg, "[[category:", "]]")) return true;
  
  //Is it a file name in a gallery?
  if(pikan.in_area(reg, "<gallery", "/gallery")){
    if(pikan.in_area(reg, "\n", "|")) return true;
  }
  
  //Is it in an external link?
  if(!in_internal_link){
    if(
      (pikan.in_area(reg, "[http", "]") ||
      pikan.in_area(reg, "[www", "]")) &&
      pikan.in_area(reg, "[", " ")
    ){
      return true;
    }
  }
  
  return false;
  
}

pikan.setup();

// </nowiki>