[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, '&amp;');
+    ret = ret.replace(/"/g, '&quot;');
+    ret = ret.replace(/</g, '&lt;');
+    ret = ret.replace(/>/g, '&gt;');
+    return ret;
+};
+
+String.prototype.deentitize = function() {
+    var ret = this.replace(/&gt;/g, '>');
+    ret = ret.replace(/&lt;/g, '<');
+    ret = ret.replace(/&quot;/g, '"');
+    ret = ret.replace(/&amp;/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"
+              >&#160;</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