f41b982009-05-07Martin Stjernholm // This is a roxen module. Copyright © 2000 - 2009, Roxen IS.
0e834c2000-02-24Martin Nilsson //
fb74971997-09-16Per Hedbor #include <module.h> inherit "module";
6a4ce21999-09-26Martin Nilsson constant thread_safe=1;
b13cba1999-09-25Martin Nilsson 
0917d32013-03-04Anders Johansson constant cvs_version = "$Id$";
559b491998-01-21Henrik Grubbström (Grubba) 
0c44e22004-08-10Fredrik Noring constant module_type = MODULE_TAG|MODULE_PROVIDER;
bc0fa02001-03-08Per Hedbor constant module_name = "Tags: Spell checker";
59ae152000-04-06Mattias Wingstedt constant module_doc =
e2b5832004-08-05Fredrik Noring #"Checks for misspelled words using the <tt>&lt;emit#spellcheck&gt;</tt> or <tt>&lt;spell&gt;</tt> tags.";
fb74971997-09-16Per Hedbor 
0c44e22004-08-10Fredrik Noring array(string) query_provides() { return ({ "spellchecker" }); }
d8546c2012-04-13Jonas Wallden  mapping(string:function) query_action_buttons() { return ([ "Rebuild Custom Dictionaries" : lambda() { sync_extra_dicts(1); } ]); }
c2dc4a2000-02-22Stefan Wallström mapping find_internal(string f, RequestID id)
fb74971997-09-16Per Hedbor {
c2dc4a2000-02-22Stefan Wallström  switch(f) { case "red.gif":
1a73582000-04-30Martin Nilsson  return Roxen.http_string_answer("GIF89a\5\0\5\0\200\0\0\0\0\0\267\0\0,\0\0\0\0\5\0\5\0\0\2\7\204\37i\31\253g\n\0;","image/gif");
c2dc4a2000-02-22Stefan Wallström  case "green.gif":
1a73582000-04-30Martin Nilsson  return Roxen.http_string_answer("GIF89a\5\0\5\0\200\0\0\2\2\2\0\267\14,\0\0\0\0\5\0\5\0\0\2\7\204\37i\31\253g\n\0;","image/gif");
c2dc4a2000-02-22Stefan Wallström  default: return 0; }
fb74971997-09-16Per Hedbor }
c2dc4a2000-02-22Stefan Wallström void create() {
a4cbbf2004-09-23Jonas Wallden  defvar("spellchecker", #ifdef __NT__
e9cae22005-01-21Henrik Grubbström (Grubba)  lambda() { catch { // RegGetValue() throws if the key isn't found. return replace(RegGetValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\Aspell", "Path") + "\\aspell.exe", "\\", "/"); }; // Reasonable default. return "C:/Program Files/Aspell/bin/aspell.exe"; }(),
a4cbbf2004-09-23Jonas Wallden #else "/usr/bin/aspell", #endif
c2dc4a2000-02-22Stefan Wallström  "Spell checker", TYPE_STRING,
59ae152000-04-06Mattias Wingstedt  "Spell checker program to use.");
fb74971997-09-16Per Hedbor 
905ae42000-09-04Johan Sundström  defvar("dictionary", "american", "Default dictionary", TYPE_STRING,
e2b5832004-08-05Fredrik Noring  "The default dictionary used, when not specified in the " "&lt;spell&gt; tag.");
fb74971997-09-16Per Hedbor 
d8546c2012-04-13Jonas Wallden  defvar("extra_dicts", ({ }), "Custom dictionaries", TYPE_FILE_LIST, "Paths to custom dictionary files. These should be plain-text " "files with one word on each line. NOTE: Filenames must include " "a valid language code before the file suffix, e.g. " "<tt>mywords.en.txt</tt> or <tt>mywords.en_US.txt</tt>. " "The plain-text files must also use UTF-8 encoding if you enable " "the UTF-8 support in the setting below.");
2d8d812013-11-01Jonas Walldén  defvar("run_together_langs", "sv", "Languages with run-together words", TYPE_STRING, "A comma-separated list of language codes where run-together words " "are considered valid. This behavior is useful in languages such as " "Swedish but not appropriate for English.");
d8546c2012-04-13Jonas Wallden 
905ae42000-09-04Johan Sundström  defvar("report", "popup", "Default report type", TYPE_STRING_LIST,
e2b5832004-08-05Fredrik Noring  "The default report type used, when not specified in the " "&lt;spell&gt; tag.",
c2dc4a2000-02-22Stefan Wallström  ({ "popup","table" }) );
fb74971997-09-16Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  defvar("prestate", "", "Prestate",TYPE_STRING,
e2b5832004-08-05Fredrik Noring  "If specified, only check spelling in the &lt;spell&gt; tag " "when this prestate is present.");
fb74971997-09-16Per Hedbor 
d194d12010-10-27Jonas Wallden  defvar("use_utf8", 1, "Enable UTF-8 support", TYPE_FLAG, "If set takes advantage of UTF-8 support in Aspell. NOTE: Requires " "Aspell version 0.60 or later.");
fb74971997-09-16Per Hedbor }
b956721998-02-03Per Hedbor 
d8546c2012-04-13Jonas Wallden  string status() { // Sync dictionaries and list status sync_extra_dicts(); array(string) ed_res = ({ }); foreach (get_extra_dicts(); string ed_path; string pd_path) { ed_res += ({ "<li>" + Roxen.html_encode_string(ed_path) + " <span style='color: #888'>&ndash;</span> " + (pd_path ? "<span style='color: green'>OK</span>" : "<span style='color: red'>Error</span>") + "</li>" }); } if (sizeof(ed_res)) { return "<p><b>Custom dictionaries</b></p>" "<ul>" + (sort(ed_res) * "\n") + "</ul>"; } return ""; } void start(int when, Configuration conf) { sync_extra_dicts(); } string get_processed_dict_path(string extra_dict) { // Hash the external path and return a corresponding item in $VARDIR string ed_hash =
57eb122014-10-04Jonas Walldén  lower_case(String.string2hex(Crypto.MD5.hash(extra_dict)));
d8546c2012-04-13Jonas Wallden  return combine_path(getcwd(), roxen_path("$VARDIR/check_spelling/" + ed_hash + ".dict")); } string|void get_extra_dict_language(string ed_path) { // Only accept filenames structured as mywords.en.txt. We require // at least two "." and a non-empty language code. string ed_name = basename(ed_path); array(string) ed_segments = ed_name / "."; return (sizeof(ed_segments) > 2) && sizeof(ed_segments[-2]) && ed_segments[-2]; } mapping(string:string) get_extra_dicts(void|int(0..1) include_empty) { mapping(string:string) res = ([ ]); foreach (query("extra_dicts"), string ed_path) { // Only accept files that follow required naming convention res[ed_path] = 0; if (!get_extra_dict_language(ed_path)) continue; if (file_stat(ed_path)) { // Processed dictionary string pd_path = get_processed_dict_path(ed_path); if (Stdio.Stat pd_stat = file_stat(pd_path)) { // Don't include zero-byte placeholders that we only keep to // avoid re-converting broken source files unless caller wants // them. if (pd_stat->size || include_empty) res[ed_path] = pd_path; } else if (include_empty) { // Not yet processed but a valid candidate res[ed_path] = pd_path; } } } return res; }
2959dd2013-04-26Jonas Walldén // Returns tuple < encoding, chars to skip > if the given data string // starts with a BOM, and zero otherwise. array(string|int) get_encoding_from_bom(string data) { // We only care about UTF-8 and UTF-16 BE/LE: // // EF BB BF - UTF-8 // FE FF - UTF-16 big-endian // FF FE - UTF-16 little-endian if (sizeof(data) >= 3) { if (data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF) return ({ "utf-8", 3 }); } if (sizeof(data) >= 2) { if (data[0] == 0xFE && data[1] == 0xFF) return ({ "utf-16", 2 }); if (data[0] == 0xFF && data[1] == 0xFE) return ({ "utf-16le", 2 }); } return 0; }
d8546c2012-04-13Jonas Wallden int process_extra_dict(string ed_path, string pd_path) { // Make sure destination directory exists mkdirhier(dirname(pd_path) + "/"); // Convert the extra_dict source file (plain-text file) in ed_path // and write it to the processed dictionary at pd_path. string aspell_binary = query("spellchecker"); if (!Stdio.exist(aspell_binary)) return -1; int use_utf8 = query("use_utf8"); array(string) args = ({ aspell_binary, "--lang", get_extra_dict_language(ed_path) }) + (use_utf8 ? ({ "--encoding", "utf-8" }) : ({ }) ) + ({ "create", "master", pd_path }); report_notice("Spell Checker: Converting dictionary %s... ", ed_path);
5404402013-04-26Jonas Walldén 
2959dd2013-04-26Jonas Walldén  // Aspell doesn't like MS-DOS line endings so write a clean temp file. // We also heed any BOM that we find.
5404402013-04-26Jonas Walldén  string in_data = Stdio.read_bytes(ed_path); if (!in_data) { report_notice("Error reading dictionary: %s\n", ed_path); return -1; }
2959dd2013-04-26Jonas Walldén  if (array bom_data = get_encoding_from_bom(in_data)) { // Skip BOM bytes and recode to UTF-8 if currently in a different format in_data = in_data[bom_data[1]..]; if (bom_data[0] != "utf-8") {
50aa162015-04-28Jonas Walldén  if (Charset.Decoder dec = Charset.decoder(bom_data[0]))
2959dd2013-04-26Jonas Walldén  in_data = string_to_utf8(dec->feed(in_data)->drain()); } }
5404402013-04-26Jonas Walldén  in_data = replace(in_data, ({ "\r\n", "\r" }), ({ "\n", "\n" }) ); string ed_cleaned_path = ed_path + ".tmp"; if (mixed err = catch { Stdio.write_file(ed_cleaned_path, in_data); }) { report_notice("Error writing temp file: %s\n", ed_cleaned_path); return -1; }
d8546c2012-04-13Jonas Wallden 
5404402013-04-26Jonas Walldén  Stdio.File in_file = Stdio.File(ed_cleaned_path);
d8546c2012-04-13Jonas Wallden  Process.Process p = Process.Process(args, ([ "stdin": in_file ]) ); in_file->close(); int err = p->wait();
5404402013-04-26Jonas Walldén  rm(ed_cleaned_path);
d8546c2012-04-13Jonas Wallden  report_notice((err ? "Error" : "OK") + "\n"); return err; } void sync_extra_dicts(void|int force_rebuild) { // Stat each of the configured extra dictionaries and check whether our // compressed versions are out-of-date. foreach (get_extra_dicts(1); string ed_path; string pd_path) { // Skip invalid filenames if (!pd_path) continue; if (Stdio.Stat ed_stat = file_stat(ed_path)) { // Compare to stat of our derived file Stdio.Stat pd_stat = file_stat(pd_path); if (force_rebuild || !pd_stat || (ed_stat->mtime >= pd_stat->mtime)) { int err = process_extra_dict(ed_path, pd_path); if (err) { // Write a zero-byte file in case of failed conversion. This // means we can avoid re-converting it but still skip it when // we gather all extra dictionaries. rm(pd_path); Stdio.write_file(pd_path, ""); } } } } }
c2dc4a2000-02-22Stefan Wallström string render_table(array spellreport) { string ret="<table bgcolor=\"#000000\" border=\"0\" cellspacing=\"0\" cellpadding=\"1\">\n" "<tr><td><table border=\"0\" cellspacing=\"0\" cellpadding=\"4\">\n" "<tr bgcolor=\"#112266\">\n" "<th align=\"left\"><font color=\"#ffffff\">Word</font></th><th align=\"left\"><font color=\"#ffffff\">Suggestions</th></tr>\n";
b956721998-02-03Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  int row=0; foreach(spellreport,array word) { row++; ret+="<tr bgcolor=\"#"+(row&1?"ffffff":"ddeeff")+"\"><td align=\"left\">"+word[0]+"</td><td align=\"left\">"+word[1]+"</td></tr>\n"; } return ret+"</table></td></tr>\n</table>";
b956721998-02-03Per Hedbor }
c2dc4a2000-02-22Stefan Wallström  string do_spell(string q, mapping args, string content,RequestID id)
fb74971997-09-16Per Hedbor {
c2dc4a2000-02-22Stefan Wallström  string ret="";
fb74971997-09-16Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  if(args->help) return register_module()[2]+"<p>";
fb74971997-09-16Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  string dict=args->dictionary || query("dictionary"); if(!sizeof(dict)) dict="american";
fb74971997-09-16Per Hedbor 
58792a2008-08-07Martin Stjernholm  string text=Parser.parse_html_entities (content, 1);
fb74971997-09-16Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  text=replace(text,({"\n","\r"}),({" "," "})); text=Array.everynth((replace(text,">","<")/"<"),2)*" "; text=replace(text, ({ ".",",",":",";","\t","!","|","?","(",")","\"" }), ({ "", "", "", "", "", "", "", "", "", "", "" }) ); array(string) words=text/" "; words-=({"-",""}); array result=spellcheck(words,dict); if(args->report||query("report")=="popup") { if(!sizeof(result))
db053e2000-12-05Martin Nilsson  return "<img src=\""+query_absolute_internal_location(id)+"green.gif\">"+content;
0e834c2000-02-24Martin Nilsson 
c2dc4a2000-02-22Stefan Wallström  if(!id->misc->__checkspelling) { id->misc->__checkspelling=1;
0e834c2000-02-24Martin Nilsson 
c2dc4a2000-02-22Stefan Wallström  ret+=#"<script language=\"javascript\"> var spellcheckpopup=''; var isNav4 = false; if (navigator.appVersion.charAt(0) == \"4\" && navigator.appName == \"Netscape\") isNav4 = true; function getObj(obj) { if (isNav4) return eval(\"document.\" + obj); else return eval(\"document.all.\" + obj);
fb74971997-09-16Per Hedbor }
cd20a32008-08-14Martin Stjernholm function getRecursiveLeft(o) {
c2dc4a2000-02-22Stefan Wallström  if(o.tagName == \"BODY\") return o.offsetLeft; return o.offsetLeft + getRecursiveLeft(o.offsetParent); }
fb74971997-09-16Per Hedbor 
cd20a32008-08-14Martin Stjernholm function getRecursiveTop(o) {
c2dc4a2000-02-22Stefan Wallström  if(o.tagName == \"BODY\") return o.offsetTop; return o.offsetTop + getRecursiveTop(o.offsetParent); }
fb74971997-09-16Per Hedbor 
c2dc4a2000-02-22Stefan Wallström function showPopup(popupid,e) { if(isNav4){ getObj(popupid).moveTo(e.target.x,e.target.y); } else { getObj(popupid).style.pixelLeft=getRecursiveLeft(window.event.srcElement); getObj(popupid).style.pixelTop=getRecursiveTop(window.event.srcElement);
fb74971997-09-16Per Hedbor  }
c2dc4a2000-02-22Stefan Wallström  spellcheckpopup=popupid if(isNav4) { getObj(popupid).visibility=\"visible\"; document.captureEvents(Event.MOUSEMOVE); document.onMouseMove = checkPopupCoord;
0e834c2000-02-24Martin Nilsson  } else {
c2dc4a2000-02-22Stefan Wallström  getObj(popupid).style.visibility=\"visible\"; document.onmousemove = checkPopupCoord;
cd1dde2000-02-17Per Hedbor  }
c2dc4a2000-02-22Stefan Wallström }
fb74971997-09-16Per Hedbor 
cd20a32008-08-14Martin Stjernholm function checkPopupCoord(e) {
c2dc4a2000-02-22Stefan Wallström  p = getObj(spellcheckpopup); if(isNav4) { x=e.pageX; y=e.pageY; pw=p.clip.width; ph=p.clip.height; px=p.left; py=p.top; } else { x=window.event.clientX + document.body.scrollLeft; y=window.event.clientY + document.body.scrollTop; pw=p.offsetWidth; ph=p.offsetHeight; px=p.style.pixelLeft; py=p.style.pixelTop; } if(!((x > px && x < px + pw) && (y > py && y < py + ph))) { if(isNav4) { p.visibility=\"hidden\"; document.releaseEvents(Event.MOUSEMOVE); } else { p.style.visibility=\"hidden\";
0e834c2000-02-24Martin Nilsson  document.onMouseMove = 0;
c2dc4a2000-02-22Stefan Wallström  } } } </script>";
0e834c2000-02-24Martin Nilsson 
c2dc4a2000-02-22Stefan Wallström  }
fb74971997-09-16Per Hedbor 
cd1dde2000-02-17Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  string popupid="spellreport"+sprintf("%02x",id->misc->__checkspelling);
0e834c2000-02-24Martin Nilsson 
c2dc4a2000-02-22Stefan Wallström  ret+="<style>#"+popupid+" {position:absolute; left:0; top:0; visibility:hidden}</style>"; ret+="<div id=\""+popupid+"\">"+render_table(result)+"</div>";
cd1dde2000-02-17Per Hedbor 
db053e2000-12-05Martin Nilsson  ret+= "<a href=\"\" onMouseOver='if(isNav4) showPopup(\""+popupid+"\",event);else showPopup(\""+popupid+"\");'><img border=0 src=\""+query_absolute_internal_location(id)+"red.gif\"></a>"+content;
0e834c2000-02-24Martin Nilsson 
c2dc4a2000-02-22Stefan Wallström  id->misc->__checkspelling++; return ret; }
fb74971997-09-16Per Hedbor 
cd1dde2000-02-17Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  return content + "<p><b>Spell checking report:</b><p>"+ render_table(result); }
fb74971997-09-16Per Hedbor 
c2dc4a2000-02-22Stefan Wallström class TagSpell { inherit RXML.Tag; constant name="spell";
cd1dde2000-02-17Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  class Frame { inherit RXML.Frame; array do_return (RequestID id) { string _prestate=id->variables->prestate||query("prestate"); if(sizeof(_prestate) && !id->prestate[_prestate]) return ({ content }); else return ({ do_spell("spell",args,content,id) }); }
fb74971997-09-16Per Hedbor 
c2dc4a2000-02-22Stefan Wallström  } }
cd1dde2000-02-17Per Hedbor 
e2b5832004-08-05Fredrik Noring string run_spellcheck(string|array(string) words, void|string dict)
514bd62004-08-11Fredrik Noring // Returns 0 on failure.
e2b5832004-08-05Fredrik Noring {
d8546c2012-04-13Jonas Wallden  // Sync any custom dictionaries in case they have been edited, and // fetch a list of valid ones to add in this run. sync_extra_dicts(); mapping(string:string) extra_dicts = get_extra_dicts(); array(string) ed_args = ({ }); foreach (extra_dicts; string ed_path; string pd_path) { if (pd_path)
9ead792013-04-29Jonas Walldén  ed_args += ({ "--add-extra-dicts", pd_path });
d8546c2012-04-13Jonas Wallden  }
2d8d812013-11-01Jonas Walldén  // Should run-together words be considered? array(string) run_together_langs = map(query("run_together_langs") / ",", String.trim_all_whites); int use_run_together = dict && has_value(run_together_langs, dict);
c2dc4a2000-02-22Stefan Wallström  object file1=Stdio.File(); object file2=file1->pipe(); object file3=Stdio.File(); object file4=file3->pipe(); string spell_res;
d194d12010-10-27Jonas Wallden  int use_utf8 = query("use_utf8");
c2dc4a2000-02-22Stefan Wallström 
abdc0f2004-08-06Fredrik Noring  if(stringp(words)) words = replace(words, "\n", " ");
346c742004-08-12Fredrik Noring  if(!Stdio.exist(query("spellchecker"))) { werror("check_spelling: Missing binary in %s\n", query("spellchecker")); return 0; }
cd20a32008-08-14Martin Stjernholm  Process.Process p =
2d8d812013-11-01Jonas Walldén  Process.Process(({ query("spellchecker"), "-a" }) + (use_run_together ? ({ "-C" }) : ({ }) ) +
92ced82011-09-12Henrik Grubbström (Grubba)  (use_utf8 ? ({ "--encoding=utf-8" }) : ({ }) ) + (stringp(words) ? ({ "-H" }) : ({ }) ) +
d8546c2012-04-13Jonas Wallden  (dict ? ({ "-d", dict }) : ({ }) ) + ed_args,
92ced82011-09-12Henrik Grubbström (Grubba)  ([ "stdin":file2,"stdout":file4 ]));
c2dc4a2000-02-22Stefan Wallström 
9b4c812004-08-06Fredrik Noring  string text = stringp(words) ?
e2b5832004-08-05Fredrik Noring  " "+words /* Extra space to ignore aspell commands
0daa022004-08-05Fredrik Noring  (potential security problem), compensated
e2b5832004-08-05Fredrik Noring  below. */ :
9b4c812004-08-06Fredrik Noring  " "+words*"\n "+"\n" /* Compatibility mode. */;
d194d12010-10-27Jonas Wallden  // Aspell 0.60 or later understands UTF-8 encoding natively if (use_utf8) text = string_to_utf8(text); else
50aa162015-04-28Jonas Walldén  text = Charset.encoder("iso-8859-1", "\xa0")->feed(text)->drain();
d194d12010-10-27Jonas Wallden 
514bd62004-08-11Fredrik Noring  Stdio.sendfile(({ text }), 0, 0, -1, 0, file1, lambda(int bytes) { file1->close(); });
c2dc4a2000-02-22Stefan Wallström  file2->close(); file4->close(); spell_res=file3->read(); file3->close();
d194d12010-10-27Jonas Wallden  if (use_utf8 && spell_res) catch { spell_res = utf8_to_string(spell_res); };
514bd62004-08-11Fredrik Noring  return p->wait() == 0 ? spell_res : 0;
e2b5832004-08-05Fredrik Noring } array spellcheck(array(string) words,string dict) { array res=({ });
514bd62004-08-11Fredrik Noring  array ispell_data = (run_spellcheck(words, dict) || "")/"\n";
0e834c2000-02-24Martin Nilsson 
c2dc4a2000-02-22Stefan Wallström  if(sizeof(ispell_data)>1) { int i,row=0,pos=0,pos2; string word,suggestions; for(i=1;i<sizeof(ispell_data)-1 && row<sizeof(words);i++) { if(!sizeof(ispell_data[i])){ // next row row++; pos=0;
fb74971997-09-16Per Hedbor  }
c2dc4a2000-02-22Stefan Wallström  else { switch(ispell_data[i][0]) { case '&': // misspelled, suggestions sscanf(ispell_data[i],"& %s %*d %d:%s",word,pos2,suggestions); res += ({ ({ words[row],suggestions }) });; pos=pos2-1+sizeof(word); break; case '#': //misspelled sscanf(ispell_data[i],"# %s %d",word,pos2); res += ({ ({ words[row],"-" }) }); pos=pos2-1+sizeof(word); break;
fb74971997-09-16Per Hedbor  } } }
c2dc4a2000-02-22Stefan Wallström  return res;
fb74971997-09-16Per Hedbor  } }
7ec4e32000-04-06Kenneth Johansson 
e2b5832004-08-05Fredrik Noring class TagEmitSpellcheck { inherit RXML.Tag; constant name = "emit"; constant plugin_name = "spellcheck"; mapping(string:RXML.Type) req_arg_types = ([ "text" : RXML.t_text(RXML.PEnt), ]); array get_dataset(mapping args, RequestID id) { array(mapping(string:string)) entries = ({}); string dict = args["dict"]; string text = args["text"]; if(text)
514bd62004-08-11Fredrik Noring  { string s = run_spellcheck(text, dict); if(!s) {
155eb82004-08-12Fredrik Noring  if(args["error"]) RXML.user_set_var(args["error"], "checkfailed");
514bd62004-08-11Fredrik Noring  return ({}); } foreach(s/"\n", string line)
e2b5832004-08-05Fredrik Noring  {
570b972004-08-12Fredrik Noring  line -= "\r"; // Needed for aspell on Windows.
e2b5832004-08-05Fredrik Noring  if(!sizeof(line)) continue; switch(line[0]) { case '*': // FIXME: Optimisation: Make aspell not send this! continue; case '&': if(sscanf(line, "& %s %*d %d: %s", string word, int offset, string suggestions) == 4) entries += ({ ([ "word":word, "offset":offset-1 /* For extra space (see above)! */, "suggestions":suggestions ]) });
0daa022004-08-05Fredrik Noring  continue;
e2b5832004-08-05Fredrik Noring  case '#': if(sscanf(line, "# %s %d", string word, int offset) == 2) entries += ({ ([ "word":word, "offset":offset-1 /* For extra space (see above)! */ ]) });
0daa022004-08-05Fredrik Noring  continue;
e2b5832004-08-05Fredrik Noring  } }
514bd62004-08-11Fredrik Noring  }
e2b5832004-08-05Fredrik Noring  return entries; } }
7ec4e32000-04-06Kenneth Johansson  TAGDOCUMENTATION; #ifdef manual constant tagdoc=([
e2b5832004-08-05Fredrik Noring "emit#spellcheck":({ #"<desc type='plugin'><p><short> Lists from a text words that are not found in a dictionary using aspell.</short></p> </desc> <attr name='text' value='string'><p>The text to be spell checked. Tags are allowed in the text.</p></attr> <attr name='dict' value='string'><p>Optionally select a dictionary.</p></attr>
155eb82004-08-12Fredrik Noring  <attr name='error' value='string'><p>Variable to set if an error occurs.</p></attr>
e2b5832004-08-05Fredrik Noring ", ([ "&_.word;":#"<desc type='entity'><p>The word not found in the dictionary.</p></desc>", "&_.offset;":#"<desc type='entity'><p>Character offset to the word in the text.</p></desc>", "&_.suggestions;":#"<desc type='entity'><p>If present, a comma and space separated list of suggested word replacements.</p></desc>" ]) }),
ce8fb02001-09-21Johan Sundström "spell":#"<desc type='cont'><p><short>
9b03652001-03-07Kenneth Johansson  Checks words for spelling problems.</short> The spellchecker uses the ispell dictionary. </p></desc>
7ec4e32000-04-06Kenneth Johansson 
9b03652001-03-07Kenneth Johansson <attr name='dict' value='american,others'><p> Select dictionary to use in the spellchecking. American is default.</p>
7ec4e32000-04-06Kenneth Johansson </attr>
9b03652001-03-07Kenneth Johansson <attr name='prestate' value='string'><p> What prestate to use.</p>
7ec4e32000-04-06Kenneth Johansson </attr>
9b03652001-03-07Kenneth Johansson <attr name='report' value='popup,table'><p>
7ec4e32000-04-06Kenneth Johansson  Either recieve the spellreport as a popup-window when clicking on the
9b03652001-03-07Kenneth Johansson  misspelled word or as a table with all misspelled words.</p>
7ec4e32000-04-06Kenneth Johansson </attr>",
9b03652001-03-07Kenneth Johansson 
7ec4e32000-04-06Kenneth Johansson  ]); #endif