From 8fbeeeeb115a799a91742acfad4130e6cad48ca5 Mon Sep 17 00:00:00 2001
From: Victor Bulatov <bt.uytya@gmail.com>
Date: Tue, 15 Jun 2021 05:10:47 +0300
Subject: [PATCH] rest api?

---
 constants.py        |   4 +
 main.html           | 147 ---------------------------------
 server.py           |  55 +++++++++++++
 templates/main.html | 193 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 252 insertions(+), 147 deletions(-)
 delete mode 100644 main.html
 create mode 100644 server.py
 create mode 100644 templates/main.html

diff --git a/constants.py b/constants.py
index 9535eec..4fd9e36 100644
--- a/constants.py
+++ b/constants.py
@@ -7,6 +7,10 @@
     'sȯ', 's', 'u', 'vȯ', 'vo', 'v', 'vȯz', 'voz', 'vy', 'za',
 ]
 
+CYR_LETTER_SUBS = {
+    "н": "њ", "л": "љ", "е": "є", "и": "ы"
+}
+
 SIMPLE_DIACR_SUBS = {
     'e': 'ě', 'c': 'č', 'z': 'ž', 's': 'š',
 }
diff --git a/main.html b/main.html
deleted file mode 100644
index bfba5ad..0000000
--- a/main.html
+++ /dev/null
@@ -1,147 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-
-<meta charset="utf-8" />
-<style>
-body {
-  margin: 15px;
-}
-
-div.search span,
-div.search input[name="keyword"] {
-  display: block;
-}
-
-div.search input[name="keyword"] {
-  margin-top: 4px;
-}
-
-div.panel {
-  margin-bottom: 15px;
-}
-
-div.panel .panel-body p:last-child {
-  margin-bottom: 0;
-}
-
-mark {
-  padding: 0;
-}
-
-mark.unknown {
-  background: #ffcccb;
-}
-mark.one_option {
-  background: #bca9d0;
-}
-mark.fixed {
-  background: #e6e6fa;
-}
-mark.many_options {
-  background: #ffff66;
-}
-
-[data-tooltip]:before {            
-    position : absolute;
-    content : attr(data-tooltip);
-    opacity : 0;
-    background: #9678b6;
-	border: 2px solid #333;
-    padding: 10px;
-    border-radius: 10px;
-    box-shadow: 2px 2px 1px silver;    
-	
-}
-[data-tooltip]:hover:before {        
-    opacity : 1;
-	margin-top: -50px;
-    margin-left: 20px;    
-}
-[data-tooltip]:not([data-tooltip-persistent]):before {
-    pointer-events: none;
-}
-
-</style>
-
-<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.es6.min.js"></script>
-</head>
-
-
-<h2><a href="https://markjs.io/" target="_blank">mark.js</a> example with pure JavaScript</h2>
-
-
-
-<div class="panel panel-default">
-  <div class="panel-body context">
-    <p>
-      
-    </p>
-  </div>
-</div>
-
-<div class="panel panel-default">
-  <div class="panel-body">
-    <p>
-		<small>The data is obtained from pymorphy2 and hardcoded in HTML. Some of elements are clickable</small>
-    </p>
-  </div>
-</div>
-
-
-<script>
-// Create an instance of mark.js and pass an argument containing
-// the DOM object of the context (where to search for matches)
-var markInstance = new Mark(document.querySelector(".context"));
-
-
-
-let text = "Biblioteka pymorphy2 jest napisana za jezyk Python v 2012 letu. Ona imaje nekoliko osoblivostej, ktore delajut jej ukoristanje za MS mnogo uměstnym."
-// let spans = [(11, 20, '^^^^^^^^^'), (44, 50, '^^^^^^'), (58, 62, '1'), (74, 82, '2'), (83, 95, '3'), (103, 110, '4'), (115, 126, '^^^^^^^^^^^'), (130, 132, '^^')]
-let spans = [[11, 20, '^^^^^^^^^'], [44, 50, '^^^^^^'], [58, 62, '1'], [74, 82, '2'], [83, 95, '3'], [103, 110, '4'], [115, 126, '^^^^^^^^^^^'], [130, 132, '^^']]
-let corrections = ['letu/lětu', 'několiko', 'osoblivostěj', 'dělajut']
-
-
-
-document.querySelector(".context").textContent = text
-
-// mark all unknown words
-let unk_ranges = spans.filter(entry => entry[2].includes("^")).map(entry => {
-	return {"start": entry[0], "length": entry[1]-entry[0]}
-})
-markInstance.markRanges(unk_ranges, {"className": "unknown"})
-
-// mark all words that could be changed easily
-// let fixable_ranges = spans.filter(entry => !entry[2].includes("^")).map(entry => {
-// return {"start": entry[0], "length": entry[1]-entry[0]}
-// })
-let fixable_ranges = spans.filter(entry => !entry[2].includes("^"))
-fixable_ranges.forEach(entry => {
-    console.log(entry)
-	// abusing js type coercion 'coz this is stupid prototype
-	let single_range = [{"start": entry[0], "length": entry[1]-entry[0]}]
-    let correction = corrections[entry[2] - 1]
-    console.log(correction)
-	if (correction.includes("/")) {
-		markInstance.markRanges(single_range, {
-			className: "many_options", 
-			each: el => {el.setAttribute('data-tooltip', correction)}
-			// each: el => {el.setAttribute('title', correction)}
-		})
-	} else {
-		markInstance.markRanges(single_range, {
-			className: "one_option",
-			each: el => {
-				el.setAttribute('data-tooltip', correction);
-				el.setAttribute('onclick', 
-					"this.innerHTML = '" + correction + "'; this.removeAttribute('data-tooltip'); this.removeAttribute('onclick'); this.classList.remove('one_option'); this.classList.add('fixed');"
-				);
-			}
-		})
-	}
-})
-
-</script>
-
-
-
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..ee17c85
--- /dev/null
+++ b/server.py
@@ -0,0 +1,55 @@
+import argparse
+from flask import Flask, render_template, request, jsonify
+from example2 import perform_spellcheck, pymorphy2
+from constants import DEFAULT_UNITS, SIMPLE_DIACR_SUBS, ETM_DIACR_SUBS, CYR_LETTER_SUBS
+
+app = Flask(__name__)
+app.config["JSON_AS_ASCII"] = False
+
+path = "C:\\dev\\pymorphy2-dicts\\"
+
+std_morph = pymorphy2.MorphAnalyzer(
+    path+"out_isv_lat",
+    units=DEFAULT_UNITS,
+    char_substitutes=SIMPLE_DIACR_SUBS
+)
+
+etm_morph = pymorphy2.MorphAnalyzer(
+    path+"out_isv_etm",
+    units=DEFAULT_UNITS,
+    char_substitutes=ETM_DIACR_SUBS
+)
+
+cyr_morph = pymorphy2.MorphAnalyzer(
+    path+"out_isv_cyr",
+    units=DEFAULT_UNITS,
+    char_substitutes=CYR_LETTER_SUBS
+)
+
+abecedas = {"lat": std_morph, "etm": etm_morph, "cyr": cyr_morph}
+    
+@app.route('/')
+def index():
+    return render_template('main.html')
+
+@app.route('/koriguj', methods=['POST'])
+def korigovanje():
+    text = request.json['text'];
+    selected_morph = abecedas[request.json["abeceda"]]
+    text, spans, proposed_corrections = perform_spellcheck(text, selected_morph)
+
+    resp = {
+        'text': text,
+        'spans': spans,
+        'corrections': proposed_corrections
+    }
+    return jsonify(resp)
+ 
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+           '--port', type=int, default=666,
+           help='The port to listen on (defaults to 666).')
+    args = parser.parse_args()
+    app.run(host='localhost', port=args.port, debug=True)
+
diff --git a/templates/main.html b/templates/main.html
new file mode 100644
index 0000000..9bb6206
--- /dev/null
+++ b/templates/main.html
@@ -0,0 +1,193 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+
+<meta charset="utf-8" />
+<style>
+body {
+  margin: 15px;
+}
+
+div.search span,
+div.search input[name="keyword"] {
+  display: block;
+}
+
+div.search input[name="keyword"] {
+  margin-top: 4px;
+}
+
+div.panel {
+  margin-bottom: 15px;
+}
+
+div.panel .panel-body p:last-child {
+  margin-bottom: 0;
+}
+
+mark {
+  padding: 0;
+}
+
+mark.unknown {
+  background: #ffcccb;
+}
+mark.one_option {
+  background: #bca9d0;
+}
+mark.fixed {
+  background: #e6e6fa;
+}
+mark.many_options {
+  background: #ffff66;
+}
+
+[data-tooltip]:before {            
+    position : absolute;
+    content : attr(data-tooltip);
+    opacity : 0;
+    background: #9678b6;
+	border: 2px solid #333;
+    padding: 10px;
+    border-radius: 10px;
+    box-shadow: 2px 2px 1px silver;    
+	
+}
+[data-tooltip]:hover:before {        
+    opacity : 1;
+	margin-top: -50px;
+    margin-left: 20px;    
+}
+[data-tooltip]:not([data-tooltip-persistent]):before {
+    pointer-events: none;
+}
+
+</style>
+
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.es6.min.js"></script>
+</head>
+
+
+<h2>An example with <a href="https://markjs.io/" target="_blank">mark.js</a>, pymorphy2 and Flask</h2>
+
+
+
+
+
+<textarea name="text" rows="6" cols="80" id="text" class="v" spellcheck="false">
+Biblioteka pymorphy2 jest napisana za jezyk Python v 2012 letu. Ona imaje nekoliko osoblivostej, ktore delajut jej ukoristanje za MS mnogo uměstnym.
+</textarea>
+
+<h3>Please select orthography the text should conform to</h3>
+
+<input type="radio" class="abeceda_select" name="abeceda_select" value="cyr" /> Кирилицеју
+<input type="radio" class="abeceda_select" name="abeceda_select" value="lat" checked /> Standardnoju latiniceju
+<input type="radio" class="abeceda_select" name="abeceda_select" value="etm" /> Etimologičnojų abecedojų
+
+<!--
+<fieldset><table id="zs" border="0">
+	<tbody><tr>
+		<td><input id="zs_0" type="radio" name="zs" value="0"><label for="zs_0">Заглавные</label></td>
+	</tr><tr>
+		<td><input id="zs_1" type="radio" name="zs" value="1"><label for="zs_1">Строчные</label></td>
+	</tr><tr>
+		<td><input id="zs_2" type="radio" name="zs" value="2" checked="checked"><label for="zs_2">Как есть</label></td>
+	</tr>
+</tbody></table></fieldset> -->
+
+<button id="calc" onclick="sendData()">Prověriti pravopis</button>
+
+<div class="panel panel-default">
+  <div class="panel-body">
+    <p>
+		<small>The data is obtained from pymorphy2 and rendered in HTML. Some of elements are clickable</small>
+    </p>
+  </div>
+</div>
+
+<div class="panel panel-default">
+  <div class="panel-body context">
+    <p>
+      
+    </p>
+  </div>
+</div>
+
+
+<script>
+// Create an instance of mark.js and pass an argument containing
+// the DOM object of the context (where to search for matches)
+var markInstance = new Mark(document.querySelector(".context"));
+
+
+async function postData() {
+  const data = document.getElementById("text").value
+  const morph = document.querySelector('input[name="abeceda_select"]:checked').value;
+
+  const sendData = JSON.stringify({"text": data, "abeceda": morph});
+
+  const response = await fetch("/koriguj", {
+    method: 'POST',
+    mode: 'cors', // no-cors, *cors, same-origin
+    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
+    credentials: 'same-origin', // include, *same-origin, omit
+    headers: {
+      'Content-Type': 'application/json'
+    },
+    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
+    body: sendData // body data type must match "Content-Type" header
+  });
+  return response.json(); // parses JSON response into native JavaScript objects
+}
+
+function sendData() {
+  postData().then(data => {
+      console.log(data); // JSON data parsed by `data.json()` call
+	  renderData(data['text'], data['spans'], data['corrections'])
+  });
+}
+
+
+function renderData(text, spans, corrections) {
+	document.querySelector(".context").textContent = text
+	// mark all unknown words
+	let unk_ranges = spans.filter(entry => entry[2].includes("^")).map(entry => {
+		return {"start": entry[0], "length": entry[1]-entry[0]}
+	})
+	markInstance.markRanges(unk_ranges, {"className": "unknown"})
+
+	// mark all words that could be changed easily
+	// let fixable_ranges = spans.filter(entry => !entry[2].includes("^")).map(entry => {
+	// return {"start": entry[0], "length": entry[1]-entry[0]}
+	// })
+	let fixable_ranges = spans.filter(entry => !entry[2].includes("^"))
+	fixable_ranges.forEach(entry => {
+		console.log(entry)
+		// abusing js type coercion 'coz this is stupid prototype
+		let single_range = [{"start": entry[0], "length": entry[1]-entry[0]}]
+		let correction = corrections[entry[2] - 1]
+		console.log(correction)
+		if (correction.includes("/")) {
+			markInstance.markRanges(single_range, {
+				className: "many_options", 
+				each: el => {el.setAttribute('data-tooltip', correction)}
+				// each: el => {el.setAttribute('title', correction)}
+			})
+		} else {
+			markInstance.markRanges(single_range, {
+				className: "one_option",
+				each: el => {
+					el.setAttribute('data-tooltip', correction);
+					el.setAttribute('onclick', 
+						"this.innerHTML = '" + correction + "'; this.removeAttribute('data-tooltip'); this.removeAttribute('onclick'); this.classList.remove('one_option'); this.classList.add('fixed');"
+					);
+				}
+			})
+		}
+	})
+}
+
+</script>
+
+
+