[prev in list] [next in list] [prev in thread] [next in thread]
List: kupu-checkins
Subject: [kupu-checkins] r11657 - in kupu/trunk/kupu: cgi common
From: guido () codespeak ! net
Date: 2005-04-30 17:19:15
Message-ID: 20050430171915.6D17427BEE () code1 ! codespeak ! net
[Download RAW message or body]
Author: guido
Date: Sat Apr 30 19:19:15 2005
New Revision: 11657
Added:
kupu/trunk/kupu/cgi/
kupu/trunk/kupu/cgi/spellcheck.cgi (contents, props changed)
kupu/trunk/kupu/common/kupuimages/text-check.png (contents, props changed)
kupu/trunk/kupu/common/kupuspellchecker.js
kupu/trunk/kupu/default/spellchecker.kupu
Modified:
kupu/trunk/kupu/common/kupuhelpers.js
kupu/trunk/kupu/common/kupuinit.js
kupu/trunk/kupu/common/kupustyles.css
kupu/trunk/kupu/default/include.kupu
kupu/trunk/kupu/default/wire.kupu
Log:
Added support for server-side spell checking. Currently only a CGI script is
provided as a server-side example, but it should be fairly simple to write
other versions (just a script that receives the text via POST (varname 'text')
and returns a space-seperated list of all unknown words). The client side
will call the script with the iframe contents as argument, and open a window
with the iframe content displayed, and (currently, can be changed from init)
all unknown words coloured red.
Added: kupu/trunk/kupu/cgi/spellcheck.cgi
==============================================================================
--- (empty file)
+++ kupu/trunk/kupu/cgi/spellcheck.cgi Sat Apr 30 19:19:15 2005
@@ -0,0 +1,74 @@
+#!/usr/bin/python
+
+"""SpellChecker for Kupu"""
+
+COMMAND = 'aspell -l'
+
+import popen2, re
+
+class SpellChecker:
+ """Simple spell checker, uses ispell (or aspell) with pipes"""
+
+ reg_split = re.compile("[^\w']+", re.UNICODE)
+
+ def __init__(self, text):
+ self.chout, self.chin = popen2.popen2(COMMAND)
+ self.text = text
+ self.result = None
+
+ def check(self):
+ """checks a line of text
+
+ returns None if spelling was okay, and an HTML string with words
+ that weren't recognized marked (with a span class="wrong_spelling")
+ """
+ if self.result is None:
+ # send it to the child app
+ self.chin.write(self.text)
+ self.chin.flush()
+ # close in (this makes the app spew out the result)
+ self.chin.close()
+
+ # read the result
+ result = self.chout.read()
+ result = ' '.join(result.split())
+ if not result:
+ result = None
+
+ # close out
+ self.chout.close()
+
+ self.result = result
+ return self.result
+
+def is_cgi():
+ import os
+ if os.environ.has_key('GATEWAY_INTERFACE'):
+ return True
+ return False
+
+if is_cgi():
+ import cgi, cgitb
+ #cgitb.enable()
+ #result = repr(sys.stdin.read())
+ data = cgi.FieldStorage()
+ data = data['text'].value
+ c = SpellChecker(data)
+ result = c.check()
+ if result == None:
+ result = ''
+ print 'Content-Type: text/plain'
+ print 'Content-Length: %s' % len(result)
+ print
+ print result
+elif __name__ == '__main__':
+ while 1:
+ line = raw_input('Enter text to check: ')
+ if line == 'q':
+ break
+ c = SpellChecker(line)
+ ret = c.check()
+ if ret is None:
+ print 'okay'
+ else:
+ print ret
Modified: kupu/trunk/kupu/common/kupuhelpers.js
==============================================================================
--- kupu/trunk/kupu/common/kupuhelpers.js (original)
+++ kupu/trunk/kupu/common/kupuhelpers.js Sat Apr 30 19:19:15 2005
@@ -75,6 +75,11 @@
function addEventHandler(element, event, method, context) {
/* method to add an event handler for both IE and Mozilla */
var wrappedmethod = new ContextFixer(method, context);
+ var args = new Array(null, null);
+ for (var i=4; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ };
+ wrappedmethod.args = args;
try {
if (_SARISSA_IS_MOZ) {
element.addEventListener(event, wrappedmethod.execute, false);
@@ -255,7 +260,6 @@
returns false if no nodes are left
*/
-
this.node = node;
this.current = node;
this.terminator = continueatnextsibling ? null : node;
@@ -1207,16 +1211,106 @@
return false;
};
+// return a copy of an array with doubles removed
+Array.prototype.removeDoubles = function() {
+ var ret = [];
+ for (var i=0; i < this.length; i++) {
+ if (!ret.contains(this[i])) {
+ ret.push(this[i]);
+ };
+ };
+ return ret;
+};
+
+Array.prototype.map = function(func) {
+ /* apply 'func' to each element in the array */
+ for (var i=0; i < this.length; i++) {
+ this[i] = func(this[i]);
+ };
+};
+
+Array.prototype.reversed = function() {
+ var ret = [];
+ for (var i = this.length; i > 0; i--) {
+ ret.push(this[i - 1]);
+ };
+ return ret;
+};
+
// JavaScript has a friggin' blink() function, but not for string stripping...
String.prototype.strip = function() {
var stripspace = /^\s*([\s\S]*?)\s*$/;
return stripspace.exec(this)[1];
};
+String.prototype.reduceWhitespace = function() {
+ /* returns a string in which all whitespace is reduced
+ to a single, plain space */
+ var spacereg = /(\s+)/g;
+ var copy = this;
+ while (true) {
+ var match = spacereg.exec(copy);
+ if (!match) {
+ return copy;
+ };
+ copy = copy.replace(match[0], ' ');
+ };
+};
+
+String.prototype.entitize = function() {
+ var ret = this.replace(/&/g, '&');
+ ret = ret.replace(/"/g, '"');
+ ret = ret.replace(/</g, '<');
+ ret = ret.replace(/>/g, '>');
+ return ret;
+};
+
+String.prototype.deentitize = function() {
+ var ret = this.replace(/>/g, '>');
+ ret = ret.replace(/</g, '<');
+ ret = ret.replace(/"/g, '"');
+ ret = ret.replace(/&/g, '&');
+ return ret;
+};
+
+String.prototype.urldecode = function() {
+ var reg = /%([a-fA-F0-9]{2})/g;
+ var str = this;
+ while (true) {
+ var match = reg.exec(str);
+ if (!match || !match.length) {
+ break;
+ };
+ var repl = new RegExp(match[0], 'g');
+ str = str.replace(repl, String.fromCharCode(parseInt(match[1], 16)));
+ };
+ return str;
+};
+
+String.prototype.centerTruncate = function(maxlength) {
+ if (this.length <= maxlength) {
+ return this;
+ };
+ var chunklength = maxlength / 2 - 3;
+ var start = this.substr(0, chunklength);
+ var end = this.substr(this.length - chunklength);
+ return start + ' ... ' + end;
+};
+
//----------------------------------------------------------------------------
// Exceptions
//----------------------------------------------------------------------------
+function debug(str, win) {
+ if (!win) {
+ win = window;
+ };
+ var doc = win.document;
+ var div = doc.createElement('div');
+ div.appendChild(doc.createTextNode(str));
+ doc.getElementsByTagName('body')[0].appendChild(div);
+};
+
// XXX don't know if this is the regular way to define exceptions in JavaScript?
function Exception() {
return;
Added: kupu/trunk/kupu/common/kupuimages/text-check.png
==============================================================================
Binary file. No diff available.
Modified: kupu/trunk/kupu/common/kupuinit.js
==============================================================================
--- kupu/trunk/kupu/common/kupuinit.js (original)
+++ kupu/trunk/kupu/common/kupuinit.js Sat Apr 30 19:19:15 2005
@@ -185,6 +185,10 @@
'kupu-editor-textarea');
kupu.registerTool('sourceedittool', sourceedittool);
+ var spellchecker = new KupuSpellChecker('kupu-spellchecker-button',
+ '../cgi/spellcheck.cgi');
+ kupu.registerTool('spellchecker', spellchecker);
+
// Drawers...
// Function that returns function to open a drawer
Added: kupu/trunk/kupu/common/kupuspellchecker.js
==============================================================================
--- (empty file)
+++ kupu/trunk/kupu/common/kupuspellchecker.js Sat Apr 30 19:19:15 2005
@@ -0,0 +1,131 @@
+function KupuSpellChecker(buttonid, scripturl, spanstyle,
+ winwidth, winheight, skip_tags) {
+ this.button = document.getElementById(buttonid);
+ this.scripturl = scripturl;
+ this.spanstyle = spanstyle || 'color: red; text-decoration: underline;';
+ this.winwidth = winwidth || '600';
+ this.winheight = winheight || '400';
+ this.skip_tags = skip_tags || ['head', 'script'];
+};
+
+KupuSpellChecker.prototype = new KupuTool;
+
+KupuSpellChecker.prototype.initialize = function(editor) {
+ this.editor = editor;
+ addEventHandler(this.button, 'click', this.check, this);
+};
+
+KupuSpellChecker.prototype.check = function() {
+ var request = Sarissa.getXmlHttpRequest();
+ request.open('POST', this.scripturl, true);
+ request.setRequestHeader('Content-Type',
+ 'application/x-www-form-urlencoded');
+ request.onreadystatechange = new ContextFixer(
+ this.stateChangeHandler,
+ this,
+ request).execute;
+ var result = this.getCurrentContents();
+ result = escape(result.strip().replace('\n', ' ').reduceWhitespace());
+ request.send('text=' + result);
+};
+
+KupuSpellChecker.prototype.stateChangeHandler = function(request) {
+ if (request.readyState == 4) {
+ var result = request.responseText;
+ if (!result) {
+ alert('There were no errors.');
+ } else {
+ this.displayUnrecognized(result);
+ };
+ };
+};
+
+KupuSpellChecker.prototype.getCurrentContents = function() {
+ var doc = this.editor.getInnerDocument().documentElement;
+ var iterator = new NodeIterator(doc);
+ var bits = [];
+ while (true) {
+ var node = iterator.next();
+ if (!node) {
+ break;
+ };
+ while (this.skip_tags.contains(node.nodeName.toLowerCase())) {
+ node = node.nextSibling;
+ iterator.setCurrent(node);
+ };
+ if (node.nodeType == 3) {
+ bits.push(node.nodeValue);
+ };
+ };
+ return bits.join(' ');
+};
+
+KupuSpellChecker.prototype.displayUnrecognized = function(words) {
+ // copy the current editable document into a new window
+ var doc = this.editor.getInnerDocument().documentElement;
+ var win = window.open('kupuspellchecker.html', 'spellchecker',
+ 'width=' + this.winwidth + ',' +
+ 'height=' + this.winheight + ',toolbar=no,' +
+ 'menubar=no,scrollbars=yes,alwaysRaised=yes');
+ var html = doc.innerHTML;
+ win.document.write('<html>' + doc.innerHTML + '</html>');
+ win.document.close();
+ addEventHandler(win, 'load', this.continueDisplay, this, win, words);
+};
+
+KupuSpellChecker.prototype.continueDisplay = function(win, words) {
+ words = words.split(' ').removeDoubles();
+
+ // walk through all elements of the body, colouring the text nodes
+ var body = win.document.getElementsByTagName('body')[0];
+ var iterator = new NodeIterator(body);
+ var node = iterator.next();
+ while (true) {
+ if (!node) {
+ break;
+ };
+ var next = iterator.next();
+ if (node.nodeType == 3) {
+ var span = win.document.createElement('span');
+ var before = node.nodeValue;
+ var after = this.colourText(before, words);
+ if (before != after) {
+ span.innerHTML = after;
+ var last = span.lastChild;
+ var parent = node.parentNode;
+ parent.replaceChild(last, node);
+ while (span.hasChildNodes()) {
+ parent.insertBefore(span.firstChild, last);
+ };
+ };
+ node = span;
+ };
+ node = next;
+ };
+};
+
+KupuSpellChecker.prototype.colourText = function(text, words) {
+ var currtext = text;
+ var newtext = '';
+ for (var i=0; i < words.length; i++) {
+ var reg = new RegExp('([^\w])(' + words[i] + ')([^\w])');
+ while (true) {
+ var match = reg.exec(currtext);
+ if (!match) {
+ newtext += currtext;
+ currtext = newtext;
+ newtext = '';
+ break;
+ };
+ var m = match[0];
+ newtext += currtext.substr(0, currtext.indexOf(m));
+ newtext += match[1] +
+ '<span style="' + this.spanstyle + '">' +
+ match[2] +
+ '</span>' +
+ match[3];
+ currtext = currtext.substr(currtext.indexOf(m) + m.length);
+ };
+ };
+ return currtext;
+};
Modified: kupu/trunk/kupu/common/kupustyles.css
==============================================================================
--- kupu/trunk/kupu/common/kupustyles.css (original)
+++ kupu/trunk/kupu/common/kupustyles.css Sat Apr 30 19:19:15 2005
@@ -130,6 +130,7 @@
.kupu-save-and-exit {background-image: url("kupuimages/exit.gif");}
.kupu-space {background-image: url("kupuimages/space.gif");}
.kupu-source {background-image: url("kupuimages/view-source.png");}
+.kupu-spellchecker {background-image: url("kupuimages/text-check.png");}
.kupu-subscript {background-image: url("kupuimages/subscript.png");}
.kupu-subscript-pressed {background-image: url("kupuimages/subscript.png"); background-color: white}
.kupu-superscript {background-image: url("kupuimages/superscript.png");}
Modified: kupu/trunk/kupu/default/include.kupu
==============================================================================
--- kupu/trunk/kupu/default/include.kupu (original)
+++ kupu/trunk/kupu/default/include.kupu Sat Apr 30 19:19:15 2005
@@ -13,6 +13,7 @@
<xi:include href="xmlconfig.kupu" />
<xi:include href="saveonpart.kupu" />
<xi:include href="sourceedit.kupu" />
+ <xi:include href="spellchecker.kupu" />
<xi:include href="contextmenu.kupu" />
<xi:include href="toolbar.kupu" />
<xi:include href="toolboxes.kupu" />
Added: kupu/trunk/kupu/default/spellchecker.kupu
==============================================================================
--- (empty file)
+++ kupu/trunk/kupu/default/spellchecker.kupu Sat Apr 30 19:19:15 2005
@@ -0,0 +1,26 @@
+<?xml version="1.0" ?>
+<kupu:feature
+ name="spellchecker"
+ implementation="default"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:kupu="http://kupu.oscom.org/namespaces/dist"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ i18n:domain="kupu"
+ >
+ <kupu:id>$Id: sourceedit.kupu 9779 2005-03-15 11:34:47Z duncan $</kupu:id>
+ <kupu:part name="jsincludes">
+ <script type="text/javascript" src="kupuspellchecker.js"> </script>
+ </kupu:part>
+
+ <kupu:part name="buttons">
+ <span class="kupu-tb-buttongroup kupu-spellchecker-span" id="kupu-spellchecker">
+ <button type="button"
+ class="kupu-spellchecker"
+ id="kupu-spellchecker-button"
+ title="check spelling"
+ i18n:attributes="title"
+ > </button>
+ </span>
+ </kupu:part>
+
+</kupu:feature>
Modified: kupu/trunk/kupu/default/wire.kupu
==============================================================================
--- kupu/trunk/kupu/default/wire.kupu (original)
+++ kupu/trunk/kupu/default/wire.kupu Sat Apr 30 19:19:15 2005
@@ -36,6 +36,7 @@
<kupu:insert-part feature="head" part="bootstrap-editor" />
<kupu:insert-part feature="saveonpart" part="jsincludes" />
<kupu:insert-part feature="sourceedit" part="jsincludes" />
+ <kupu:insert-part feature="spellchecker" part="jsincludes" />
<kupu:insert-part feature="drawers" part="jsincludes" />
</kupu:fill-slot>
@@ -82,6 +83,7 @@
<kupu:insert-part feature="drawers" part="buttons" />
<kupu:insert-part feature="toolbar" part="buttongroup-remove" />
<kupu:insert-part feature="toolbar" part="buttongroup-undo" />
+ <kupu:insert-part feature="spellchecker" part="buttons" />
<kupu:insert-part feature="sourceedit" part="buttons" />
</kupu:fill-slot>
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic