Improvements of "gf -server" mode and related setup

"gf -server" mode now contains everything needed to run the minibar and
the grammar editor (including example-based grammar writing).

The Setup.hs script installs the required files where gf -server can find them.
These files have been moved to a new directory: src/www.

The separate server program pgf-http is now obsolete.
This commit is contained in:
hallgren
2011-10-12 17:03:54 +00:00
parent 0aba45560d
commit 44d1a5a9f7
51 changed files with 118 additions and 20 deletions

7
src/www/gfse/Makefile Normal file
View File

@@ -0,0 +1,7 @@
save: save.hs
ghc --make save.hs
install::
@make save
rsync -avz --exclude .DS_Store P *.html *.css *.js ../../runtime/javascript/minibar/support.js *.cgi *.manifest save www.grammaticalframework.org:/usr/local/www/GF/demos/gfse

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
src/www/gfse/P/w1s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/www/gfse/P/w2s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
src/www/gfse/P/w3s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
src/www/gfse/P/w4s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

43
src/www/gfse/TODO Normal file
View File

@@ -0,0 +1,43 @@
+ Safety question before deleting a grammar
+ Check identifier syntax
+ Allow lincat for deleted cat to be deleted
+ Allow lin for deleted fun to be deleted
+ Apply category alpha conversion in concrete syntax
+ Remove a concrete syntax
+ Apply function alpha conversion in concrete syntax
+ Change lhs of lins when function type is changed
+ Allow languages other than the ones in the given list to be added
+ Export as plain text
+ Allow definitions to be reordered
+ 1. possibility to compile the grammar set, returning a URL to a translator app
+ 2a. possibility to import modules - resource libraries
- 2b. possibility to import modules - user's own auxiliaries
- 3. possibility to upload own modules
+ 4. access to the created files in an on-line shell (making testing possible)
- 5. rule-to-rule type checking and guidance (e.g. with library oper
suggestions)
+ Try grammars in the Translation Quiz
+ Show lincat and lin before params and opers below
+ Create a new concrete syntax by copying an existing one.
- Easy access to compute_concrete from the editor
- Instead of starting with an empty grammar, start a new grammar by copying
an example.
+ Cloning grammars
- Allow grammars to contain a resoure module. Create the resource module by
factoring out common parts of the concrete syntaxes.
- Integrate example-based concrete syntax construction (using Ramona's tool)
- Open lexicon modules? They are not installed by default!
DictEng, MorphalouFre, DictSwe, DictTur, DictBul, DictFun, DictLav
+ compile only the uploaded grammar even if other grammars are present
+ 'flags startcat' is needed for grammars with only one category (since the
default startcat is S, even if it doesn't exist)
- Bug! After adding a 2nd def of a fun with a different type and then deleting
the old fun, the corresponding lin will have the wrong lhs.
+ Bug! The startcat menu shows the first category by default, but the startcat
flag is actually not set until a selection is made from the menu.

243
src/www/gfse/about.html Normal file
View File

@@ -0,0 +1,243 @@
<!DOCTYPE HTML>
<html>
<head>
<title>About: GF online editor for simple multilingual grammars</title>
<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren">
<meta name = "viewport" content = "width = device-width">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta charset="UTF-8">
<script type="text/javascript" src="slideshow.js"></script>
</head>
<body>
<h1>GF online editor for simple multilingual grammars</h1>
<div class=right>
<div class=slideshow>
<img onload="start_slideshow(this,{delay:5,fade:0.3})" src="P/w1s.jpg" alt="[GF online editor screen shoot]">
<img class=hidden src="P/w2s.jpg" alt="[GF online editor screen shoot]">
<img class=hidden src="P/w3s.jpg" alt="[GF online editor screen shoot]">
<img class=hidden src="P/w4s.jpg" alt="[GF online editor screen shoot]">
</div>
</div>
<h2>Introduction</h2>
Traditionally, <a href="http://www.grammaticalframework.org/">GF</a>
grammars are created in a text editor and tested in the
GF shell. Text editors know very little (if anything) about the syntax of
GF grammars, and thus provide little guidance for novice GF users. Also, the
grammar author has to download and install the GF software on his/her own
computer.
<p>
In contrast, the
<a href="."><em>GF online editor for simple multilingual grammars</em></a>
is available online, making it easier to get started. All that
is needed is a reasonably modern web browser. Even Android and iOS devices
can be used.
<p>
The editor
also guides the grammar author by showing a skeleton grammar file and
hinting how the parts should be filled in. When a new part is added to the
grammar, it is immediately checked for errors.
<p>
Editing operations are accessed by clicking on editing symbols embedded
in the grammar display:
<span class=more>+</span>=Add an item,
<span class=delete>×</span>=Delete an item,
<span class=edit>%</span>=Edit an item.
These are revealed when hovering over items. On touch devices, hovering is
in some cases simulated by tapping, but there is also a button at the bottom
of the display to "Enable editing on touch devices" that reveals all editing
symbols.
<p>
In spite of its name, the editor runs entierly in the web
browser, so once you have opened the web page, you can
<strong>continue editing</strong> grammars even while you are
<strong>offline</strong>.
<h3>Current status</h3>
<p>
At the moment, the editor supports only a small subset of the GF grammar
notation.
Proper error checking is done for abstract syntax, but not (yet) for concrete
syntax.
<p>
The grammars created with this editor always consists of one file for the
abstract syntax, and one file for each concrete syntax.
<h4>Abstract syntax</h4>
The supported abstract syntax corresponds to context-free grammars
(no dependent types). The definition of an abstract syntax is limited to
<ul>
<li>a list of <em>category names</em>,
<var>Cat<sub>1</sub></var> ; ... ; <var>Cat<sub>n</sub></var>,
<li>a list of <em>functions</em> of the form
<var>Fun</var> : <var>Cat<sub>1</sub></var> -> ... ->
<var>Cat<sub>n</sub></var>,
<li>and a <em>start category</em>.
</ul>
Available editing operations:
<ul>
<li>Categories can be added, removed and renamed. When renaming a category,
occurences of it in function types will be updated accordingly.
<li>Functions can be added, removed and edited. Concrete syntaxes are updated
to reflect changes.
<li>Functions can be reordered using drag-and-drop.
</ul>
Error checks:
<ul>
<li>Syntactically incorrect function definitions are refused.
<li>Semantic problem such as duplicated definitions or references to
undefined categories, are highlighted.
</ul>
<h4>Concrete syntax</h4>
At the moment, the concrete syntax for a language <var>L</var> is limited to
<ul>
<li>opening the Resource Grammar Library modules
<code>Syntax</code><var>L</var> and <code>Paradigms</code><var>L</var>,
<code>Lexicon</code><var>L</var> and <code>Extra</code><var>L</var>,
<li><em>linearization types</em> for the categories in the abstract syntax,
<li><em>linearizations</em> for the functions in the abstract syntax,
<li><em>parameter type definitions</em>,
<var>P</var> = <var>C<sub>1</sub></var> | ... |<var>C<sub>n</sub></var>,
<li>and <em>operation definitions</em>, <var>op</var> = <var>expr</var>,
<var>op</var> : <var>type</var> = <var>expr</var>,
</ul>
Available editing operations:
<ul>
<li>The LHSs of the linearization types and linearizations are determined by
the abstract syntax and do not need to be entered manually.
The RHSs can
be edited.
<li>Parameter types can be added, removed and edited.
<li>Operation definitons can be added, removed and edited.
<li>Definitions can be reordered (using drag-and-drop)
</ul>
Also,
<ul>
<li>When a new concrete syntax is added to the grammar, a copy of the
currently open concrete syntax is created, since copying and modifying
is usually easier than creating something new from scratch.
(If the abstract syntax is currently open, the new conrete syntax will
start out empty.)
<li>When adding a new concrete syntax, you normally pick one of the supported
languages from a list. The language code and the file name is determined
automatically. But you can also pick <em>Other</em> from the list and change
the language code afterwards to add a concrete syntax for a language
that is not in the list.
</ul>
Error checks:
<ul>
<li>The RHSs in the concrete syntax are checked
for syntactic correctness by the editor as they are entered.
(TODO: the syntax of parameter types is not check at the moment.)
<li>Duplicated definitions are highlighted. Checks for other
semantic errors are delayed until the grammar is compiled.
</ul>
<h3>Compiling and testing grammars</h3>
When pressing the <strong>Compile</strong> button, the grammar will be compiled
with GF, and any errors not detected by the editor will be reported.
If the grammar is free from errors the user can then
test the grammar by clicking on links to the online GF shell, the Minibar or
the Translation Quiz.
<h3><img class=right src="P/1307545089_weather_04.png" alt="">
<img class=right src="P/1306856253_weather_06.png" alt="">Grammars in the
cloud</h3>
While the editor normally stores grammars locally in the browser, it is also
possible to store grammars in the cloud. Grammars can be stored in the cloud
just for backup, or to make them accessible from multiple devices.
<p>
There is no automatic synchronization between local grammars and the cloud.
Instead, the user should press
<img src="P/1306856253_weather_06.png" alt="[Cloud Upload]">
to upload the grammars to the cloud, and press
<img src="P/1307545089_weather_04.png" alt="[Cloud download]">
to download grammars from the cloud. In both cases, complete grammars
are copied and older versions at the destination will be overwritten.
When a grammar is deleted, both the local copy and the copy in the cloud
is deleted.
<p>
Each device is initially assigned to its own unique cloud. Each device can thus
have its own set of grammars that are not available on other devices. It is
also possible to merge clouds and share a common set of grammars between
multiple devices: when uploading grammars to the cloud, a link to this grammar
cloud appears. Accessing this link from another device will cause the clouds of
the two devices to be merged. After this, grammars uploaded from one of the
devices can be downloaded on the other devices. Any number devices can join the
same grammar cloud in this way.
<p>
<strong>Note</strong> that while it is possible to copy grammars between
multiple devices, there is no way to merge concurrent edits from multiple
devices. If the same grammar is uploaded to the
cloud from multiple devices, the last upload wins. Thus the current
implementation is suitable for a single user switching between different
devices, but not recommended for sharing grammars between multiple users.
<p>
Also <strong>note</strong> that each grammar is assigned a unique identity
when it is first created. Renaming a grammar does not change its identity.
This means that name changes are propagated between devices like other changes.
<h3>Example-based grammar writing</h3>
This is work in progress...
<h3>Future work</h3>
This prototype gives an idea of how a web based GF grammar editor could work.
While this editor is implemented in JavaScript and runs in the web browser,
we do not expect to create a full implementation of GF that runs in the
web browser, but let the editor communicate with a server running GF.
<p>
By developing a GF server with an appropriate API, it should
be possible to extend the editor to support a larger fragment of GF,
to do proper error checking and make more of the existing GF shell functionality
accessible directly from the editor.
<p>
The current grammar cloud service is very primitive. In particular, it is not
suitable for multiple users developing a grammar in collaboration.
<h3>Related documents</h3>
<ul>
<li><a href="http://www.grammaticalframework.org/~hallgren/Talks/GF/gf-ide.html">GF Grammar Development Tools</a>. Slides from a presentation at the MOLTO meeting in Göteborg, March 2011.
<li><a href="http://www.grammaticalframework.org/grammar-ide/">The GF Grammar IDE</a>. MOLTO deliverable.
<li><a href="http://www.grammaticalframework.org/compiler-api">The GF Grammar
Compiler API</a>. MOLTO deliverable.
</ul>
<hr>
<div class=modtime><small>
<!-- hhmts start --> Last modified: Fri Oct 7 14:06:14 CEST 2011 <!-- hhmts end -->
</small></div>
<address>
<a href="http://www.cse.chalmers.se/~hallgren/">TH</a>
<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt="">
</address>
</body> </html>

148
src/www/gfse/cloud.js Normal file
View File

@@ -0,0 +1,148 @@
function with_dir(cont) {
var dir=local.get("dir","");
if(dir) cont(dir);
else ajax_http_get("upload.cgi?dir",
function(dir) {
local.put("dir",dir);
cont(dir);
});
}
function remove_cloud_grammar(g) {
var dir=local.get("dir")
if(dir && g.unique_name) {
var path=dir+"/"+g.unique_name+".json"
ajax_http_get("upload.cgi?rm="+encodeURIComponent(path),debug);
}
}
// Upload the grammar to the server and check it for errors
function upload(g) {
function upload2(dir) {
var form=node("form",{method:"post",action:"upload.cgi"+dir},
[hidden(g.basename+".gf",show_abstract(g))])
for(var i in g.concretes)
form.appendChild(hidden(g.basename+g.concretes[i].langcode+".gf",
show_concrete(g.basename)(g.concretes[i])));
editor.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
}
with_dir(upload2);
}
// Upload the grammar to store it in the cloud
function upload_json(cont) {
function upload3(resptext,status) {
local.put("json_uploaded",Date.now());
//debug("Upload complete")
if(cont) cont();
else {
var sharing=element("sharing");
if(sharing) sharing.innerHTML=resptext;
}
}
function upload2(dir) {
var prefix=dir.substr(10)+"-" // skip "/tmp/gfse."
//debug("New form data");
//var form=new FormData(); // !!! Doesn't work on Android 2.2!
var form="",sep="";
//debug("Preparing form data");
for(var i=0;i<local.count;i++) {
var g=local.get(i,null);
if(g) {
if(!g.unique_name) {
g.unique_name=prefix+i;
save_grammar(g)
}
//form.append(g.unique_name+".json",JSON.stringify(g));
form+=sep+encodeURIComponent(g.unique_name+".json")+"="+
encodeURIComponent(JSON.stringify(g))
sep="&"
}
}
//debug("Upload to "+prefix);
ajax_http_post("upload.cgi"+dir,form,upload3,cont)
}
with_dir(upload2);
}
function download_json() {
var dir=local.get("dir");
var index=grammar_index();
var downloading=0;
function get_list(ok,err) {
ajax_http_get("upload.cgi?ls="+dir,ok,err);
}
function get_file(file,ok,err) {
downloading++;
ajax_http_get("upload.cgi?download="+encodeURIComponent(dir+"/"+file),ok,err);
}
function file_failed(errormsg,status) {
debug(errormsg)
downloading--;
}
function file_downloaded(grammar) {
downloading--;
var newg=JSON.parse(grammar);
debug("Downloaded "+newg.unique_name)
var i=index[newg.unique_name];
if(i!=undefined) merge_grammar(i,newg)
else {
debug("New")
newg.index=null;
save_grammar(newg);
}
if(downloading==0) done()
}
function done() {
setTimeout(function(){location.href="."},2000);
}
function download_files(ls) {
local.put("current",0);
if(ls) {
//debug("Downloading "+ls);
var files=ls.split(" ");
cleanup_deleted(files);
for(var i in files) get_file(files[i],file_downloaded,file_failed);
}
else {
debug("No grammars in the cloud")
done()
}
}
get_list(download_files);
}
function link_directories(newdir,cont) {
with_dir(function(olddir) {
ajax_http_get("upload.cgi?rmdir="+olddir+"&newdir="+newdir,cont)
})
}
/* -------------------------------------------------------------------------- */
// Send a command to the GF shell
function gfshell(cmd,cont) {
alert("gfshell(...) not implmemented!!!")
}
// Check the syntax of an expression
function check_exp(s,cont) {
function check(gf_message) {
//debug("cc "+s+" = "+gf_message);
cont(/parse error/.test(gf_message) ? "parse error" : null);
}
if(navigator.onLine)
ajax_http_get("upload.cgi?cc="+encodeURIComponent(s),check)
else cont(null)
}

159
src/www/gfse/cloud2.js Normal file
View File

@@ -0,0 +1,159 @@
function with_dir(cont) {
var dir=local.get("dir","");
if(/^\/tmp\//.test(dir)) cont(dir);
else ajax_http_get("/new",
function(dir) {
local.put("dir",dir);
cont(dir);
});
}
function remove_cloud_grammar(g) {
var dir=local.get("dir")
if(dir && g.unique_name) {
var path=g.unique_name+".json"
gfcloud("rm",{file:path},debug);
}
}
// Upload the grammar to the server and check it for errors
function upload(g) {
function upload2(dir) {
var form=node("form",{method:"post",action:"/cloud"},
[hidden("dir",dir),hidden("command","make"),
hidden(g.basename+".gf",show_abstract(g))])
var files = [g.basename+".gf"]
for(var i in g.concretes) {
var cname=g.basename+g.concretes[i].langcode+".gf";
files.push(cname);
form.appendChild(hidden(cname,
show_concrete(g.basename)(g.concretes[i])));
}
editor.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
}
function upload3(message) { if(message) alert(message); }
with_dir(upload2)
}
// Upload the grammar to store it in the cloud
function upload_json(cont) {
function upload3(resptext,status) {
local.put("json_uploaded",Date.now());
//debug("Upload complete")
if(cont) cont();
else {
var sharing=element("sharing");
if(sharing) sharing.innerHTML=resptext;
}
}
function upload2(dir) {
var prefix=dir.substr(10)+"-" // skip "/tmp/gfse."
//debug("New form data");
//var form=new FormData(); // !!! Doesn't work on Android 2.2!
var form={dir:dir};
//debug("Preparing form data");
for(var i=0;i<local.count;i++) {
var g=local.get(i,null);
if(g) {
if(!g.unique_name) {
g.unique_name=prefix+i;
save_grammar(g)
}
//form.append(g.unique_name+".json",JSON.stringify(g));
form[encodeURIComponent(g.unique_name+".json")]=JSON.stringify(g)
}
}
//debug("Upload to "+prefix);
ajax_http_post("/cloud","command=upload"+encodeArgs(form),upload3,cont)
}
with_dir(upload2);
}
function download_json() {
var dir=local.get("dir");
var index=grammar_index();
var downloading=0;
function get_list(ok,err) { gfcloud("ls",{},ok,err) }
function get_file(file,ok,err) {
downloading++;
gfcloud("download",{file:encodeURIComponent(file)},ok,err);
}
function file_failed(errormsg,status) {
debug(errormsg)
downloading--;
}
function file_downloaded(grammar) {
downloading--;
var newg=JSON.parse(grammar);
debug("Downloaded "+newg.unique_name)
var i=index[newg.unique_name];
if(i!=undefined) merge_grammar(i,newg)
else {
debug("New")
newg.index=null;
save_grammar(newg);
}
if(downloading==0) done()
}
function done() {
setTimeout(function(){location.href="."},2000);
}
function download_files(ls) {
local.put("current",0);
if(ls) {
//debug("Downloading "+ls);
var files=ls.split(" ");
cleanup_deleted(files);
for(var i in files) get_file(files[i],file_downloaded,file_failed);
}
else {
debug("No grammars in the cloud")
done()
}
}
get_list(download_files);
}
function link_directories(newdir,cont) {
gfcloud("link_directories",{newdir:newdir},cont)
}
/* -------------------------------------------------------------------------- */
// Request GF cloud service
function gfcloud(cmd,args,cont,err) {
with_dir(function(dir) {
var enc=encodeURIComponent;
var url="/cloud?dir="+enc(dir)+"&command="+enc(cmd)+encodeArgs(args)
ajax_http_get(url,cont,err)
})
}
// Send a command to the GF shell
function gfshell(cmd,cont) {
with_dir(function(dir) {
var enc=encodeURIComponent;
ajax_http_get("/gfshell?dir="+enc(dir)+"&command="+enc(cmd),cont)
})
}
// Check the syntax of an expression
function check_exp(s,cont) {
function check(gf_message) {
//debug("cc "+s+" = "+gf_message);
cont(/parse error/.test(gf_message) ? "parse error" : null);
}
gfshell("cc "+s,check);
}

88
src/www/gfse/editor.css Normal file
View File

@@ -0,0 +1,88 @@
body { background: #eee; }
h1 { font-size: 175%; }
h1,h2,h3,h4,small { font-family: sans-serif; }
h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; }
#editor { max-width: 50em; }
div.home, div.grammar { border: 1px solid black; background: #9df; }
div.home { padding: 5px; }
div.files { margin: 0 8px 8px 8px; }
div#file { border: 2px solid #009; border-top-width: 0; }
pre.plain { border: 2px solid #009; }
div#file, pre.plain { background: white; padding: 0.6ex; }
.slideshow .hidden { display: none; }
img.cloud, img.right, div.right, div.modtime { float: right; }
.modtime { color: #999; white-space: nowrap; }
div.namebar { background: #9df; }
div.namebar table { width: 100%; }
.namebar h3, .home h3 { margin: 0; color: #009; }
td.right { text-align: right; }
.kw { font-weight: bold; font-family: sans-serif; color: #009; }
.sep { font-weight: bold; color: #009; }
div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; }
div.fun:hover, div.param:hover, div.lincat:hover, div.oper:hover, div.lin:hover,
div.template:hover
{ background: #def;}
.lin input[type=button],
.template input[type=button] { float: right; clear: right; margin: 0; }
.exb_output { background: #dfd; float: right; margin: 0 10px; }
.more, .delete { font-weight: bold; font-family: sans-serif; }
.more, .delete, .edit { cursor: pointer; }
.hover .more, .hover .delete, .hover .edit { visibility: hidden }
.hover .hidden, .nohover .ifhover { display: none; }
.editable:hover, .deletable:hover { background: #ff9; }
.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete
{ visibility: visible; }
.more { color: green; }
.edit { color: orange; }
.delete { color: red; }
.error_message,.inError { color: red; }
.template, .template .sep { color: #999; }
form { display: inline-block; }
table.tabs {
width: 100%;
border-width: 0; border-spacing: 0; empty-cells: show;
}
table.tabs td { text-align: center; border: 2px solid #009; padding: 2px; }
table.tabs td.active { background: white; border-bottom-width: 0; }
table.tabs td.inactive {
background: #cef;
border-top-color: #66c; border-left-color: #66c; border-right-color: #66c;
}
table.tabs td.gap
{ border-top-width: 0; border-left-width: 0; border-right-width: 0; }
table.tabs input[type=button] {
border: 0;
background: inherit;
color: #009;
font-size: inherit;
font-weight: bold;
/*text-decoration: underline;*/
}
input.string_edit { font-family: inherit; font-size: inherit; }
ul.languages { -moz-column-width: 20em; }
li { margin-top: 0.5ex; margin-bottom: 0.5ex; }
#sharing h1, #sharing .footer { display: none; }

1114
src/www/gfse/editor.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,158 @@
var example_based=[];
/*
-- cat lincat fun lin fun cat cat
environ :: ([(CId, CId)],[(CId, Expr)],[((CId, CId), [CId])]) -> Environ
*/
function exb_state(g,ci) {
var conc=g.concretes[ci]
function show_list(show1,xs) {
return "["+map(show1,xs).join(",")+"]";
}
function show_fun(fun) {
var t=fun.type
var res=t[t.length-1]
var args=t.slice(0,length-1);
return "(("+fun.name+","+res+"),["+args.join(",")+"])"
}
function show_lincat(lincat) {
return "("+lincat.cat+","+lincat.type+")"
}
function show_lin(lin) {
return "("+lin.fun+","+(lin.eb_lin||"?")+")"
}
function show_funs(funs) { return show_list(show_fun,funs) }
function show_lincats(lincats) { return show_list(show_lincat,lincats); }
function show_lins(lins) { return show_list(show_lin,lins) }
return "("+show_lincats(conc.lincats)
+","+show_lins(conc.lins)
+","+show_funs(g.abstract.funs)+")"
}
function exb_call(g,ci,command,args,cont) {
var url=window.exb_url || "exb/exb.fcgi";
var q=encodeArgs(args);
var cmd="?command="+command+"&state="+encodeURIComponent(exb_state(g,ci))+q;
http_get_json(url+cmd,cont)
}
function ask_possibilities(g,ci) {
var conc=g.concretes[ci];
function show_poss(poss) {
//debug("possibilities: "+JSON.stringify(poss))
var exready={}
for(var i in poss[0]) exready[poss[0][i]]=true;
var testable={}
for(var i in poss[1]) testable[poss[1][i]]=true;
example_based[ci]={exready:exready,testable:testable}
conc.example_based=true;
conc.example_lang=g.concretes[0].langcode;
reload_grammar(g);
}
exb_call(g,ci,"possibilities",{},show_poss)
}
function exb_extra(g,ci) {
var conc=g.concretes[ci];
function stop_exb() {
conc.example_based=false;
reload_grammar(g);
}
function exblangmenu() {
function opt(conc) { return option(conc.langcode,conc.langcode); }
// skip target language
var m =node("select",{},map(opt,g.concretes));
m.onchange=function() { conc.example_lang=m.value }
return m
}
function ask_poss() { ask_possibilities(g,ci) }
if(navigator.onLine && conc.example_based && !example_based[ci]) ask_poss();
return conc.langcode=="Eng"
? indent([text("Example based editing: "),
conc.example_based
? node("span",{},[button("Stop",stop_exb),
text(" Example language: "),
exblangmenu()
])
: button("Start",ask_poss)])
: text("")
}
function exb_linbuttons(g,ci,f) {
var conc=g.concretes[ci];
var fun=f.fun;
var eb=example_based[ci];
var exb_output;
function fill_example(maybetree) {
var tree=maybetree.Just
if(tree) {
if(f.template)
conc.lins.push({fun:f.fun,args:f.args,
lin:tree[0],eb_lin:tree[1]});
else {
f.lin=tree[0];
f.eb_lin=tree[1];
}
ask_possibilities(g,ci)
}
else exb_output.innerHTML="Bug: no tree found"
}
function show_example(example){
exb_output.innerHTML="";
var s=prompt(example[1]);
if(s) {
var t=function_type(g,fun);
var abscat=t[t.length-1]
var cat=cat_lincat(conc,abscat)
exb_output.innerHTML="...";
//server.parse({from:"ParseEng",cat:cat,input:s},fill_example)
exb_call(g,ci,"abstract_example",
{cat:cat,input:s,
params:"["+f.args.join(",")+"]",
abstract:example[0]},
fill_example)
}
}
function by_example() {
var dir=local.get("dir")
if(dir) {
if(exb_output) {
exb_output.innerHTML="...";
exb_call(g,ci,"provide_example",
{lang:g.basename+conc.example_lang,
fun:fun,
grammar:dir+"/"+g.basename+".pgf"},
show_example)
}
}
else exb_output.innerHTML="Compile the grammar first!"
}
function show_test(txt) {
exb_output.innerHTML="";
exb_output.appendChild(text(txt))
}
function test_it(b) {
if(exb_output) {
exb_output.innerHTML="...";
exb_call(g,ci,"test_function",{fun:fun},show_test)
}
}
var buttons=[];
if(conc.example_based && eb) {
if(eb.exready[fun])
buttons.push(button("By example",by_example))
if(eb.testable[fun] && f.eb_lin) {
var b=button("Test it",test_it);
buttons.push(b)
}
var exb_output=node("span",{"class":"exb_output"},[]);
buttons.push(exb_output)
}
return buttons
}

226
src/www/gfse/gf_abs.js Normal file
View File

@@ -0,0 +1,226 @@
/* Abstract syntax for a small subset of GF grammars in JavaScript */
function defined_cats(g) {
var dc={};
with(g.abstract)
for(var i in cats) dc[cats[i]]=true;
return dc;
}
function defined_funs(g) {
var df={};
with(g.abstract)
for(var i in funs) df[funs[i].name]=true;
return df;
}
function function_type(g,fun) {
with(g.abstract)
for(var i in funs) if(funs[i].name==fun) return funs[i].type
return null;
}
function cat_lincat(conc,cat) {
with(conc)
for(var i in lincats) if(lincats[i].cat==cat) return lincats[i].type
return null;
}
function rename_category(g,oldcat,newcat) {
function rename_cats(cats) {
for(var i in cats) if(cats[i]==oldcat) cats[i]=newcat;
}
function rename_type(t) {
for(var i in t) if(t[i]==oldcat) t[i]=newcat;
}
function rename_funs(funs) {
for(var i in funs) rename_type(funs[i].type)
}
function rename_abstract(a) {
rename_cats(a.cats);
rename_funs(a.funs);
}
function rename_lincat(lc) {
if(lc.cat==oldcat) lc.cat=newcat;
}
function rename_concrete(c) {
for(var i in c.lincats) rename_lincat(c.lincats[i]);
}
function rename_concretes(cs) {
for(var i in cs) rename_concrete(cs[i]);
}
rename_abstract(g.abstract)
rename_concretes(g.concretes);
return g;
}
function rename_function(g,oldfun,newfun) {
function rename_lin(lin) {
if(lin.fun==oldfun) lin.fun=newfun;
}
function rename_concrete(c) {
for(var i in c.lins) rename_lin(c.lins[i]);
}
for(var i in g.concretes) rename_concrete(g.concretes[i]);
return g;
}
function change_lin_lhs(g,fun) {
function change_lin(lin) {
if(lin.fun==fun.name) lin.args=arg_names(fun.type);
}
function change_concrete(c) {
for(var i in c.lins) change_lin(c.lins[i]);
}
for(var i in g.concretes) change_concrete(g.concretes[i]);
return g;
}
/* --- Parsing -------------------------------------------------------------- */
// GF idenfifier syntax:
var lex_id=/^[A-Za-z][A-Za-z0-9_']*$/
// See https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions
function check_id(s) { return lex_id.test(s); }
function check_name(s,kind) {
return check_id(s)
? null
: s+"? "+kind+" names must start with a letter and can contain letters, digits, _ and '"
}
function parse_fun(s) {
var ws=s.split(/\s+/);
var fun={name:"",type:[]};
/* Use a state machine to parse function definitions */
/* f : T1 -> ... -> Tn */
var state="name";
var ok=true;
for(var i=0;ok && i<ws.length;i++) {
if(ws[i]!="") {
switch(state) {
case "name": fun.name=ws[i]; state=":"; break;
case ":": ok=ws[i]==":"; state="type"; break;
case "type": fun.type.push(ws[i]); state="->"; break;
case "->": ok=ws[i]=="->"; state="type"; break;
}
}
}
var err=check_name(fun.name,"Function");
if(err) return {error: err};
return ok && state=="->"
? {ok:fun}
: { error : "Fun : Cat<sub>1</sub> -> ... -> Cat<sub>n</sub>" }
}
function parse_param(s) {
var ws=s.split("=");
if(ws.length==2) {
var name=ws[0].trim();
var err=check_name(name,"Parameter type");
return err ? { error:err } : { ok: { name:name,rhs:ws[1].trim() } }
}
else
return { error: "P = C1 | ... | Cn" }
}
function parse_oper(s) {
var i=s.indexOf(" ");
var operr = { error: "op = expr" }
if(i>0 && i<s.length-1) {
var name=s.substr(0,i).trim();
var rhs=s.substr(i).trim();
var err=check_name(name,"Operator");
return err
? {error:err}
: rhs!="" ? {ok: {name:name, rhs:rhs}}
: operr
}
else return operr
}
/* --- Print as plain text (normal GF concrete syntax) ---------------------- */
function show_type(t) {
var s="";
for(var i in t) {
if(i>0) s+=" -> ";
s+=t[i];
}
return s;
}
function show_fun(fun) {
return fun.name+" : "+show_type(fun.type);
}
function show_grammar(g) {
return show_abstract(g)+"\n"+show_concretes(g)
}
function show_abstract(g) {
// var startcat= g.abstract.cats.length==1 ? g.abstract.cats[0] : g.abstract.startcat;
var startcat= g.abstract.startcat || g.abstract.cats[0];
return "abstract "+g.basename+" = {\n\n"
+"flags coding = utf8 ;\n\n"
+show_startcat(startcat)
+show_cats(g.abstract.cats)
+show_funs(g.abstract.funs)
+"}\n";
}
function show_startcat(startcat) {
return startcat ? "flags startcat = "+startcat+";\n\n" : "";
}
function show_cats(cats) {
return cats.length>0 ? "cat\n "+cats.join("; ")+";\n\n" : "";
}
function show_funs(funs) { return show_list("fun",show_fun,funs); }
function show_concretes(g) {
return map(show_concrete(g.basename),g.concretes).join("\n\n");
}
function show_concrete(basename) {
return function(conc) {
return "--# -path=.:present\n"
+ "concrete "+basename+conc.langcode+" of "+basename+" ="
+show_opens(conc.opens)
+" {\n\nflags coding = utf8 ;\n\n"
+show_params(conc.params)
+show_lincats(conc.lincats)
+show_opers(conc.opers)
+show_lins(conc.lins)
+"}\n"
}
}
function show_list(kw,show1,list) {
return list.length>0
? kw+"\n "+map(show1,list).join(";\n ")+";\n\n"
: ""
}
function show_opens(opens) {
return opens && opens.length>0 ? "\n\nopen "+opens.join(", ")+" in" : ""
}
function show_params(params) { return show_list("param",show_param,params); }
function show_lincats(lincats) { return show_list("lincat",show_lincat,lincats); }
function show_opers(opers) { return show_list("oper",show_oper,opers); }
function show_lins(lins) { return show_list("lin",show_lin,lins); }
function show_param(p) { return p.name + " = " + p.rhs; }
function show_oper(p) { return p.name + " " + p.rhs; }
function show_lincat(p) { return p.cat + " = " + p.type; }
function show_lin(lin) {
return lin.fun + " " + lin.args.join(" ")+ " = " + lin.lin;
}

View File

@@ -0,0 +1,4 @@
CACHE MANIFEST
# 5
NETWORK:
*

19
src/www/gfse/grammars.cgi Normal file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
echo "Content-Type: text/javascript"
echo ""
case "$QUERY_STRING" in
jsonp=*) prefix="${QUERY_STRING#jsonp=}("; suffix=")" ;;
*) prefix=""; suffix=""
esac
echo -n "$prefix"
sep="["
for g in *.pgf ; do
echo -n "$sep\"$g\""
sep=", "
done
echo "]$suffix"
#echo "/*"
#set
#echo "*/"

48
src/www/gfse/index.html Normal file
View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html> <!-- manifest="gfse.manifest" -->
<head>
<title>GF online editor for simple multilingual grammars</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren">
<meta name = "viewport" content = "width = device-width">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head>
<body class=hover>
<h2>GF online editor for simple multilingual grammars</h2>
<div id=editor>
</div>
<small class="hidden">
<span class=more>+</span>=Add an item,
<span class=delete>×</span>=Delete item,
<span class=edit>%</span>=Edit item.
</small>
<small class="ifhover">Hover over items for hints and editing options.</small>
<noscript>
This page does not work without JavaScript.
</noscript>
<hr>
<div class=modtime><small>
HTML
<!-- hhmts start --> Last modified: Mon Oct 10 19:24:05 CEST 2011 <!-- hhmts end -->
</small></div>
<a href="about.html">About</a>
<pre id=debug></pre>
<script type="text/javascript" src="config.js"></script> <!-- optional -->
<script type="text/javascript" src="support.js"></script>
<script type="text/javascript" src="localstorage.js"></script>
<script type="text/javascript" src="gf_abs.js"></script>
<script type="text/javascript" src="example_based.js"></script>
<script type="text/javascript" src="editor.js"></script>
<script type="text/javascript" src="cloud2.js"></script>
<script type="text/javascript" src="sort.js"></script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
// We use localStorage to store the grammars,
// see http://diveintohtml5.org/storage.html
var local={
prefix:"gf.editor.simple.grammar",
get: function (name,def) {
var id=this.prefix+name
return localStorage[id] ? JSON.parse(localStorage[id]) : def;
},
put: function (name,value) {
var id=this.prefix+name;
localStorage[id]=JSON.stringify(value);
},
remove: function(name) {
var id=this.prefix+name;
localStorage.removeItem(id);
},
get count() { return this.get("count",0); },
set count(v) { this.put("count",v); }
}

82
src/www/gfse/molto.css Normal file
View File

@@ -0,0 +1,82 @@
body { color: #413b36;
background: #fffcfa;
}
h1 { font-size: 175%; }
h1,h2,h3,h4,small { font-family: sans-serif; }
h1,h2,h3,h4,a { color: #5c1a1a; }
h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; }
#editor { max-width: 50em; }
div.grammar { border: 2px solid #b09779; background: #642121; }
div.files { margin: 0 8px 8px 8px; }
div#file { border: 2px solid #b0977d; border-top-width: 0; }
pre.plain { border: 2px solid #b0977d; }
div#file, pre.plain { background: #fffcfa; padding: 0.6ex; }
.slideshow .hidden { display: none; }
img.right, div.right, div.modtime { float: right; }
.modtime { color: #999; white-space: nowrap; }
/*div.namebar { background: #642121; }*/
div.namebar table { width: 100%; }
.namebar h3 { margin: 0; color: white; }
td.right { text-align: right; }
.kw { font-weight: bold; font-family: sans-serif; color: #642121; }
.sep { font-weight: bold; color: #642121; }
div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; }
/*
div.fun, div.param, div.lincat, div.oper, div.lin
{ padding-left: 2em; text-indent: -2em; }
*/
.more, .delete { font-weight: bold; font-family: sans-serif; }
.more, .delete, .edit { cursor: pointer; }
.hover .more, .hover .delete, .hover .edit { visibility: hidden }
.hover .hidden, .nohover .ifhover { display: none; }
.editable:hover, .deletable:hover { background: #ff9; }
.namebar .editable:hover { background: #04b; }
.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete
{ visibility: visible; }
.more { color: green; }
.edit { color: orange; }
.delete { color: red; }
.error_message,.inError { color: red; }
.template, .template .sep { color: #999; }
form { display: inline-block; }
table.tabs {
width: 100%;
border-width: 0; border-spacing: 0; empty-cells: show;
}
table.tabs td { text-align: center; border: 2px solid #b09779; padding: 2px; }
table.tabs td.active { background: white; border-bottom-width: 0; }
table.tabs td.inactive {
background: #e1e1e1;
border-top-color: #b09779; border-left-color: #b09779; border-right-color: #b09779;
}
table.tabs td.gap
{ border-top-width: 0; border-left-width: 0; border-right-width: 0; }
table.tabs input[type=button] {
border: 0;
background: inherit;
color: #642121;
font-size: inherit;
font-weight: bold;
/*text-decoration: underline;*/
}
input.string_edit { font-family: inherit; font-size: inherit; }

25
src/www/gfse/save.hs Normal file
View File

@@ -0,0 +1,25 @@
import System(getArgs)
import CGI(getQuery,string)
import MUtils(apSnd)
main = save2 =<< getArgs
{-
save1 [dir] =
do fs@[ns,_] <- readIO =<< getContents
nes <- save_all fs
putStrLn $ unwords nes
where
save_all [ns,cs] = mapM (write1 dir) (zip ns cs)
-}
write1 dir (n,c) =
do writeFile (dir++"/"++ne) c
return ne
where
ne=if '.' `elem` n then n else n++".gf"
save2 [dir] =
do nfs <- getQuery
nes <- mapM (write1 dir . apSnd string) nfs
putStrLn $ unwords nes

27
src/www/gfse/share.html Normal file
View File

@@ -0,0 +1,27 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>Download from Grammar Cloud</title>
<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
</head>
<body>
<h1><img src="P/1307545048_weather_09.png" alt="">Download from Grammar Cloud</h1>
<pre id=debug></pre>
<hr>
<address></address>
<!-- hhmts start --> Last modified: Mon Oct 10 20:29:01 CEST 2011 <!-- hhmts end -->
<script type="text/javascript" src="support.js"></script>
<script type="text/javascript" src="localstorage.js"></script>
<script type="text/javascript" src="gf_abs.js"></script>
<script type="text/javascript" src="editor.js"></script>
<script type="text/javascript" src="cloud2.js"></script>
<script type="text/javascript" src="sort.js"></script>
<script type="text/javascript">
download_from_cloud();
</script>
</body>
</html>

86
src/www/gfse/slideshow.js Normal file
View File

@@ -0,0 +1,86 @@
var internet_explorer=navigator.appName=="Microsoft Internet Explorer";
/* How to change opacity in IE:
http://joseph.randomnetworks.com/archives/2006/08/16/css-opacity-in-internet-explorer-ie/
*/
var set_opacity =
internet_explorer
? function(el,o) { el.style.filter="alpha(opacity="+Math.round(o*100)+")";}
: function(el,o) { el.style.opacity=o; };
function start_slideshow(img,options) {
var p=img.parentNode;
if(p.tagName=="A") p=p.parentNode;
var is=p.getElementsByTagName("img");
if(is.length>1) {
var cur=0;
var w=img.width;
var h=img.height;
//p.style.position="relative";
p.style.minWidth=w+"px";
p.style.minHeight=h+"px";
var images=[];
for(var i=0;i<is.length;i++) {
images[i]=is[i];
var c=images[i];
if(internet_explorer) c.style.zoom=1;
c.style.position="absolute";
}
var timeout=1000*(options.delay || 5);
var ft=options.fade==null ? 1 : options.fade;
var tick=function() {
var c=images[cur];
cur= (cur+1) % images.length;
var n=images[cur];
set_opacity(n,0);
//n.style.position="static";
n.style.zIndex=1;
n.className="";
if(n.width>w) { w=n.width; p.style.minWidth=w+"px"; }
if(n.height>h) { h=n.height; p.style.minHeight=h+"px"; }
c.style.position="absolute";
c.style.zIndex=0;
fade(n,0,1,ft,function() {
if(c.width>n.width || c.height>n.height) fade(c,1,0,ft,null);
else set_opacity(c,0); });
//debug.innerHTML=w+"x"+h;
//for(var i=0;i<images.length;i++)
//debug.appendChild(text(" "+images[i].style.position));
}
//var debug=document.createElement("div");
//p.parentNode.insertBefore(debug,p);
//debug.innerHTML=w+"x"+h;
setInterval(tick,timeout);
}
//else alert("No slideshow!");
}
function fade(el,start,stop,t,after) {
// el: which element to fade
// start: starting opacity [0..1]
// stop: ending opacity [0..1]
// t: duration of fade (in seconds), default 1s
// after: function to call when done fading, optional
var dt=40; // Animation granularity, 1/40ms = 25fps
el.step=(stop-start)*dt/(1000*(t==null ? 1 : t));
el.stop=stop;
//alert("fade "+start+" "+stop+" "+el.step);
var done=function() {
clearInterval(el.timer);
el.timer=null;
if(after) after();
}
var f=function() {
var next=el.current+el.step;
if(next>=1) { next=1; done(); }
if(next<=0) { next=0; done(); }
set_opacity(el,next);
el.current=next
}
if(!el.timer) {
el.current=start;
el.timer=setInterval(f,dt);
}
}

252
src/www/gfse/upload.cgi Normal file
View File

@@ -0,0 +1,252 @@
#!/bin/bash
bin=/Users/hallgren/www/bin
charset="UTF-8"
AUTOHEADER=no
. $bin/cgistart.sh
export LC_CTYPE="UTF-8"
style_url="editor.css"
tmp="$documentRoot/tmp"
make_dir() {
dir="$(mktemp -d "$tmp/gfse.XXXXXXXXXX")"
# chmod a+rxw "$dir"
chmod a+rx "$dir"
cp "grammars.cgi" "$dir"
}
check_grammar() {
pagestart "Uploaded"
# echo "$PATH_INFO"
chgrp everyone "$dir"
chmod g+ws "$dir"
umask 002
# files=( $(Reg from-url | LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") )
files=( $(LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") )
gffiles=( )
otherfiles=( )
for f in ${files[*]} ; do
case "$f" in
*.gf) gffiles=( ${gffiles[*]} "$f" ) ;;
*) otherfiles=( ${otherfiles[*]} "$f" ) ;;
esac
done
if [ ${#otherfiles} -gt 0 -a -n "$PATH_INFO" ] ; then
echo "Use the following link for shared access to your grammars from multiple devices:"
begin ul
case "$SERVER_PORT" in
80) port="" ;;
*) port=":$SERVER_PORT"
esac
parent="http://$SERVER_NAME$port${REQUEST_URI%/upload.cgi/tmp/gfse.*}"
cloudurl="$parent/share.html#${dir##*/}"
li; link "$cloudurl" "$cloudurl"
end
#begin dl
#dt ; echo "◂"; link "javascript:history.back()" "Back to Editor"
#end
fi
cd $dir
if [ ${#gffiles} -gt 0 ] ; then
begin pre
echo "gf -s -make ${gffiles[*]}"
if gf -s -make ${gffiles[*]} 2>&1 ; then
end
h3 OK
begin dl
[ -z "$minibar_url" ] || { dt; echo "▸"; link "$minibar_url?/tmp/${dir##*/}/" "Minibar"; }
[ -z "$transquiz_url" ] || { dt; echo "▸"; link "$transquiz_url?/tmp/${dir##*/}/" "Translation Quiz"; }
[ -z "$gfshell_url" ] || { dt; echo "▸"; link "$gfshell_url?dir=${dir##*/}" "GF Shell"; }
dt ; echo "◂"; link "javascript:history.back()" "Back to Editor"
end
sed=();
for pgf in *.pgf ; do
sed=("${sed[@]}" -e "s%$pgf%<a href=\"${dir##*/}/$pgf\">$pgf</a>%")
done
begin pre
ls -l *.pgf | sed "${sed[@]}"
end
else
end
begin h3 class=error_message; echo Error; end
for f in ${gffiles[*]} ; do
h4 "$f"
begin pre class=plain
cat -n "$f"
end
done
fi
fi
begin div class=footer
hr
date
end
# begin pre ; env
endall
}
error400() {
echo "Status: 400"
pagestart "Error"
echo "What do you want?"
endall
}
error404() {
echo "Status: 404"
pagestart "Not found"
echo "Not found"
endall
}
if [ -z "$tmp" ] || ! [ -d "$tmp" ] ; then
pagestart "Error"
begin pre
echo "upload.cgi is not properly configured:"
if [ -z "$tmp" ] ; then
echo "cgiconfig.sh must define tmp"
elif [ ! -d "$tmp" ] || [ ! -w "$tmp" ] ; then
echo "$tmp must be a writeable directory"
fi
# cgiconfig.sh should define minibar & gfshell to allow grammars to be tested.
endall
else
case "$REQUEST_METHOD" in
POST)
case "$PATH_INFO" in
/tmp/gfse.*)
style_url="../../$style_url"
dir="$tmp/${PATH_INFO##*/}"
check_grammar
;;
*)
make_dir
echo >&2 "Using temporary directory $dir"
check_grammar
rm -rf "$dir"
esac
;;
GET)
case "$QUERY_STRING" in
dir) make_dir
ContentType="text/plain"
cgiheaders
echo_n "/tmp/${dir##*/}"
;;
ls=*)
dir=$(qparse "$QUERY_STRING" ls)
case "$dir" in
/tmp/gfse.*) # shouldn't allow .. in path !!!
path="$documentRoot$dir"
if [ -d "$path" ] ; then
ContentType="text/plain; charset=$charset"
cgiheaders
cd "$path"
shopt -s nullglob
echo_n *-*.json
else
error404
fi
;;
*) error400
esac
;;
rmdir=*)
dir=$(qparse "$QUERY_STRING" rmdir)
case "$dir" in
/tmp/gfse.*) # shouldn't allow .. in path !!!
path="$documentRoot$dir"
if [ -d "$path" ] ; then
ContentType="text/plain; charset=$charset"
cgiheaders
if [ -h "$path" ] ; then
cd "$path"
cd ..
rm "$path"
else
cd "$path"
shopt -s nullglob
rm *.gf *.gfo *-*.json *.pgf grammars.cgi
cd ..
rmdir "$path"
fi
newdir=$(qparse "$QUERY_STRING" newdir)
case "$newdir" in
/tmp/gfse.*) # shouldn't allow .. in path !!!
newnode="${newdir##*/}"
oldnode="${path##*/}"
ln -s "$newnode" "$oldnode"
esac
else
error404
fi
;;
*) error400
esac
;;
download=*)
file=$(qparse "$QUERY_STRING" download)
case "$file" in
/tmp/gfse.*/*.json) # shouldn't allow .. in path !!!
path="$documentRoot$file"
if [ -r "$path" ] ; then
ContentType="text/javascript; charset=$charset"
cgiheaders
cat "$path"
else
error404
fi
;;
*) error400
esac
;;
rm=*)
file=$(qparse "$QUERY_STRING" rm)
case "$file" in
/tmp/gfse.*/*.json) # shouldn't allow .. in path !!!
path="$documentRoot$file"
if [ -r "$path" ] ; then
ContentType="text/javascript; charset=$charset"
cgiheaders
rm "$path"
else
error404
fi
;;
*) error400
esac
;;
cc=*)
# Just to check an expression for syntax errors
exp=$(qparse "$QUERY_STRING" cc)
ContentType="text/plain; charset=$charset"
cgiheaders
echo "cc $exp" | GF_RESTRICTED=True gf -run
;;
"")
case "$PATH_INFO" in
/tmp/gfse.*/*.pgf)
path="$documentRoot$PATH_INFO"
if [ -r $path ] ; then
ContentType="application/binary"
cgiheaders
cat "$path"
else
error404
fi
;;
*)
error400
esac
;;
*) error400
esac
esac
fi

18
src/www/index.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!-- This is the start page served by "gf -server" -->
<title>GF web service</title>
<h1>GF web service</h1>
<h2>Available web services</h2>
<ul>
<li><a href="minibar/minibar.html">Minibar</a>
<li><a href="gfse/">GF online editor for simple multilingual grammars</a>
</ul>
<hr>
<a href="http://www.grammaticalframework.org/">Grammatical Framework</a>

180
src/www/minibar/about.html Normal file
View File

@@ -0,0 +1,180 @@
<!DOCTYPE html>
<html> <head>
<title>About Minibar</title>
<link rel=stylesheet type="text/css" href="minibar.css">
<meta charset="UTF-8">
</head>
<body>
<h1>About Minibar</h1>
<a href="minibar.html">Minibar</a> is an alternative implementation of the
<a href="http://www.grammaticalframework.org/">GF</a> web app
<a href="http://www.grammaticalframework.org:41296/fridge/">Fridge Poetry</a>.
It doesn't do everything the original Fridge Poetry does (e.g. drag-and-drop is missing),
so I refer to it as a minibar rather than a full refrigerator :-)
<p>
Some implementation details:
<ul class=space>
<li>It is implemented directly in JavaScipt. It does not use Google Web Toolkit or any big JavaScript libraries.
<li>It has been tested and found to work in the following browsers:
<ul>
<li>On the Mac: Firefox 3.5 &amp; 3.6, Safari 4.0, Opera 10.10 and
Google Chrome 4.0.249.49.
<li>On Linux: Firefox 3.0.18 & 3.5, Opera 10.10.
<li>On the Android Dev Phone: Android Mobile Safari 3.0.4 & 3.1.2
and Android Opera Mini 4.2.
</ul>
It does not seem work in Internet Explorer 7
(there are both styling and scripting issues).
There seems to be some rendering bugs in Chrome 5.0.342.9 β.
<li>The implementation consist of two JavaScript files:
<a href="minibar.js">minibar.js</a> and <a href="support.js">support.js</a>
The latter is also used in
<a href="http://spraakbanken.gu.se/swe/forskning/saldo/ordspel">a couple of
small web apps</a> based on the
<a href="http://spraakbanken.gu.se/sal/ws/">SALDO web services</a>.
<li>To access the GF web service, it uses the
<a href="http://en.wikipedia.org/wiki/JSON#JSONP">JSONP method</a>
mentioned in the GF
web services paper, which allows the web app to be hosted on a different server
from the GF web service. (To demonstrate this, I put the Minibar demo on
www.cs.chalmers.se, while the GF server that it calls is on
www.grammaticalframework.org.)
<li>As an experiment, it does no use the <code>grammars.xml</code> file,
but instead calls a little CGI script,
<a href="http://www.grammaticalframework.org:41296/grammars/grammars.cgi.txt">grammars.cgi</a>
which lists the .pgf files in the directory, in JSONP format.
(Note: if you want to install this on your own computer,
<ul>
<li>if you click on the link,
the CGI script will be downloaded as <code>grammars.cgi.txt</code>,
but it should be called <code>grammars.cgi</code> and stored on the server
in the same directory as the grammar files.
<li>for CGI scripts to work with lighttpd, <code>"mod_cgi"</code> needs
to be included in the definition of <code>server.modules</code> in the
<code>lighttpd.conf</code> file.)
</ul>
<li>[Added 2010-02-16] There is a button for generating random sentences.
<li>[Added 2010-02-23] All translations are shown, not just the first one,
if there are multiple parses.
<li>[Added 2010-02-25] Next to each translation, there is now a little tree
icon that you can click on to see a drawing of an abstract syntax tree or a
parse tree. If you click on a drawing it collapses back into a tree icon.
<li>[Added 2010-04-09] Preparations to support different ways to access the
grammar: currently we access a PGF server via JSONP, but I would also like
to support AJAX, and local/downloaded JavaScript grammars.
<li>[Added 2010-04-19] A text entry field appears when you click in
the sentence area (with a dashed border). This allows you to enter words by
typing on the keyboard. As you start typing word magnets that don't match what
you are typing are removed. When only one magnet remains, you can press enter
to complete the word.
<li>[Added 2010-04-19] There is a menu for choosing the output language:
you can pick "All" to translate to all available languages, or pick one
particular language.
<li>[Added 2010-04-19] You can pass options to the function
<code>start_minibar</code> to customize the user interface. The default is
<code>{show_abstract:true,show_trees:true}</code> to show the abstract syntax
of parsed sentences, and to show icons that expand to syntax/parse trees next
each translation.
These features can be turned off by setting the fields to <code>false</code>.
<li>[Added 2010-04-30] The grammar menu is omitted if there is only one
grammar in the grammar list.
<li>[Added 2010-04-30] Fewer hardwired constants and new
<code>start_minibar</code> options (server, grammars_url, grammar_list,
show_grouped_translations, delete_button_text) to make
<code>minibar.js</code> more resuable.)
<li>[Added 2010-05-26] The magnets are now created with
<code>&lt;input type=button&gt;</code> tags to make them clickable in more
browsers.
<li>[Added 2010-05-26] The text entry field is now visible from the start,
and it is removed when no more words can be added to the sentence. When you
press enter, a word is added if there is only one magnet left,
<em>or</em> if what you have entered exactly matches one of the remaining
magnet.
<li>[Added 2010-05-28] Added a link to make it easy to try the same sentence in
<a href="http://translate.google.com">Google Translate</a>.This can be
turned off by passing the option <code>{try_google:false}</code> to
<code>start_minibar</code>.
<li>[Added 2010-06-02] Added support for Help and Feedback buttons, controlled
by the options <code>feedback_url</code> and <code>help_url</code> passed to
<code>start_minibar</code>.
<li>[Added 2010-06-02] New option: <code>default_source_language</code>.
<li>[Added 2010-09-10] Minibar now automatically uses
<a href="http://en.wikipedia.org/wiki/XMLHttpRequest">XHR</a>
instead of JSONP when possible (i.e. when the HTML document and the
PGF service are on the same server).
<li>[Added 2010-09-10] The default input language is now the user's preferred
language, if possible. This is implemented by consulting the
<code>userLanguage</code> field in the grammar info output by pgf-server.
<li>[Added 2010-10-27] Keyboard input and completion should now work much
more smoothly:
<ul>
<li>When you press space, the current word will be completed (if incomplete)
and a new magnet will be created. If there is more than one possible
completion, no magnet is created, but the common prefix of the possible
completions is added to the text box.
<li>Instead of asking the server for possible completions every time a new
letter is added to the curent word, minibar only ask for completions for
whole words and then filters the list locally when more letters are entered,
speeding things up when server responses are slow.
</ul>
<li>[Added 2010-10-27] Code restructuring:
<ul>
<li>The PGF server API has been moved to its own file:
<a href="pgf_online.js">pgf_online.js</a>. This
allows it to be reused in other applicaitons without importing the entire
minibar. It also allows minibar to be used with different server
interfaces. <a href="minibar.html">minibar.html</a> has been updated to
show how you use the new <a href="minibar.js">minibar.js</a> and
<a href="pgf_online.js">pgf_online.js</a>.
<li>The minibar code has been rewritten to avoid storing state information
in the document tree and accessing it by referring to named document
elements. The code now also avoids using string literals containing
the names of top-level functions to specify event handlers for buttons
and menus. (The code is no longer introspective, so &alpha; conversion
will not change its meaning.)
</ul>
<li>[Added 2010-11-09] Some new documentation:
<ul>
<li><a href="gf-web-api-examples.html">gf-web-api-examples.html</a>:
examples illustrating the PGF server API provided by
<a href="pgf_online.js">pgf_online.js</a>.
<li><a href="example.html">example.html</a>: a minimal example of a web
page that uses <a href="pgf_online.js">pgf_online.js</a> to talk to the
PGF server.
</ul>
<li>[Added 2011-03-03] Added a button to display word alignment.
<li>[Changed 2011-03-22] Don't force focus to the typed input field
after every word. On touch-based devices, the on-screen keyboard kept
popping up after every word, which was very annoying if you were
entering a sentence by tapping on the magnets.
<li>[Changed 2011-08-03] Moved the initialization code in minibar.html to
<a href="minibar_online.js">minibar_online.js</a>.
<li>[Changed 2011-08-08] For improved modularity and reusability,
two smaller objects have been factored out from the Minibar object:
Input and Translations. These have been placed in two separate files:
<a href="minibar_input.js">minibar_input.js</a> and
<a href="minibar_translations.js">minibar_translations.js</a>.
Some common auxiliary functions have also been moved to a separate file:
<a href="minibar_support.js">minibar_support.js</a>.
<li>[Added 2011-08-09] Added some <a href="minibar-api.html">Minibar API</a>
documentation.
<li>[Changed 2011-08-22] Quick fix to allow literals to be entered:
if you press Enter, the current word will be accepted, even if there are no
matching completions.
(You can now use names of people when constructing sentences in the Letter
grammar, for example.)
</ul>
<hr>
<small class=modtime>
<!-- hhmts start --> Last modified: Mon Aug 22 19:31:37 CEST 2011 <!-- hhmts end -->
</small>
<address>
<a href="http://www.cs.chalmers.se/~hallgren/">TH</a>
<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address>
</address>
</body> </html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html> <head>
<title>PGF online server example</title>
<style type="text/css">
body { background: #ddd; }
h1, h2, h3, small, th { font-family: sans-serif; }
div.modtime { float: right; }
.modtime { color: #666; white-space: nowrap; }
</style>
<script type="text/JavaScript" src="support.js"></script>
<script type="text/JavaScript" src="pgf_online.js"></script>
<script type="text/JavaScript">
var server_options={
grammars_url: "http://www.grammaticalframework.org/grammars/",
grammar_list: ["Foods.pgf"]
}
var pgf_server = pgf_online(server_options);
function call_server() {
pgf_server.parse({from:"FoodsEng",input:document.forms[0].input.value},
show_output)
}
function show_output(parsed) {
document.getElementById("output").innerHTML=parsed[0].trees[0]
}
</script>
</head>
<body>
<h1>PGF online server example</h1>
<form onsubmit="call_server(); return false">
Input:
<input name=input size=50 value="this cheese is expensive">
<input type=submit value=Parse>
<p>
Output:
<span id=output></span>
</form>
<h2>Documentation</h2>
<ul>
<li><a href="gf-web-api-examples.html">GF Web API examples</a>
</ul>
<hr>
<div class=modtime><small>
<!-- hhmts start --> Last modified: Wed Aug 3 16:52:51 CEST 2011 <!-- hhmts end -->
</small></div>
<address><a href="http://www.cse.chalmers.se/~hallgren/">TH</a></address>
</body> </html>

View File

@@ -0,0 +1,44 @@
#!/bin/bash
bin=bin
AUTOHEADER=no
. $bin/cgistart.sh
save_feedback() {
getquery
if [ -n "$feedback_path" ] &&
echo "t=$(date +%F+%T)&ip=$REMOTE_ADDR&$query&accept_language=$HTTP_ACCEPT_LANGUAGE&user_agent=$(echo -n $HTTP_USER_AGENT | plain2url)" >> "$feedback_path"
then
pagestart "Thank you!"
echo "Your feedback has been saved."
begin script type="text/javascript"
echo "setTimeout(function(){window.close()},4000);"
end
pageend
else
pagestart "Feedback error"
echo "Your feedback could not be saved. Sorry."
p
tag 'input type=button onclick="javascript:history.back()" value="&lt;- Go back"'
pageend
fi
}
view_feedback() {
charset="UTF-8"
pagestart "Collected Feedback"
begin pre class=feedbacklist
Reg show reverse drop color_depth,pixel_depth,outer_size,inner_size,available_screen_size from-url <"$PATH_TRANSLATED" | plain2html
end
pageend
}
case "$PATH_TRANSLATED" in
"") save_feedback ;;
*) view_feedback
esac

View File

@@ -0,0 +1,48 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html> <head>
<title>Feedback</title>
<link rel=stylesheet type="text/css" href="minibar.css">
<script type="text/JavaScript" src="support.js"></script>
<script type="text/JavaScript" src="minibar.js"></script>
<meta name = "viewport" content = "width = device-width">
</head>
<body onload="prefill_feedback_form()">
<h2><span id=grammar></span> Feedback</h2>
<form class=feedback name=feedback action="feedback.cgi" method="post">
<input type=hidden name="grammar">
<p>
<input type=hidden name="from"> <span class=field id=from>...</span> input:
<input type=hidden name="input"> <span class=field id=input>...</span>
<div id=translation_box>
<p><input type=hidden name="to"> <span class=field id="to">...</span> translation:
<input type=hidden name="translation"> <span class=field id=translation>...</span>
<p><label accesskey="S">Suggest a better translation:
<textarea rows=3 name="improvement"></textarea></label>
</div>
<p><label accesskey="C">Comments:
<br><textarea rows=5 name="comment"></textarea></label>
<p>
<input type=submit value="Submit Feedback">
<input type=button value="Cancel" onclick="window.close()">
<input type=hidden name="inner_size">
<input type=hidden name="outer_size">
<input type=hidden name="screen_size">
<input type=hidden name="available_screen_size">
<input type=hidden name="color_depth">
<input type=hidden name="pixel_depth">
</form>
</body>
</html>

View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html>
<head>
<title>GF web services API examples</title>
<meta charset="UTF-8">
<style type="text/css">
body { background: #ddd; }
h1, h2, h3, small, th { font-family: sans-serif; }
dt { background: #cef; }
dt.js { background: white; margin-bottom: 1ex; }
dt.js em { color: #36f; }
dd { background: #ffc; margin-top: 1ex; margin-bottom: 1ex; }
dl.apiexamples>dt, dl.apiexamples>dd { font-family: monospace; }
dl.apiexamples>dd { white-space: pre; }
div.modtime { float: right; }
.modtime { color: #666; white-space: nowrap; }
@media projection {
div.intro { display: none; }
body {
font-size: 150%;
}
h2 { page-break-before: always; }
dl.apiexamples dd {
page-break-after: always;
/*border-style: none;*/
}
}
</style>
<body>
<h1>GF web services API examples</h1>
GF can be used interactively from the GF Shell. Some of the functionality
availiable in the GF shell is also available via the GF web services API.
<p>
The
<a href="http://code.google.com/p/grammatical-framework/wiki/GFWebServiceAPI">GF
Web Service API page</a> describes the calls supported by the GF web service
API. Below, we illustrate these calls by examples, and also show
how to make these calls from JavaScript using the API defined in
<a href="pgf_online.js"><code>pgf_online.js</code></a>.
<p>
<strong>Note</strong> that <code>pgf_online.js</code> was initially developed
with one particular web application in mind (the minibar), so the server API was
incomplete. It was simplified and generalized in August 2011 to support the
full API.
<dl>
<dt class=js>These boxes show what the calls look like in the JavaScript
API defined in <code>pgf_online.js</code>.
<dt>These boxes show the corresponding URLs sent to the PGF server.
<dd>These boxes show the JSON (JavaScript data structures) returned by the PGF
server. This will be passed to the callback function supplied in the
call.
</dl>
<h2>Initialization</h2>
<dl class=apiexamples>
<dt class=js>
<em>// Select which server and grammars to use:</em>
<br>var server_options = {
<br>&nbsp;&nbsp;grammars_url: "http://www.grammaticalframework.org/grammars/",
<br>&nbsp;&nbsp;grammar_list: ["Foods.pgf"] <em>// It's ok to skip this</em>
<br>}
<br>var server = pgf_online(server_options);
</dl>
<h2>Examples</h2>
<dl class=apiexamples>
<dt class=js> <em>// Get the list of available grammars</em>
<br>server.get_grammarlist(callback)
<dt>http://localhost:41296/grammars/grammars.cgi
<dd>["Foods.pgf","Phrasebook.pgf"]
<dt class=js> <em>// Select which grammar to use</em>
<br>server.switch_grammar("Foods.pgf")
<dt class=js><em>// Get list of concrete languages and other grammar info</em>
<br>server.grammar_info(callback)
<dt>http://localhost:41296/grammars/Foods.pgf
<dd>{"name":"Foods",
"userLanguage":"FoodsEng",
"categories":["Comment","Float","Int","Item","Kind","Quality","String"],
"functions":["Boring","Cheese","Delicious","Expensive","Fish","Fresh",
"Italian","Mod","Pizza","Pred","That","These","This","Those","Very",
"Warm","Wine"],
"languages":[{"name":"FoodsBul","languageCode":""},
{"name":"FoodsEng","languageCode":"en-US"},
{"name":"FoodsFin","languageCode":""},
{"name":"FoodsSwe","languageCode":"sv-SE"},
...]
}
<dt class=js><em>// Get a random syntax tree</em>
<br>server.get_random({},callback)
<dt>http://localhost:41296/grammars/Foods.pgf?command=random
<dd>[{"tree":"Pred (That Pizza) (Very Boring)"}]
<dt class=js><em>// Linearize a syntax tree</em>
<br>server.linearize({tree:"Pred (That Pizza) (Very Boring)",to:"FoodsEng"},callback)
<dt>http://localhost:41296/grammars/Foods.pgf?command=linearize&tree=Pred+(That+Pizza)+(Very+Boring)&to=FoodsEng
<dd>[{"to":"FoodsEng","text":"that pizza is very boring"}]
<dt class=js>server.linearize({tree:"Pred (That Pizza) (Very Boring)"},callback)
<dt>http://localhost:41296/grammars/Foods.pgf?command=linearize&tree=Pred+(That+Pizza)+(Very+Boring)
<dd>[{"to":"FoodsBul","text":"онази пица е много еднообразна"},
{"to":"FoodsEng","text":"that pizza is very boring"},
{"to":"FoodsFin","text":"tuo pizza on erittäin tylsä"},
{"to":"FoodsSwe","text":"den där pizzan är mycket tråkig"},
...
]
<dt class=js><em>// Parse a string</em>
<br>server.parse({from:"FoodsEng",input:"that pizza is very boring"},callback)
<dt>http://localhost:41296/grammars/Foods.pgf?command=parse&input=that+pizza+is+very+boring&from=FoodsEng
<dd>[{"from":"FoodsEng",
"brackets":{"cat":"Comment","fid":10,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"cat":"Quality","fid":9,"index":0,"children":[{"token":"very"},{"cat":"Quality","fid":8,"index":0,"children":[{"token":"boring"}]}]}]},
"trees":["Pred (That Pizza) (Very Boring)"]}]
<dt class=js><em>// Translate to all available languages</em>
<br>server.translate({from:"FoodsEng",input:"that pizza is very boring"},callback)
<dd>...
<dt class=js><em>// Translate to one language</em>
<br>server.translate({input:"that pizza is very boring", from:"FoodsEng", to:"FoodsSwe"}, callback)
<dt>http://localhost:41296/grammars/Foods.pgf?command=translate&input=that+pizza+is+very+boring&from=FoodsEng&to=FoodsSwe
<dd>[{"from":"FoodsEng",
"brackets":{"cat":"Comment","fid":10,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"cat":"Quality","fid":9,"index":0,"children":[{"token":"very"},{"cat":"Quality","fid":8,"index":0,"children":[{"token":"boring"}]}]}]},
"translations":
[{"tree":"Pred (That Pizza) (Very Boring)",
"linearizations":
[{"to":"FoodsSwe",
"text":"den där pizzan är mycket tråkig"}]}]}]
<dt class=js><em>// Get completions (what words could come next)</em>
<br>server.complete({from:"FoodsEng",input:"that pizza is very "},callback)
<dt>http://localhost:41296/grammars/Foods.pgf?command=complete&input=that+pizza+is+very+&from=FoodsEng
<dd>[{"from":"FoodsEng",
"brackets":{"cat":"_","fid":0,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"token":"very"}]},
"completions":["boring","delicious","expensive","fresh","Italian","very","warm"],
"text":""}]
</dl>
<hr>
<div class=modtime><small>
<!-- hhmts start --> Last modified: Sun Aug 21 10:52:43 CEST 2011 <!-- hhmts end -->
</small></div>
<address><a href="http://www.cse.chalmers.se/~hallgren/">TH</a></address>

View File

@@ -0,0 +1,235 @@
<!DOCTYPE html>
<html>
<head>
<title>GF web services API examples</title>
<meta charset="UTF-8">
<style type="text/css">
body { background: #eee; }
h1, h2, h3, small, th { font-family: sans-serif; }
th { text-align: left; }
h1,h2 { border-bottom: 2px solid black }
dt { background: #cef; }
code { background: #ffc; }
dt.js { background: white; margin-bottom: 1ex; }
dt.js em { color: #36f; }
dd { background: #ffc; margin-top: 1ex; margin-bottom: 1ex; }
dl.apiexamples>dt, dl.apiexamples>dd { font-family: monospace; }
dl.apiexamples>dd { white-space: pre; }
table.border { border-collapse: collapse; margin-top: 1ex; margin-bottom: 1ex; }
table.border td, table.border th { border: 1px solid black; background: #fcfcfc; }
div.modtime { float: right; }
.modtime { color: #666; white-space: nowrap; }
</style>
<body>
<h1>Minibar API</h1>
The Minibar web app consists of the following objects:
<ul>
<li><a href="#Minibar">Minibar</a>
<li><a href="#Input">Input</a>
<li><a href="#Translations">Translations</a>
</ul>
They are described below.
<h2 id=Minibar>The Minibar object</h2>
<p>
This object implements the complete Minibar web app. It is defined in
<a href="minibar.js">minibar.js</a>. It also uses the <code>Input</code>
and <code>Translations</code> objects described below, and some auxiliary
functions defined in <a href="minibar_support.js">minibar_support.js</a>
and <a href="support.js">support.js</a>, so to use it in an
HTML file, you would normally include at least the following:
<blockquote><pre>
&lt;script type="text/JavaScript" src="minibar.js">&lt;/script>
&lt;script type="text/JavaScript" src="minibar_input.js">&lt;/script>
&lt;script type="text/JavaScript" src="minibar_translations.js">&lt;/script>
&lt;script type="text/JavaScript" src="minibar_support.js">&lt;/script>
&lt;script type="text/JavaScript" src="support.js">&lt;/script>
</pre></blockquote>
<p>
For an example, see <a href="minibar.html">minibar.html</a>.
<h3>Constructor</h3>
<code>var minibar=new Minibar(server,options,target)</code>
<ul>
<li><code>server</code> is the PGF service object.
<li><code>options</code> is an object where the following properties
can be set to override various default options:
<table class=border>
<tr><th>Option<th>Default<th>Description
<tr><td>show_abstract<td>false<td rowspan=3>See Translations,
not used directly by Minibar
<tr><td>show_trees<td>false
<tr><td>show_grouped_translations<td>true
<tr><td>delete_button_text<td>"⌫"<td rowspan=3>See Input,
not used directly by Minibar
<tr><td>default_source_language<td>null
<tr><td>random_button<td>true
<tr><td>try_google<td>true<td>Include a button to try the current
sentence in Google Translate
<tr><td>feedback_url<td>null<td>Include a button to open a feedback
form. The HTTP server must be configured to handle form submissions
for this to work.
<tr><td>help_url<td>null<td>Include a button to open a help text.
</table>
<li><code>target</code> is the <code>id</code> of the HTML element inside
which the minibar user interface is created. It can be omitted if
the <code>id</code> is <code>minibar</code>. The HTML document should
contain something like this:
<blockquote><code>&lt;div id="minibar">&lt;/div></code></blockquote>
</ul>
<h3>Methods</h3>
There are several internal methods, but since this is a self-contained
web app, there is usually no need to call any methods from outside.
<h2 id=Input>The Input object</h2>
This object handles user input. Text can be entered by typing or by clicking
on the "refrigerator magnets".
<p>
It is defined in
<a href="minibar_input.js">minibar_input.js</a>.
It also uses some auxiliary functions defined
in <a href="minibar_support.js">minibar_support.js</a>
and <a href="support.js">support.js</a>, so to use it in an
HTML file, you would normally include at least the following:
<blockquote><pre>
&lt;script type="text/JavaScript" src="minibar_input.js">&lt;/script>
&lt;script type="text/JavaScript" src="minibar_support.js">&lt;/script>
&lt;script type="text/JavaScript" src="support.js">&lt;/script>
</pre></blockquote>
<h3>Constructor</h3>
<code>var input=new Input(server,translations,options)</code>
<ul>
<li><code>server</code> is the PGF service object
<li><code>options</code> is an object where the following properties
can be set to override various default options:
<table class=border>
<tr><th>Option<th>Default<th>Description
<tr><td>delete_button_text<td>"⌫"<td>the label for the button that deletes the last word
<tr><td>default_source_language<td>null<td>the concrete language to
use for input in case the user's browers doesn't supply a suitable
default. If none is provided the first language in alphabetical
order will be used.
<tr><td>random_button<td>true<td>include a button to generate a
random sentence
</table>
<li><code>translations</code> is the object that is notified when the input
has changed. In the minibar, this is the object that display translations, but
other apps might of course use the entered text for other purposes.
The following methods will be called:
<ul>
<li><code>translations.clear()</code> is called when there no entered
text.
<li><code>translations.translateFrom({from:<var>conc</var>,input:<var>string</var>})</code>
is called when the user has entered some text. The <code>from</code>
property is the name of the concrete syntax and the <code>input</code>
property is the entered text.
</ul>
</ul>
<h3>Properties and user interface</h3>
The <code>input</code> object created by the <code>Input</code> constructor
contains two field that the caller should add to the user interface:
<ul>
<li><code>input.main</code> is the main user interface where the current
input and the refrigerator magnets are displayed.
<li><code>input.menus</code> contains the menu for selecting input language,
and buttons for deleting the last word, clearing the input and generating
a random sentence (if enabled in the options)
</ul>
<h3>Methods</h3>
<ul>
<li><code>input.change_grammar(grammar_info)</code> should be called
after a different grammar is selected in the <code>server</code> object. It
will clear away old input and magnets, and update the input language menu
with the languages available in the new grammar.
</ul>
<h2 id=Translations>The Translations object</h2>
This object display translations. It is defined in
<a href="minibar_translations.js">minibar_translations.js</a>.
It also uses some auxiliary functions defined
in <a href="minibar_support.js">minibar_support.js</a>
and <a href="support.js">support.js</a>, so to use it in an
HTML file, you would normally include at least the following:
<blockquote><pre>
&lt;script type="text/JavaScript" src="minibar_input.js">&lt;/script>
&lt;script type="text/JavaScript" src="minibar_support.js">&lt;/script>
&lt;script type="text/JavaScript" src="support.js">&lt;/script>
</pre></blockquote>
<h3>Constructor</h3>
<code>var translations=new Translations(server,options)</code>
<ul>
<li><code>server</code> is the PGF service object.
<li><p><code>options</code> is an object where the following properties
can be set to override various default options:
<table class=border>
<tr><th>Option<th>Default<th>Description
<tr><td>show_abstract<td>false<td>show the abstract syntax in addition
to the concrete syntax for the translations
<tr><td>show_trees<td>false<td>add buttons to display syntax trees
next to translations.
<tr><td>show_grouped_translations<td>true<td>in case there are
multiple translations, group them by concrete language
</table>
</ul>
<h3>Properties and user interface</h3>
The <code>translations</code> object created by the
<code>Translations</code> constructor contains two field that the caller
should add to the user interface:
<ul>
<li><code>input.main</code> is the main user interface where the current
translations are displayed.
<li><code>input.menus</code> contains the menu for selecting target language.
</ul>
<h3>Methods</h3>
<ul>
<li><code>translations.change_grammar(grammar_info)</code> should be called
after a different grammar is selected in the <code>server</code> object. It
will clear away old translations and update the target language menu
with the languages available in the new grammar.
</ul>
<hr>
<div class=modtime>
<small class=modtime>
HTML <!-- hhmts start --> Last modified: Sun Aug 21 19:11:35 CEST 2011 <!-- hhmts end -->
</small>
</div>
<address>
<a href="Http://www.cse.chalmers.se/~hallgren/">TH</a>
</address>

View File

@@ -0,0 +1,53 @@
body {
background: #ccc url("brushed-metal.png");
}
h1, h2, h3, small, th { font-family: sans-serif; }
th, td { vertical-align: baseline; text-align: left; }
div#surface {
min-height: 3ex;
margin: 5px;
padding: 5px;
border: 3px dashed #e0e0e0;
}
div#words {
min-height: 3ex;
margin: 5px;
padding: 6px;
border: 3px solid #e0e0e0;
}
div.word, span.word, div#words div, div#words input[type=button] {
display: inline-block;
font-family: sans-serif;
font-size: 100%;
background-color: white;
border: 1px solid black;
padding: 3px;
margin: 3px;
}
.invalid { color: red; }
div.modtime { float: right; }
.modtime { color: #666; white-space: nowrap; }
ul.space>li { margin-top: 0.75ex; }
div#saldospel input[type=button] { font-size: 100%; }
div#saldospel input.correct { color: green; }
div#saldospel input.incorrect { color: red; }
#surface input[type=text] { width: 5em; }
.feedback textarea { width: 95%; }
span.field { background-color: #eee; }
pre.feedbacklist { background: white }
img.button { padding: 1px; }

View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<title>Minibar</title>
<link rel=stylesheet type="text/css" href="minibar.css">
<meta name = "viewport" content = "width = device-width">
<meta charset="UTF-8">
</head>
<body>
<h2>Minibar online</h2>
<div id=minibar></div>
<noscript>This page doesn't works unless JavaScript is enabled.</noscript>
<hr>
<small>
[<a href="about.html">About Minibar</a>
| <a href="http://www.grammaticalframework.org:41296/fridge/">Original Fridge Poetry</a>
&amp; <a href="http://www.grammaticalframework.org:41296/translate/">Translator</a>]
</small>
<small class=modtime>
HTML <!-- hhmts start --> Last modified: Mon Aug 8 18:04:22 CEST 2011 <!-- hhmts end -->
</small>
<address>
<a href="http://www.cse.chalmers.se/~hallgren/">TH</a>
<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address>
<script type="text/JavaScript" src="support.js"></script>
<script type="text/JavaScript" src="minibar.js"></script>
<script type="text/JavaScript" src="minibar_input.js"></script>
<script type="text/JavaScript" src="minibar_translations.js"></script>
<script type="text/JavaScript" src="minibar_support.js"></script>
<script type="text/JavaScript" src="pgf_online.js"></script>
<script type="text/javascript" src="minibar_online.js"></script>
</body>
</html>

176
src/www/minibar/minibar.js Normal file
View File

@@ -0,0 +1,176 @@
/* minibar.js
needs: minibar_support.js, minibar_input.js, minibar_translations.js, support.js
*/
/*
// This is essentially what happens when you call start_minibar:
if(server.grammar_list) grammars=server.grammar_list;
else grammars=server.get_grammarlist();
show_grammarlist(grammars)
select_grammar(grammars[0])
grammar_info=server.get_languages()
show_languages(grammar_info)
new_language()
complete_output=get_completions()
show_completions(complete_output)
*/
// For backward compatibility:
function start_minibar(server,opts,target) {
if(target) opts.target=target;
return new Minibar(server,opts);
}
/* --- Main Minibar object -------------------------------------------------- */
function Minibar(server,opts) {
// Contructor, typically called when the HTML document is loaded
/* --- Configuration ---------------------------------------------------- */
// default values for options:
this.options={
target: "minibar",
try_google: true,
feedback_url: null,
help_url: null
}
// Apply supplied options
if(opts) for(var o in opts) this.options[o]=opts[o];
/* --- Creating the components of the minibar --------------------------- */
this.translations=new Translations(server,this.options)
this.input=new Input(server,this.translations,this.options)
/* --- Creating user interface elements --------------------------------- */
this.menubar=empty("div");
this.extra=div_id("extra");
this.minibar=element(this.options.target);
this.minibar.innerHTML="";
with(this) {
appendChildren(menubar,[input.menus,translations.menus,input.buttons])
appendChildren(minibar,[menubar,input.main,translations.main,extra]);
append_extra_buttons(extra,options);
}
/* --- Minibar client state initialisation ------------------------------ */
this.grammar=null;
this.server=server;
/* --- Main program, this gets things going ----------------------------- */
with(this) {
if(server.grammar_list) show_grammarlist(server.grammar_list);
else server.get_grammarlist(bind(show_grammarlist,this));
}
}
Minibar.prototype.show_grammarlist=function(grammars) {
this.grammar_menu=empty_id("select","grammar_menu");
with(this) {
if(grammars.length>1) {
function opt(g) { return option(g,g); }
appendChildren(grammar_menu,map(opt,grammars));
grammar_menu.onchange=
bind(function() { select_grammar(grammar_menu.value); },this);
insertFirst(menubar,grammar_menu);
insertFirst(menubar,text("Grammar: "));
}
if(options.help_url)
menubar.appendChild(button("Help",bind(open_help,this)));
select_grammar(grammars[0]);
}
}
Minibar.prototype.select_grammar=function(grammar_name) {
var t=this;
//debug("select_grammar ");
function change_grammar() {
t.server.grammar_info(bind(t.change_grammar,t));
}
t.server.switch_grammar(grammar_name,change_grammar);
}
Minibar.prototype.change_grammar=function(grammar_info) {
var t=this;
with(t) {
//debug("show_languages ");
grammar=grammar_info;
input.change_grammar(grammar)
translations.change_grammar(grammar)
}
}
Minibar.prototype.append_extra_buttons=function(extra,options) {
with(this) {
if(options.try_google)
extra.appendChild(button("Try Google Translate",bind(try_google,this)));
if(options.feedback_url)
appendChildren(extra,[text(" "),button("Feedback",bind(open_feedback,this))]);
}
}
Minibar.prototype.try_google=function() {
with(this) {
var to=translations.target_lang();
var s=input.current.input;
if(input.surface.typed) s+=input.surface.typed.value;
var url="http://translate.google.com/?sl="
+langpart(input.current.from,grammar.name);
if(to!="All") url+="&tl="+to;
url+="&q="+encodeURIComponent(s);
window.open(url);
}
}
Minibar.prototype.open_help=function() {
with(this) open_popup(options.help_url,"help");
}
Minibar.prototype.open_feedback=function() {
with(this) {
// make the minibar state easily accessible from the feedback page:
minibar.state={grammar:grammar,current:input.current,
to:translations.to_menu.value,
translations:translations.translations};
open_popup(options.feedback_url,'feedback');
}
}
// This function is called from feedback.html
function prefill_feedback_form() {
var state=opener_element("minibar").state;
var trans=state.translations;
var gn=state.grammar.name
var to=langpart(state.to,gn);
var form=document.forms.namedItem("feedback");
setField(form,"grammar",gn);
setField(form,"from",langpart(state.current.from,gn));
setField(form,"input",state.current.input);
setField(form,"to",to);
if(to=="All") element("translation_box").style.display="none";
else setField(form,"translation",trans.single_translation.join(" / "));
// Browser info:
form["inner_size"].value=window.innerWidth+"×"+window.innerHeight;
form["outer_size"].value=window.outerWidth+"×"+window.outerHeight;
form["screen_size"].value=screen.width+"×"+screen.height;
form["available_screen_size"].value=screen.availWidth+"×"+screen.availHeight;
form["color_depth"].value=screen.colorDepth;
form["pixel_depth"].value=screen.pixelDepth;
window.focus();
}
/*
se.chalmers.cs.gf.gwt.TranslateApp/align-btn.png
GET /grammars/Foods.pgf?&command=abstrtree&tree=Pred+(This+Fish)+(Very+Fresh)
GET /grammars/Foods.pgf?&command=parsetree&tree=Pred+(This+Fish)+Expensive&from=FoodsAfr
GET /grammars/Foods.pgf?&command=alignment&tree=Pred+(This+Fish)+Expensive
*/

View File

@@ -0,0 +1,277 @@
/* --- Input object --------------------------------------------------------- */
function Input(server,translations,opts) { // Input object constructor
this.server=server;
this.translations=translations;
// Default values for options:
this.options={
delete_button_text: "⌫",
default_source_language: null,
random_button: true,
}
// Apply supplied options
if(opts) for(var o in opts) this.options[o]=opts[o];
// User interface elements
this.main=empty("div");
this.menus=empty("span");
this.buttons=empty("span");
this.surface=div_id("surface");
this.words=div_id("words");
this.from_menu=empty("select");
with(this) {
appendChildren(main,[surface,words]);
appendChildren(menus,[text(" From: "),from_menu])
appendChildren(buttons,
[button(options.delete_button_text,bind(delete_last,this),"H"),
button("Clear",bind(clear_all,this),"L")]);
if(options.random_button)
buttons.appendChild(button("Random",bind(generate_random,this),"R"));
}
/* --- Input client state initialization --- */
this.current={from: null, input: ""};
this.previous=null;
this.from_menu.onchange=bind(this.change_language,this);
}
Input.prototype.change_grammar=function (grammar) {
update_language_menu(this.from_menu,grammar);
set_initial_language(this.options,this.from_menu,grammar);
this.change_language();
}
Input.prototype.change_language=function () {
this.current.from=this.from_menu.value;
this.clear_all();
}
Input.prototype.clear_all1=function() {
with(this) {
remove_typed_input();
current.input="";
previous=null;
surface.innerHTML="";
translations.clear();
}
}
Input.prototype.clear_all=function() {
with(this) {
clear_all1();
get_completions();
}
}
Input.prototype.get_completions=function() {
with(this) {
//debug("get_completions ");
words.innerHTML="...";
server.complete({from:current.from,input:current.input},
bind(show_completions,this));
}
}
Input.prototype.show_completions=function(complete_output) {
with(this) {
//debug("show_completions ");
var completions=complete_output[0].completions;
var emptycnt=add_completions(completions)
if(true/*emptycnt>0*/) translations.translateFrom(current);
else translations.clear();
if(surface.typed && emptycnt==completions.length) {
if(surface.typed.value=="") remove_typed_input();
}
else add_typed_input();
}
}
Input.prototype.add_completions=function(completions) {
with(this) {
if(words.timeout) clearTimeout(words.timeout),words.timeout=null;
words.innerHTML="";
words.completions=completions;
words.word=[];
var t=surface.typed ? surface.typed.value : "";
var emptycnt=0;
for(var i=0;i<completions.length;i++) {
var s=completions[i];
if(s.length>0) {
var w=word(s);
words.appendChild(w);
words.word[i]=w;
}
else emptycnt++;
}
filter_completions(t,true);
return emptycnt;
}
}
Input.prototype.filter_completions=function(t,dim) {
with(this) {
if(words.timeout) clearTimeout(words.timeout),words.timeout=null;
words.filtered=t;
//if(dim) debug('filter "'+t+'"');
var w=words.word;
words.count=0;
var dimmed=0;
var prefix=""; // longest common prefix, for completion
for(var i=0;i<w.length;i++) {
var s=words.completions[i];
var keep=hasPrefix(s,t);
if(keep) {
if(words.count==0) prefix=s;
else prefix=(commonPrefix(prefix,s));
words.count++;
}
if(dim) {
w[i].style.opacity= keep ? "1" : "0.5";
if(keep) w[i].style.display="inline";
else dimmed++;
}
else
w[i].style.display=keep ? "inline" : "none";
}
words.theword=prefix;
if(dimmed>0)
words.timeout=setTimeout(function(){ filter_completions(t,false)},1000);
}
}
Input.prototype.add_typed_input=function() {
with(this) {
if(!surface.typed) {
var inp=empty("input","type","text");
inp.value="";
inp.setAttribute("accesskey","t");
inp.style.width="10em";
inp.onkeyup=bind(complete_typed,this);
surface.appendChild(inp);
surface.typed=inp;
inp.focus();
}
}
}
Input.prototype.remove_typed_input=function() {
with(this) {
if(surface.typed) {
surface.typed.parentNode.removeChild(surface.typed);
surface.typed=null;
}
}
}
Input.prototype.complete_typed=function(event) {
with(this) {
//element("debug").innerHTML=show_props(event,"event");
var inp=surface.typed;
//debug('"'+inp.value+'"');
var s=inp.value;
var ws=s.split(" ");
if(ws.length>1 || event.keyCode==13) {
if(ws[0]!=words.filtered) filter_completions(ws[0],true);
if(words.count==1) add_word(words.theword);
else if(event.keyCode==13) add_word(ws[0]) // for literals
else if(elem(ws[0],words.completions)) add_word(ws[0]);
else if(words.theword.length>ws[0].length) inp.value=words.theword;
}
else if(s!=words.filtered) filter_completions(s,true)
}
}
Input.prototype.generate_random=function() {
var t=this;
function show_random(random) {
t.clear_all1();
t.add_words(random[0].text);
}
function lin_random(abs) {
t.server.linearize({tree:abs[0].tree,to:t.current.from},show_random);
}
t.server.get_random({},lin_random);
}
Input.prototype.add_words=function(s) {
with(this) {
var ws=s.split(" ");
for(var i=0;i<ws.length;i++)
add_word1(ws[i]+" ");
get_completions();
}
}
Input.prototype.word=function(s) {
var t=this;
function click_word() {
if(t.surface.typed) t.surface.typed.value="";
t.add_word(s);
}
return button(s,click_word);
}
Input.prototype.add_word=function(s) {
with(this) {
add_word1(s+" ");
if(surface.typed) {
var s2;
if(hasPrefix(s2=surface.typed.value,s)) {
s2=s2.substr(s.length);
while(s2.length>0 && s2[0]==" ") s2=s2.substr(1);
surface.typed.value=s2;
}
else surface.typed.value="";
}
get_completions();
}
}
Input.prototype.add_word1=function(s) {
with(this) {
previous={ input: current.input, previous: previous };
current.input+=s;
var w=span_class("word",text(s));
if(surface.typed) surface.insertBefore(w,surface.typed);
else surface.appendChild(w);
}
}
Input.prototype.delete_last=function() {
with(this) {
if(surface.typed && surface.typed.value!="")
surface.typed.value="";
else if(previous) {
current.input=previous.input;
previous=previous.previous;
if(surface.typed) {
surface.removeChild(surface.typed.previousSibling);
surface.typed.focus();
}
else surface.removeChild(surface.lastChild);
translations.clear();
get_completions();
}
}
}
/* --- Auxiliary functions -------------------------------------------------- */
function set_initial_language(options,menu,grammar) {
if(grammar.userLanguage) menu.value=grammar.userLanguage;
else if(options.default_source_language) {
for(var i=0;i<menu.options.length;i++) {
var o=menu.options[i].value;
var l=langpart(o,grammar.name);
if(l==options.default_source_language) menu.value=o;
}
}
}

View File

@@ -0,0 +1,25 @@
// minibar_demo.js, assumes that minibar.js and pgf_online.js have been loaded.
var online_options={
//grammars_url: "http://www.grammaticalframework.org/grammars/",
//grammars_url: "http://tournesol.cs.chalmers.se:41296/grammars/",
//grammars_url: "http://localhost:41296/grammars/",
//grammar_list: ["Foods.pgf"], // leave undefined to get list from server
}
if(/^\?\/tmp\//.test(location.search)) {
online_options.grammars_url=location.search.substr(1);
}
var server=pgf_online(online_options);
var minibar_options= {
show_abstract: true,
show_trees: true,
show_grouped_translations: false,
default_source_language: "Eng",
//feedback_url: "feedback.html",
try_google: true
}
var minibar=new Minibar(server,minibar_options);

View File

@@ -0,0 +1,46 @@
/* --- Auxiliary functions -------------------------------------------------- */
function langpart(conc,abs) { // langpart("FoodsEng","Foods") == "Eng"
return hasPrefix(conc,abs) ? conc.substr(abs.length) : conc;
}
function update_language_menu(menu,grammar) {
// Replace the options in the menu with the languages in the grammar
var lang=grammar.languages;
menu.innerHTML="";
for(var i=0; i<lang.length; i++) {
var ln=lang[i].name;
if(!hasPrefix(ln,"Disamb")) {
var lp=langpart(ln,grammar.name);
menu.appendChild(option(lp,ln));
}
}
}
function button_img(url,action) {
var i=img(url);
i.setAttribute("class","button");
i.setAttribute("onclick",action);
return i;
}
function toggle_img(i) {
var tmp=i.src;
i.src=i.other;
i.other=tmp;
}
function setField(form,name,value) {
form[name].value=value;
var el=element(name);
if(el) el.innerHTML=value;
}
function open_popup(url,target) {
var w=window.open(url,target,'toolbar=no,location=no,status=no,menubar=no');
w.focus();
}
function opener_element(id) { with(window.opener) return element(id); }

View File

@@ -0,0 +1,162 @@
/* --- Translations object -------------------------------------------------- */
var tree_icon="tree-btn.png";
var alignment_icon="align-btn.png";
function Translations(server,opts) {
this.server=server;
// Default values for options:
this.options={
show_abstract: false,
show_trees: false,
show_grouped_translations: true,
}
// Apply supplied options
if(opts) for(var o in opts) this.options[o]=opts[o];
this.main=empty("div");
this.menus=empty("span");
this.to_menu=empty_id("select","to_menu");
appendChildren(this.menus,[text(" To: "), this.to_menu])
this.to_menu.onchange=bind(this.get_translations,this);
}
Translations.prototype.change_grammar=function(grammar) {
this.grammar=grammar;
update_language_menu(this.to_menu,grammar);
insertFirst(this.to_menu,option("All","All"));
this.to_menu.value="All";
}
Translations.prototype.clear=function() {
this.main.innerHTML="";
}
Translations.prototype.translateFrom=function(current) {
this.current=current;
this.get_translations();
}
Translations.prototype.get_translations=function() {
with(this) {
var c=current;
if(options.show_grouped_translations)
server.translategroup({from:c.from,input:c.input},
bind(show_groupedtranslations,this));
else
server.translate({from:c.from,input:c.input},
bind(show_translations,this));
}
}
Translations.prototype.tdt=function(tree_btn,txt) {
with(this) {
return options.show_trees ? tda([tree_btn,txt]) : td(txt);
}
}
Translations.prototype.target_lang=function() {
with(this) return langpart(to_menu.value,grammar.name);
}
Translations.prototype.show_translations=function(translationResults) {
with(this) {
var trans=main;
//var to=target_lang(); // wrong
var to=to_menu.value;
var cnt=translationResults.length;
//trans.translations=translations;
trans.single_translation=[];
trans.innerHTML="";
/*
trans.appendChild(wrap("h3",text(cnt<1 ? "No translations?" :
cnt>1 ? ""+cnt+" translations:":
"One translation:")));
*/
for(p=0;p<cnt;p++) {
var tra=translationResults[p];
if (tra.translations != null) {
for (q = 0; q < tra.translations.length; q++) {
var t = tra.translations[q];
var lin=t.linearizations;
var tbody=empty("tbody");
if(options.show_abstract && t.tree)
tbody.appendChild(
tr([th(text("Abstract: ")),
tdt(node("span",{},[abstree_button(t.tree),
alignment_button(t.tree)]),
text(" "+t.tree))]));
for(var i=0;i<lin.length;i++) {
if(lin[i].to==to)
trans.single_translation.push(lin[i].text);
if(to=="All" || lin[i].to==to)
tbody.appendChild(tr([th(text(langpart(lin[i].to,grammar.name)+": ")),
tdt(parsetree_button(t.tree,lin[i].to),
text(lin[i].text))]));
}
trans.appendChild(wrap("table",tbody));
}
}
else if(tra.typeErrors) {
var errs=tra.typeErrors;
for(var i=0;i<errs.length;i++)
trans.appendChild(wrap("pre",text(errs[i].msg)))
}
}
}
}
Translations.prototype.show_groupedtranslations=function(translationsResult) {
with(this) {
var trans=main;
var to=target_lang();
//var to=to_menu.value // wrong
var cnt=translationsResult.length;
//trans.translations=translationsResult;
trans.single_translation=[];
trans.innerHTML="";
for(p=0;p<cnt;p++) {
var t=translationsResult[p];
if(to=="All" || t.to==to) {
var lin=t.linearizations;
var tbody=empty("tbody");
if(to=="All") tbody.appendChild(tr([th(text(t.to+":"))]));
for(var i=0;i<lin.length;i++) {
if(to!="All") trans.single_translation[i]=lin[i].text;
tbody.appendChild(tr([td(text(lin[i].text))]));
if (lin.length > 1) tbody.appendChild(tr([td(text(lin[i].tree))]));
}
trans.appendChild(wrap("table",tbody));
}
}
}
}
function abstree_button(abs) {
var i=button_img(tree_icon,"toggle_img(this)");
i.title="Click to display abstract syntax tree"
i.other=server.current_grammar_url+"?command=abstrtree&tree="+encodeURIComponent(abs);
return i;
}
function alignment_button(abs) {
var i=button_img(alignment_icon,"toggle_img(this)");
i.title="Click to display word alignment"
i.other=server.current_grammar_url+"?command=alignment&tree="+encodeURIComponent(abs);
return i;
}
function parsetree_button(abs,lang) {
var i=button_img(tree_icon,"toggle_img(this)");
i.title="Click to display parse tree"
i.other=server.current_grammar_url
+"?command=parsetree&from="+lang+"&tree="+encodeURIComponent(abs);
return i;
}

View File

@@ -0,0 +1,96 @@
// Assumes that Services.js has been loaded
function pgf_offline(options) {
var server = {
// State variables (private):
grammars_url: "",
grammar_list: ["Foods.pgf"],
current_grammar_url: null,
pgf : null,
// Methods:
switch_grammar: function(grammar_url,cont) {
//debug("switch_grammar ");
var new_grammar_url=this.grammars_url+grammar_url;
var self=this;
var update_pgf=function(pgfbinary) {
debug("Got "+new_grammar_url+", length="
+pgfbinary.length+", parsing... ");
self.pgf = {v: Services_decodePGF.v({v:pgfbinary}) }
//debug("done")
self.current_grammar_url=new_grammar_url;
cont();
}
ajax_http_get_binary(new_grammar_url,update_pgf);
},
get_grammarlist: function(cont) { cont([this.grammar_list]); },
get_languages: function(cont) {
cont(fromJSValue(Services_grammar.v(this.pgf)))
},
grammar_info: function(cont) {
cont(fromJSValue(Services_grammar.v(this.pgf)))
},
get_random: function(cont) {
alert("Random generation not supported yet in the offline version");
},
linearize: function(args,cont) {
cont(fromJSValue(Services_linearize.v(this.pgf)(v(args.tree))(v(args.to))));
},
complete: function(args,cont) {
cont(fromJSValue(Services_complete.v(this.pgf)(v(args.from))(v(args.input))));
},
parse: function(args,cont) {
cont(fromJSValue(Services_parse.v(this.pgf)(v(args.from))(v(args.input))));
},
translate: function(args,cont) {
cont(fromJSValue(Services_translate.v(this.pgf)(v(args.from))(v(args.input))));
},
translategroup: function(args,cont) {
cont(fromJSValue(Services_translategroup.v(this.pgf)(v(args.from))(v(args.input))));
}
};
for(var o in options) server[o]=options[o];
return server;
};
// See https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest
function ajax_http_get_binary(url,callback) {
var http=GetXmlHttpObject()
if (http==null) {
alert ("Browser does not support HTTP Request")
return
}
var statechange=function() {
if (http.readyState==4 || http.readyState=="complete") {
if(http.status==200) {
var buffer=http.mozResponseArrayBuffer;
if(buffer) callback(bufferToString(buffer)) // Gecko 2 (Firefox 4)
else callback(http.responseText); // other browsers
}
else alert("Request for "+url+" failed: "
+http.status+" "+http.statusText);
}
}
http.onreadystatechange=statechange;
http.open("GET",url,true);
http.overrideMimeType('text/plain; charset=x-user-defined');
http.send(null);
//dump("http get "+url+"\n")
return http;
}
function bufferToString(buffer) {
// This function converts to the current representation of ByteString,
// but it would be better to use binary buffers for ByteStrings as well.
debug("bufferToString");
var u=new Uint8Array(buffer);
var a=new Array(u.length);
for(var i=0;i<u.length;i++)
a[i]=String.fromCharCode(u[i]);
return a.join("");
}

View File

@@ -0,0 +1,52 @@
/* --- Grammar access object ------------------------------------------------ */
function pgf_online(options) {
var server = {
// State variables (private):
grammars_url: "/grammars/",
grammar_list: null,
current_grammar_url: null,
// Methods:
switch_grammar: function(grammar_url,cont) {
this.current_grammar_url=this.grammars_url+grammar_url;
if(cont) cont();
},
get_grammarlist: function(cont) {
http_get_json(this.grammars_url+"grammars.cgi",cont);
},
pgf_call: function(cmd,args,cont) {
var url=this.current_grammar_url+"?command="+cmd+encodeArgs(args)
http_get_json(url,cont);
},
get_languages: function(cont) { this.pgf_call("grammar",{},cont); },
grammar_info: function(cont) { this.pgf_call("grammar",{},cont); },
get_random: function(args,cont) { // cat, limit
args.random=Math.random(); // side effect!!
this.pgf_call("random",args,cont);
},
linearize: function(args,cont) { // tree, to
this.pgf_call("linearize",args,cont);
},
complete: function(args,cont) { // from, input, cat, limit
this.pgf_call("complete",args,cont);
},
parse: function(args,cont) { // from, input, cat
this.pgf_call("parse",args,cont);
},
translate: function(args,cont) { // from, input, cat, to
this.pgf_call("translate",args,cont);
},
translategroup: function(args,cont) { // from, input, cat, to
this.pgf_call("translategroup",args,cont);
}
};
for(var o in options) server[o]=options[o];
if(server.grammar_list && server.grammar_list.length>0)
server.switch_grammar(server.grammar_list[0]);
return server;
}

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>Phrasebook</title>
<link rel=stylesheet type="text/css" href="minibar.css">
<meta charset="UTF-8">
<meta name = "viewport" content = "width = device-width">
</head>
<body>
<div id=minibar></div>
<hr>
<small>
Powered by <a href="http://www.grammaticalframework.org/">GF</a>,
see <a href="http://www.grammaticalframework.org/examples/phrasebook/doc-phrasebook.html">doc</a>.
</small>
<script type="text/JavaScript" src="support.js"></script>
<script type="text/JavaScript" src="minibar.js"></script>
<script type="text/JavaScript" src="minibar_input.js"></script>
<script type="text/JavaScript" src="minibar_translations.js"></script>
<script type="text/JavaScript" src="minibar_support.js"></script>
<script type="text/JavaScript" src="pgf_online.js"></script>
<script type="text/JavaScript">
var online_options={
// grammars_url: "http://www.grammaticalframework.org/grammars/",
//grammars_url: "http://tournesol.cs.chalmers.se:41296/grammars",
//grammars_url: "http://localhost:41296/grammars/",
grammar_list: ["Phrasebook.pgf"] // leave undefined to get list from server
}
var server=pgf_online(online_options);
var phrasebook_options={
delete_button_text: "Del",
help_url: "http://www.grammaticalframework.org/examples/phrasebook/help-phrasebook.html",
feedback_url: "feedback.html",
default_source_language: "Eng"
}
start_minibar(server,phrasebook_options)
</script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html> <head>
<title>Saldotest</title>
<link rel=stylesheet type="text/css" href="minibar.css">
<script type="text/JavaScript" src="support.js"></script>
<script type="text/JavaScript" src="saldotest.js"></script>
<meta name = "viewport" content = "width = device-width">
</head>
<body onload="start_saldotest();start_saldospel()">
<h2>Vilket ord ska bort?</h2>
<div id=saldospel>
</div>
<h2>Hel- och halvspöke</h2>
<div id=saldotest>
</div>
<hr>
<small>
[Baserad på <a href="http://spraakbanken.gu.se/sal/ws/">SALDOs nättjänster</a>]
</small>
<small class=modtime>
HTML <!-- hhmts start --> Last modified: Thu May 27 14:02:42 CEST 2010 <!-- hhmts end -->
</small>
<address>TH <img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address>
</body>
</html>

View File

@@ -0,0 +1,340 @@
var Saldo_ws_url = "http://spraakbanken.gu.se/ws/saldo-ws/";
//var Saldo_ff_url = Saldo_ws_url+"ff/json+remember_completions/";
var Saldo_lid_url = Saldo_ws_url+"lid/json";
function saldo_ws(fn,fmt,arg,cont_name) {
jsonp(Saldo_ws_url+fn+"/"+fmt+(cont_name ? "+"+cont_name : "")+"/"+arg,"");
}
function saldo_json(fn,arg,cont_name) { saldo_ws(fn,"json",arg,cont_name); }
function saldo_lid(arg,cont_name) { saldo_json("lid",arg,cont_name); }
function saldo_lid_rnd(cont_name) { saldo_lid("rnd?"+Math.random(),cont_name); }
var ordlista=[];
var current="";
function start_saldotest() {
appendChildren(element("saldotest"),
[button("Slumpa","random_word()"),
button("Rensa","clear_all()"),
button("⌫","delete_last()"),
//button("Ordlista","show_ordlista()"),
button("Visa tänkbara drag","show_moves()"),
button("Gör ett drag","make_a_move()"),
//button("Visa prefix","show_prefixes()"),
div_id("surface"),
div_id("words"),
div_id("translations")])
var style0="min-height: 3ex; margin: 5px; padding: 5px;";
element("surface").setAttribute("style",style0+"border: 3px dashed #e0e0e0;");
element("words").setAttribute("style",style0+"border: 3px solid #e0e0e0;");
clear_all();
}
function random_word() {
saldo_lid_rnd("show_random");
}
function show_random(lid) {
var lex=lid.lex;
reset_all(lex.substring(0,lex.indexOf('.')));
}
function clear_all() { reset_all(""); }
function reset_all(s) {
current=s;
element("surface").innerHTML=s;
element("translations").innerHTML="";
get_completions();
}
function delete_last() {
var len=current.length;
if(len>0) {
current=current.substring(0,len-1);
var s=element("surface");
s.innerHTML=current;
element("translations").innerHTML="";
get_completions();
}
}
function with_completions(s,cont) {
var c=ordlista[s];
if(c && c.a) cont(c);
else {
//if(c) alert("c already has fields"+field_names(c));
ordlista[s]={put: function(c) { ordlista[s]=c; cont(c); }};
var url=Saldo_ws_url+"ff/json+ordlista[\""+s+"\"].put/"+encodeURIComponent(s);
jsonp(url,"");
}
}
function get_completions() {
with_completions(current,show_completions);
}
function word(s) {
//var w=span_class("word",text(s));
//if(s==" ") w.innerHTML="&nbsp;";
//w.setAttribute("onclick",'extend_current("'+s+'")');
//return w;
return button(s,'extend_current("'+s+'")');
}
function extend_current(s) {
current+=s;
element("words").innerHTML="";
element("surface").innerHTML=current;
get_completions();
}
function show_completions(saldo_ff) {
var box=element("words");
box.innerHTML="";
//var c=saldo_ff.c.split("");
var c=filter(allowed,saldo_ff.c);
sort(c);
for(var i=0;i<c.length;i++) {
var s=c[i];
if(s!='-')
box.appendChild(word(s));
}
show_translations(saldo_ff.a);
}
function allowed(c) {
switch(c) {
case 'å':
case 'ä':
case 'ö':
case 'é':
case 'ü':
return true;
default:
return 'a'<=c && c<='z';
}
}
// ordklasser: mxc sxc (förekommer bara som prefix),
// *h (förekommer bara som suffix)
function ignore(msd) {
switch(msd) {
case "c":
case "ci":
case "cm":
case "seg":
case "sms":
return true;
default:
return false;
}
}
function count_wordforms(a) {
var cnt=0;
for(var i=0;i<a.length;i++)
if(!ignore(a[i].msd)) cnt++;
return cnt;
}
function pad(s) {
return s.length>0 ? " "+s : "";
}
function show_translations(a) {
var tr=element("translations");
tr.innerHTML="";
//if(!a) alert("a undefined in show_translations");
if(count_wordforms(a)<1) {
tr.appendChild(p(text(a.length<1 ? "Detta är inte en giltig ordform"
: "Denna form förekommer bara i sammansättningar")));
element("surface").setAttribute("class","invalid");
}
else {
element("surface").setAttribute("class","valid");
for(var i=0;i<a.length;i++)
if(!ignore(a[i].msd))
tr.appendChild(p(text(a[i].gf+" ("+a[i].pos+pad(a[i].is)+", "+a[i].msd+")")));
}
}
function show_ordlista() {
var trans=element("translations");
trans.innerHTML="Följande ord har slagits upp: ";
var apnd=function(el) { trans.appendChild(el) };
for(var i in ordlista) {
apnd(empty("br"));
apnd(span_class(ordlista[i].a.length<1 ? "invalid" : "valid",text(" "+i)));
apnd(text(": "+(ordlista[i].ok!=null ? ordlista[i].ok.length : "?")
+"/"+(ordlista[i].allowed!=null ? ordlista[i].allowed.length : "?")));
}
}
function extend_ordlista(s,cs,cont) {
if(cs.length<1) cont();
else {
var c=cs[0];
var cs2=cs.substring(1);
with_completions(s+c,function(o){extend_ordlista(s,cs2,cont)});
}
}
function known_possible_moves(s,cont) {
var c=implode(sort(filter(allowed,ordlista[s].c)));
ordlista[s].allowed=c;
extend_ordlista(s,c,function() {
var ok="";
for(var i=0;i<c.length;i++) {
var next=s+c[i];
var ff=ordlista[next];
//if(!ff.a) alert(show_props(ff,"ff"));
if(next.length<2 || count_wordforms(ff.a)<1) ok+=c[i];
}
ordlista[s].ok=ok;
cont(ok);
}
);
}
function unknown_possible_moves(s,cont) {
with_completions(s,function(c){known_possible_moves(s,cont);});
}
function currently_possible_moves(cont) {
known_possible_moves(current,cont);
}
function show_moves() {
var trans=element("translations");
trans.innerHTML="Letar efter möjliga drag";
currently_possible_moves(function(ok) {
trans.innerHTML="Tänkbara drag: "+ok;
winning_moves(trans,ok);
});
}
function winning_moves(trans,ok) {
var ws=map(function(c){return current+c;},ok);
mapc(unknown_possible_moves,ws,function(oks){
var winning="";
for(i=0;i<oks.length;i++)
if(oks[i].length<1) winning+=ok[i];
trans.innerHTML+="<br>Vinnande drag: "+winning;
});
}
function make_a_move() {
currently_possible_moves(function(ok) {
if(ok.length<1) element("translations").innerHTML="Hittade inga möjliga drag!";
else {
var i=Math.floor(Math.random()*ok.length);
extend_current(ok[i]);
}
}
);
}
function show_prefixes_of(trans,s) {
if(s.length>0) {
var p=s.substr(0,s.length-1);
with_completions(p,function(c) {
if(count_wordforms(c.a)>0) trans.innerHTML+="<br>"+p;
show_prefixes_of(trans,p);
});
}
}
function show_prefixes() {
var trans=element("translations");
trans.innerHTML="Prefix av "+current+":";
show_prefixes_of(trans,current);
}
/* -------------------------------------------------------------------------- */
var spel={ antal_ord: 4, // antal närbesläktade ord att visa
antal_korrekta_svar: 0,
antal_felaktiga_svar: 0
};
function start_saldospel() {
spel.hylla=div_id("hylla");
spel.status=div_id("status");
//element("saldospel").innerHTML="<span id=score></span>";
appendChildren(element("saldospel"),
[spel.hylla,spel.status,
p(text("")),
button("Nya ord","spel0()"),
text(" "),
wrap("b",span_id("score"))]);
spel.score=element("score");
show_score();
spel0();
}
function spel0() { // Välj ord 1
saldo_lid_rnd("spel1");
}
function spel1(lid) { // Slå upp md1 för ord 1
spel.lid=lid;
saldo_json("md1",lid.lex,"spel2");
}
function spel2(md1) { // Kontrollera att det finns minst 4 ord i md1 för ord1
if(md1.length<spel.antal_ord) spel0();
else {
spel.md1=md1;
spel3();
}
}
function spel3() { // Välj ord 2
saldo_lid_rnd("spel4");
}
function spel4(lid) { // Slå upp md1 för ord 2
spel.lid2=lid;
saldo_json("md1",lid.lex,"spel5");
}
function spel5(md1) { // Kontrollera att ord 1 och ord 2 inte har något gemensamt
var ordlista1=map(wf,spel.md1);
var ord2=wf(spel.lid2.lex);
var ordlista2=map(wf,md1).concat(ord2);
if(overlaps(ordlista1,ordlista2)) spel3();
else spel6(ordlista1,ord2);
}
function spel6(ordlista1,ord2) {
spel.ord2=ord2;
var pos=Math.floor(Math.random()*spel.antal_ord);
var ordlista=shuffle(shuffle(ordlista1).slice(0,spel.antal_ord).concat(ord2));
spel.hylla.innerHTML="";
var lista=empty_class("div","space");
for(var i=0;i<ordlista.length;i++)
lista.appendChild((button(ordlista[i],"spel7(this)")));
spel.hylla.appendChild(lista);
}
function spel7(btn) {
btn.disabled=true;
var ok=btn.value==spel.ord2;
//btn.setAttribute("class",ok ? "correct" : "incorrect");
btn.setAttribute("style",ok ? "color: green" : "color: red");
if(ok) spel.antal_korrekta_svar++; else spel.antal_felaktiga_svar++;
show_score();
if(ok) spel0();
}
function show_score() {
spel.score.innerHTML=""+spel.antal_korrekta_svar+" rätt, "
+spel.antal_felaktiga_svar+" fel";
}
function wf(ord) { // word form, wf("band..1") == "band"
return ord.split(".",1)[0].split("_").join(" ");
}

300
src/www/minibar/support.js Normal file
View File

@@ -0,0 +1,300 @@
/* --- Accessing document elements ------------------------------------------ */
function element(id) {
return document.getElementById(id);
}
/* --- JavaScript tricks ---------------------------------------------------- */
// To be able to use object methods that refer to "this" as callbacks
// See section 3.3 of https://github.com/spencertipping/js-in-ten-minutes/raw/master/js-in-ten-minutes.pdf
function bind(f, this_value) {
return function () {return f.apply (this_value, arguments)};
};
/* --- JSONP ---------------------------------------------------------------- */
// Inspired by the function jsonp from
// http://www.west-wind.com/Weblog/posts/107136.aspx
// See also http://niryariv.wordpress.com/2009/05/05/jsonp-quickly/
// http://en.wikipedia.org/wiki/JSON#JSONP
function jsonp(url,callback)
{
if (url.indexOf("?") > -1)
url += "&jsonp="
else
url += "?jsonp="
url += callback;
//url += "&" + new Date().getTime().toString(); // prevent caching
var script = empty("script");
script.setAttribute("src",url);
script.setAttribute("type","text/javascript");
document.body.appendChild(script);
}
var json = {next:0};
// Like jsonp, but instead of passing the name of the callback function, you
// pass the callback function directly, making it possible to use anonymous
// functions.
function jsonpf(url,callback)
{
var name="callback"+(json.next++);
json[name]=function(x) { delete json[name]; callback(x); }
jsonp(url,"json."+name);
}
/* --- AJAX ----------------------------------------------------------------- */
function GetXmlHttpObject(handler)
{
var objXMLHttp=null
if (window.XMLHttpRequest)
{
// See http://www.w3.org/TR/XMLHttpRequest/
// https://developer.mozilla.org/en/xmlhttprequest
objXMLHttp=new XMLHttpRequest()
}
else if (window.ActiveXObject)
{
objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP")
}
return objXMLHttp
}
function ajax_http(method,url,body,callback,errorcallback) {
var http=GetXmlHttpObject()
if (!http) {
var errortext="Browser does not support HTTP Request";
if(errorcallback) errorcallback(errortext,500)
else alert(errortext)
}
else {
var statechange=function() {
if (http.readyState==4 || http.readyState=="complete") {
if(http.status<300) callback(http.responseText,http.status);
else if(errorcallback) errorcallback(http.responseText,http.status);
else alert("Request for "+url+" failed: "
+http.status+" "+http.statusText);
}
}
http.onreadystatechange=statechange;
http.open(method,url,true)
http.send(body)
}
return http
}
function ajax_http_get(url,callback,errorcallback) {
ajax_http("GET",url,null,callback,errorcallback)
}
function ajax_http_post(url,formdata,callback,errorcallback) {
ajax_http("POST",url,formdata,callback,errorcallback)
// See https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest#Using_FormData_objects
}
// JSON via AJAX
function ajax_http_get_json(url,cont) {
ajax_http_get(url,function(txt) { cont(eval("("+txt+")")); });
}
function sameOrigin(url) {
var a=empty("a");
a.href=url; // converts to an absolute URL
return hasPrefix(a.href,location.protocol+"//"+location.host+"/");
}
// Use AJAX when possible, fallback to JSONP
function http_get_json(url,cont) {
if(sameOrigin(url)) ajax_http_get_json(url,cont);
else jsonpf(url,cont);
}
/* --- URL construction ----------------------------------------------------- */
function encodeArgs(args) {
var q=""
for(var arg in args)
if(args[arg]!=undefined)
q+="&"+arg+"="+encodeURIComponent(args[arg]);
return q;
}
/* --- HTML construction ---------------------------------------------------- */
function text(s) { return document.createTextNode(s); }
function node(tag,as,ds) {
var n=document.createElement(tag);
for(var a in as) n.setAttribute(a,as[a]);
for(var i in ds) n.appendChild(ds[i]);
return n;
}
function empty(tag,name,value) {
var el=node(tag,{},[])
if(name && value) el.setAttribute(name,value);
return el;
}
function empty_id(tag,id) { return empty(tag,"id",id); }
function empty_class(tag,cls) { return empty(tag,"class",cls); }
function div_id(id) { return empty_id("div",id); }
function span_id(id) { return empty_id("span",id); }
function wrap(tag,contents) { return node(tag,{},[contents]); }
function wrap_class(tag,cls,contents) {
var el=empty_class(tag,cls);
if(contents) el.appendChild(contents);
return el;
}
function span_class(cls,contents) { return wrap_class("span",cls,contents); }
function div_class(cls,contents) { return wrap_class("div",cls,contents); }
function p(contents) { return wrap("p",contents); }
function dt(contents) { return wrap("dt",contents); }
function li(contents) { return wrap("li",contents); }
function th(contents) { return wrap("th",contents); }
function td(contents) { return wrap("td",contents); }
function tr(cells) { return node("tr",{},cells); }
function button(label,action,key) {
var el=node("input",{"type":"button","value":label},[]);
if(typeof action=="string") el.setAttribute("onclick",action);
else el.onclick=action;
if(key) el.setAttribute("accesskey",key);
return el;
}
function option(label,value) {
return node("option",{"value":value},[text(label)]);
}
function appendChildren(el,ds) {
for(var i in ds) el.appendChild(ds[i]);
return el;
}
function insertFirst(parent,child) {
parent.insertBefore(child,parent.firstChild);
}
function tda(cs) { return node("td",{},cs); }
function img(src) { return empty("img","src",src); }
/* --- Debug ---------------------------------------------------------------- */
function debug(s) {
var d=element("debug");
if(d) d.appendChild(text(s+"\n"))
}
function show_props(obj, objName) {
var result = "";
for (var i in obj) {
result += objName + "." + i + " = " + obj[i] + "<br>";
}
return result;
}
function field_names(obj) {
var result = "";
for (var i in obj) {
result += " " + i;
}
return result;
}
/* --- Data manipulation ---------------------------------------------------- */
function swap(a,i,j) { // Note: this doesn't work on strings.
var tmp=a[i];
a[i]=a[j];
a[j]=tmp;
return a;
}
function sort(a) {
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/sort
return a.sort();
/* // Note: this doesn't work on strings.
for(var i=0;i<a.length-1;i++) {
var min=i;
for(var j=i+1;j<a.length;j++)
if(a[j]<a[min]) min=j;
if(min!=i) swap(a,i,min);
}
return a;
*/
}
function filter(p,xs) {
var ys=[];
for(var i=0;i<xs.length;i++)
if(p(xs[i])) ys[ys.length]=xs[i];
return ys;
}
function implode(cs) { // array of strings to string
/*
var s="";
for(var i=0;i<cs.length;i++)
s+=cs[i];
return s;
*/
return cs.join("");
}
function hasPrefix(s,pre) { return s.substr(0,pre.length)==pre; }
function commonPrefix(s1,s2) {
for(var i=0;i<s1.length && i<s2.length && s1[i]==s2[i];i++);
return s1.substr(0,i);
}
/*
function all(p,xs) {
for(var i=0;i<xs.length;i++)
if(!p(xs[i])) return false;
return true;
}
*/
function map(f,xs) {
var ys=[];
for(var i=0;i<xs.length;i++) ys[i]=f(xs[i]);
return ys;
}
// map in continuation passing style
function mapc(f,xs,cont) { mapc_from(f,xs,0,[],cont); }
function mapc_from(f,xs,i,ys,cont) {
if(i<xs.length)
f(xs[i],function(y){ys[i]=y;mapc_from(f,xs,i+1,ys,cont)});
else
cont(ys);
}
function overlaps(as,bs) {
for(var i=0;i<as.length;i++)
if(elem(as[i],bs)) return true;
return false;
}
function elem(a,as) {
for(var i=0;i<as.length;i++)
if(a==as[i]) return true;
return false;
}
function shuffle(a) {
for(i=0;i<a.length;i++) swap(a,i,Math.floor(Math.random()*a.length))
return a;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B