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> + + +