More progress on all entites in MW data load

This commit is contained in:
Christopher T. Johnson
2024-05-14 11:08:02 -04:00
parent 7c65b466f1
commit 0acba3ed9b
4 changed files with 96 additions and 30 deletions

View File

@@ -521,8 +521,10 @@ class Definition(QWidget):
self.pronounce.emit(url) self.pronounce.emit(url)
elif url.scheme() == 'word': elif url.scheme() == 'word':
self.newWord.emit(url.path()) self.newWord.emit(url.path())
elif url.scheme() == 'sense':
self.newWord.emit(url.path())
else: else:
print(f"{clk['fmt'].anchorHref()}: {url.scheme()}") print(f"{clk['fmt'].anchorHref()}")
self.alert.emit() self.alert.emit()
self._downClickable = None self._downClickable = None
return return

View File

@@ -1,12 +1,9 @@
import json from typing import Dict, List, Optional, cast
from typing import Any, Dict, List, Optional, cast
import requests
from PyQt6.QtCore import QPoint, QResource, Qt, QTimer, pyqtSignal, pyqtSlot from PyQt6.QtCore import QPoint, QResource, Qt, QTimer, pyqtSignal, pyqtSlot
from PyQt6.QtGui import ( from PyQt6.QtGui import (
QBrush, QBrush,
QColor, QColor,
QCursor,
QKeyEvent, QKeyEvent,
QPainter, QPainter,
QPainterPath, QPainterPath,
@@ -15,7 +12,7 @@ from PyQt6.QtGui import (
QTextCursor, QTextCursor,
) )
from PyQt6.QtSql import QSqlQuery from PyQt6.QtSql import QSqlQuery
from PyQt6.QtWidgets import QDialog, QTextEdit, QWidget from PyQt6.QtWidgets import QDialog, QWidget
from lib import query_error from lib import query_error
from lib.preferences import Preferences from lib.preferences import Preferences
@@ -89,6 +86,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
self.playSound.connect(self.sound.playSound) self.playSound.connect(self.sound.playSound)
self.playAlert.connect(self.sound.alert) self.playAlert.connect(self.sound.alert)
self.definition.pronounce.connect(self.sound.playSound) self.definition.pronounce.connect(self.sound.playSound)
self.definition.newWord.connect(self.newWord)
return return
# #
@@ -98,6 +96,15 @@ class ReadDialog(QDialog, Ui_ReadDialog):
# #
# slots # slots
# #
@pyqtSlot(str)
def newWord(self, word: str) -> None:
w = Word(word)
if not w.isValid():
self.playAlert.emit()
return
self.definition.setWord(w)
return
@pyqtSlot() @pyqtSlot()
def timerAction(self) -> None: def timerAction(self) -> None:
if self.session.isActive(): # We are stopping if self.session.isActive(): # We are stopping
@@ -127,6 +134,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
cursor.select(QTextCursor.SelectionType.WordUnderCursor) cursor.select(QTextCursor.SelectionType.WordUnderCursor)
text = cursor.selectedText().strip() text = cursor.selectedText().strip()
word = Word(text) word = Word(text)
if not word.isValid():
self.playAlert.emit()
return
word.playPRS() word.playPRS()
return return
@@ -221,6 +231,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
cursor.select(cursor.SelectionType.WordUnderCursor) cursor.select(cursor.SelectionType.WordUnderCursor)
text = cursor.selectedText().strip() text = cursor.selectedText().strip()
word = Word(text) word = Word(text)
if not word.isValid():
self.playAlert.emit()
return
self.definition.setWord(word) self.definition.setWord(word)
self.showDefinition() self.showDefinition()
return return

View File

@@ -40,6 +40,7 @@ class Word:
"""All processing of a dictionary word.""" """All processing of a dictionary word."""
_words: dict[str, WordType] = {} _words: dict[str, WordType] = {}
_valid = False
def __init__(self, word: str) -> None: def __init__(self, word: str) -> None:
# #
@@ -62,6 +63,7 @@ class Word:
"definition": json.loads(query.value("definition")), "definition": json.loads(query.value("definition")),
} }
self.current = Word._words[word] self.current = Word._words[word]
self._valid = True
return return
# #
# The code should look at our settings to see if we have an API # The code should look at our settings to see if we have an API
@@ -70,6 +72,9 @@ class Word:
source = "mw" source = "mw"
self._words[word] = discovered_plugins[source].fetch(word) self._words[word] = discovered_plugins[source].fetch(word)
if self._words[word] is None:
self._valid = False
return
self.current = Word._words[word] self.current = Word._words[word]
query.prepare( query.prepare(
"INSERT INTO words " "INSERT INTO words "
@@ -81,8 +86,12 @@ class Word:
query.bindValue(":definition", json.dumps(self.current["definition"])) query.bindValue(":definition", json.dumps(self.current["definition"]))
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
self._valid = True
return return
def isValid(self) -> bool:
return self._valid
@pyqtSlot() @pyqtSlot()
def playSound(self) -> None: def playSound(self) -> None:
url = discovered_plugins[self.current["source"]].getFirstSound( url = discovered_plugins[self.current["source"]].getFirstSound(

View File

@@ -4,7 +4,7 @@ from typing import Any, Literal, NotRequired, TypedDict, cast
from PyQt6.QtCore import QEventLoop, QUrl from PyQt6.QtCore import QEventLoop, QUrl
from PyQt6.QtGui import QFont, QFontDatabase, QTextCharFormat, QTextLayout from PyQt6.QtGui import QFont, QFontDatabase, QTextCharFormat, QTextLayout
from PyQt6.QtNetwork import QNetworkRequest from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest
from trycast import trycast from trycast import trycast
from lib.definition import Fragment, Line from lib.definition import Fragment, Line
@@ -91,7 +91,7 @@ Inflection = TypedDict(
class CrossReferenceTarget(TypedDict): class CrossReferenceTarget(TypedDict):
cxl: str cxl: NotRequired[str]
cxr: NotRequired[str] cxr: NotRequired[str]
cxt: str cxt: str
cxn: NotRequired[str] cxn: NotRequired[str]
@@ -277,7 +277,7 @@ class WordType(TypedDict):
definition: Any definition: Any
def fetch(word: str) -> WordType: def fetch(word: str) -> WordType|None:
request = QNetworkRequest() request = QNetworkRequest()
url = QUrl(API.format(word=word, key=key)) url = QUrl(API.format(word=word, key=key))
request.setUrl(url) request.setUrl(url)
@@ -287,6 +287,9 @@ def fetch(word: str) -> WordType:
loop = QEventLoop() loop = QEventLoop()
reply.finished.connect(loop.quit) reply.finished.connect(loop.quit)
loop.exec() loop.exec()
if reply.error() != QNetworkReply.NetworkError.NoError:
print(f"Error fetching {word}: {reply.errorString()}")
return None
content = reply.readAll() content = reply.readAll()
data = json.loads(content.data().decode("utf-8")) data = json.loads(content.data().decode("utf-8"))
return { return {
@@ -295,7 +298,6 @@ def fetch(word: str) -> WordType:
"definition": data, "definition": data,
} }
def soundUrl(sound: Sound, fmt="ogg") -> QUrl: def soundUrl(sound: Sound, fmt="ogg") -> QUrl:
"""Create a URL from a PRS structure.""" """Create a URL from a PRS structure."""
base = f"audio://media.merriam-webster.com/audio/prons/en/us/{fmt}" base = f"audio://media.merriam-webster.com/audio/prons/en/us/{fmt}"
@@ -343,16 +345,12 @@ def do_prs(frag: Fragment, prs: list[Pronunciation] | None) -> None:
fmt.setAnchorHref(soundUrl(pr["sound"]).toString()) fmt.setAnchorHref(soundUrl(pr["sound"]).toString())
fmt.setForeground(r.linkColor) fmt.setForeground(r.linkColor)
#text = pr["mw"] +' \N{SPEAKER} ' #text = pr["mw"] +' \N{SPEAKER} '
text = pr["mw"] +' ' text = ' '+pr["mw"] +' '
else: else:
text = pr['mw'] + ' ' text = pr['mw'] + ' '
print(f"text: {text}, length: {len(text)}")
frag.addText(text, fmt) frag.addText(text, fmt)
if "l2" in pr: if "l2" in pr:
frag.addText(pun + pr["l2"], r.subduedLabelFormat) frag.addText(pun + pr["l2"], r.subduedLabelFormat)
text = frag.layout().text()
for fmt in frag.layout().formats():
print(f"start: {fmt.start}, length: {fmt.length}, text: \"{text[fmt.start:fmt.start+fmt.length]}\"")
return return
@@ -741,6 +739,42 @@ def do_uros(uros: list[UndefinedRunOn]|None) -> list[Line]:
lines.append(line) lines.append(line)
lines += newLines lines += newLines
return lines return lines
def do_cxs(cxs: list[CognateCrossRef]|None) -> list[Line]:
assert cxs is not None
r = Resources()
lines: list[Line] = []
for cx in cxs:
frag = Fragment()
frag.addText(cx['cxl']+' ', r.italicFormat)
for cxt in cx['cxtis']:
if 'cxl' in cxt:
frag.addText(cxt['cxl'], r.italicFormat)
text = cxt['cxt']
anchor = text
if 'cxr' in cxt:
anchor = cxt['cxr']
if 'cxn' in cxt:
anchor += f"/{cxt['cxn']}"
fmt = QTextCharFormat(r.smallCapsFormat)
fmt.setAnchor(True)
fmt.setForeground(r.linkColor)
fmt.setFontUnderline(True)
fmt.setUnderlineColor(r.linkColor)
fmt.setFontUnderline(True)
fmt.setAnchorHref('sense:///'+anchor)
#
# XXX - Capitalization does not work
#
text = text.upper()
fmt.setFontPointSize(fmt.fontPointSize() * 0.90)
frag.addText(text, fmt)
line = Line()
line.addFragment(frag)
lines.append(line)
return lines
def getDef(defines: Any) -> list[Line]: def getDef(defines: Any) -> list[Line]:
Line.setParseText(parseText) Line.setParseText(parseText)
workList = restructure(defines) workList = restructure(defines)
@@ -831,15 +865,17 @@ def getDef(defines: Any) -> list[Line]:
lines.append(line) lines.append(line)
line = Line() line = Line()
frag = Fragment() frag = Fragment()
if 'def' in work:
defines = trycast(list[DefinitionSection], work["def"]) defines = trycast(list[DefinitionSection], work["def"])
assert defines is not None assert defines is not None
for define in defines: for define in defines:
try: try:
lines += do_def(define) lines += do_def(define)
except NotImplementedError: except NotImplementedError:
raise pass
if 'cxs' in work:
lines += do_cxs(trycast(list[CognateCrossRef], work['cxs']))
if "uros" in work: if "uros" in work:
print(json.dumps(work['uros'],indent=2))
uros = trycast(list[UndefinedRunOn], work['uros']) uros = trycast(list[UndefinedRunOn], work['uros'])
lines += do_uros(uros) lines += do_uros(uros)
if "dros" in work: if "dros" in work:
@@ -852,10 +888,6 @@ def getDef(defines: Any) -> list[Line]:
phrases.append(line) phrases.append(line)
phrases += do_dros(dros) phrases += do_dros(dros)
if "et" in work: if "et" in work:
line = Line()
frag = Fragment('', r.textFont)
frag.addText(f"{work['fl']} ({used[work['fl']]})",r.labelFormat)
line.addFragment(frag)
ets += do_ets(trycast(list[list[Pair]], work["et"])) ets += do_ets(trycast(list[list[Pair]], work["et"]))
for k in work.keys(): for k in work.keys():
if k not in [ if k not in [
@@ -872,8 +904,9 @@ def getDef(defines: Any) -> list[Line]:
"vrs", "vrs",
"dros", "dros",
'uros', 'uros',
'cxs',
]: ]:
raise NotImplementedError(f"Unknown key {k} in work") print( NotImplementedError(f"Unknown key {k} in work"))
if len(phrases) > 0: if len(phrases) > 0:
lines += phrases lines += phrases
if len(ets) > 0: if len(ets) > 0:
@@ -914,6 +947,11 @@ def replaceCode(code:str) -> tuple[str, QTextCharFormat]:
fmt.setFontItalic(True) fmt.setFontItalic(True)
elif token == 'sx': elif token == 'sx':
fmt.setFontCapitalization(QFont.Capitalization.SmallCaps) fmt.setFontCapitalization(QFont.Capitalization.SmallCaps)
#
# XXX - Capitalization does not work
#
text = text.upper()
fmt.setFontPointSize(fmt.fontPointSize() * 0.90)
elif token == 'dxt': elif token == 'dxt':
if fields[3] == 'illustration': if fields[3] == 'illustration':
fmt.setAnchorHref('article:///'+fields[2]) fmt.setAnchorHref('article:///'+fields[2])
@@ -928,10 +966,14 @@ def replaceCode(code:str) -> tuple[str, QTextCharFormat]:
fmt.setAnchorHref('etymology:///'+fields[2]) fmt.setAnchorHref('etymology:///'+fields[2])
else: else:
fmt.setAnchorHref('etymology:///' + fields[1]) fmt.setAnchorHref('etymology:///' + fields[1])
elif token == 'd_link':
if fields[2] != '':
fmt.setAnchorHref('direct:///' + fields[2])
else:
fmt.setAnchorHref('direct:///' + fields[1])
else: else:
raise NotImplementedError(f"Token {code} not implimented") raise NotImplementedError(f"Token {code} not implimented")
fmt.setForeground(r.linkColor) fmt.setForeground(r.linkColor)
print(f"Format.capitalization(): {fmt.fontCapitalization()}")
return (text,fmt) return (text,fmt)
def markup(offset: int, text:str) -> tuple[str, list[QTextLayout.FormatRange]]: def markup(offset: int, text:str) -> tuple[str, list[QTextLayout.FormatRange]]: