Mostly working Reader

This commit is contained in:
Christopher T. Johnson
2023-11-14 10:43:50 -05:00
parent 598201425c
commit 0b02ed2201
5 changed files with 374 additions and 147 deletions

View File

@@ -1,15 +1,49 @@
import json
import os
import xml.dom.minidom
from typing import Dict, List, cast
from PyQt6.QtSql import QSqlQuery
from main import query_error
class Book:
sections = []
metadata = {}
sections: List[str] = []
metadata: Dict[str, str] = {}
words = {}
def __init__(self, src: str) -> None:
super(Book, self).__init__()
self.parse_book(src)
book_id = self.store() # Does nothing if already in database
self.load(book_id)
return
def load(self, book_id: int) -> None:
query = QSqlQuery()
query.prepare("SELECT * FROM books where book_id = :book_id")
query.bindValue(":book_id", book_id)
if not query.exec():
query_error(query)
if not query.next():
raise Exception(f"Missing book? book_id={book_id}")
self.metadata = {
"title": query.value("title"),
"creator": query.value("author"),
"identifier": query.value("uuid"),
"level": query.value("level"),
}
self.sections = []
query.prepare(
"SELECT * FORM sections WHERE book_id = :book_id " "ORDER BY sequence"
)
while query.next():
self.sections.append(query.value("contents"))
#
# Load words!
#
return
def parse_book(self, src: str) -> None:
@@ -53,15 +87,105 @@ class Book:
href = item.getAttribute("href")
print(f"{idref}: {href}")
self.parse_section(src, href)
#
# "sections" is now loaded
#
return
def store(self) -> int:
uuid = self.metadata["identifier"]
query = QSqlQuery()
query.prepare(
"SELECT COUNT(*) AS number, book_id FROM books b " "WHERE b.uuid = :uuid"
)
query.bindValue(":uuid", uuid)
if not query.exec():
query_error(query)
query.next()
if query.value("number") > 0:
book_id: int = query.value("book_id")
return book_id
query.prepare(
"INSERT INTO books (title, author, uuid, level) VALUES ("
":title, :author, :uuid, 0)"
)
query.bindValue(":title", self.metadata["title"])
query.bindValue(":author", self.metadata["creator"])
query.bindValue(":uuid", uuid)
if not query.exec():
query_error(query)
book_id = query.lastInsertId()
query.prepare(
"INSERT INTO sections (sequence, book_id, content) "
"VALUES (:sequence, :book_id, :content)"
)
query.bindValue(":book_id", book_id)
for seq, section in enumerate(self.sections):
query.bindValue(":sequence", seq)
query.bindValue(":content", section)
if not query.exec():
query_error(query)
section_id = query.lastInsertId()
return book_id
def parse_section(self, src: str, href: str) -> None:
newdom = xml.dom.getDOMImplementation().createDocument("", "html", None)
def strip_node(elm: xml.dom.minidom.Element) -> xml.dom.minidom.Node:
if elm.nodeType == xml.dom.Node.TEXT_NODE:
return cast(
xml.dom.minidom.Node,
newdom.createTextNode(cast(xml.dom.minidom.Text, elm).data),
)
newelm = newdom.createElement(elm.localName)
node = elm.firstChild
while node:
if node.nodeType == xml.dom.Node.TEXT_NODE:
text = node.data
if text:
text = text.strip()
if text and len(text) > 0:
newelm.appendChild(newdom.createTextNode(text))
elif node.localName == "img":
pass
elif node.localName == "a":
a_node = node.firstChild
while a_node:
if a_node.nodeType == xml.dom.Node.TEXT_NODE:
newelm.appendChild(newdom.createTextNode(a_node.data))
else:
newelm.appendChild(strip_node(a_node))
a_node = a_node.nextSibling
else:
newelm.appendChild(strip_node(node))
node = node.nextSibling
return newelm
def parse_node(parent: xml.dom.Node, elm: xml.dom.Node) -> None:
if elm.nodeType == xml.dom.Node.ELEMENT_NODE:
if elm.localName.startswith("h"):
clone = strip_node(elm)
parent.appendChild(clone)
elif elm.localName == "p":
clone = strip_node(elm)
clone.normalize()
parent.appendChild(clone)
else:
node = elm.firstChild
while node:
parse_node(parent, node)
node = node.nextSibling
return
with open(f"{src}/{href}") as f:
dom = xml.dom.minidom.parse(f)
title = dom.getElementsByTagName("title")[0].firstChild.data
body = dom.getElementsByTagName("body")[0]
paragraphs = []
for p in body.getElementsByTagName("p"):
paragraphs.append(p.toxml())
self.sections.append({"title": title, "paragraphs": paragraphs})
section = newdom.createElement("body")
node = body.firstChild
while node:
parse_node(section, node)
node = node.nextSibling
self.sections.append(section.toxml())
return

View File

@@ -1,80 +1,133 @@
import json
from PyDictionary import PyDictionary # type: ignore[import-untyped]
from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtCore import QRect, Qt, pyqtSlot
from PyQt6.QtGui import (
QBrush,
QColor,
QFont,
QPainter,
QPainterPath,
QTextCharFormat,
QTextCursor,
QTextDocument,
QTextListFormat,
)
from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
from PyQt6.QtWidgets import QDialog
from PyQt6.QtWidgets import QDialog, QPushButton
from main import query_error
from ui.EditDialog import Ui_Dialog
class EditDialog(QDialog, Ui_Dialog):
def __init__(self, book_id: int, person_id: int) -> None:
def __init__(self, person_id: int) -> None:
super(EditDialog, self).__init__()
self.book_id = book_id
self.person_id = person_id
self.setupUi(self)
self.current_paragraph(self.person_id)
self.load_book(self.person_id)
blockNumber = self.block
self.paraEdit.setReadOnly(True)
self.defEdit.setReadOnly(True)
self.show_section(self.section_id)
self.block = blockNumber
self.savePosition()
self.stackedWidget.setCurrentIndex(0)
self.nextParaBtn = QPushButton(parent=self.widget)
self.nextParaBtn.setObjectName("nextParaBtn")
self.verticalLayout.addWidget(self.nextParaBtn)
self.nextParaBtn.setText("Next Paragraph")
self.defineBtn.clicked.connect(self.defineAction)
self.showBtn.clicked.connect(self.showAction)
self.nextBtn.clicked.connect(self.nextAction)
self.prevBtn.clicked.connect(self.prevAction)
self.nextParaBtn.clicked.connect(self.nextParaAction)
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
return
def current_paragraph(self, person_id: int) -> None:
query = QSqlQuery()
query.prepare("SELECT * FROM people WHERE person_id = :person_id")
query.bindValue(":person_id", person_id)
query.exec()
query.next()
paragraph_id = query.value("paragraph_id")
self.display_paragraph(paragraph_id)
@pyqtSlot(int)
def scrollSlot(self, value):
self.update()
return
def display_paragraph(self, paragraph_id: int) -> None:
self.paragraph_id = paragraph_id
def load_book(self, person_id: int) -> None:
query = QSqlQuery()
query.prepare("SELECT * FROM paragraphs WHERE paragraph_id = :paragraph_id")
query.bindValue(":paragraph_id", paragraph_id)
query.exec()
query.next()
self.section_id = query.value("section_id")
self.paraSequence = query.value("sequence")
cursor = self.paraEdit.textCursor()
cursor.movePosition(QTextCursor.MoveOperation.Start)
cursor.movePosition(
QTextCursor.MoveOperation.End, QTextCursor.MoveMode.KeepAnchor
)
cursor.removeSelectedText()
cursor.insertHtml(query.value("content"))
query.prepare(
"SELECT * FROM word_paragraph " "WHERE paragraph_id = :paragraph_id"
"SELECT pb.* FROM people p "
"LEFT JOIN person_book pb "
"ON (p.book_id = pb.book_id "
"AND p.person_id = pb.person_id) "
"WHERE p.person_id = :person_id"
)
query.bindValue(":paragraph_id", self.paragraph_id)
query.bindValue(":person_id", person_id)
if not query.exec():
query_error(query)
if not query.next():
self.done(0)
self.book_id = query.value("book_id")
self.section_id = query.value("section_id")
self.block = query.value("block")
self.sections = []
self.section_map = {}
self.sequence_map = {}
query.prepare(
"SELECT * FROM sections " "WHERE book_id = :book_id " "ORDER BY sequence"
)
query.bindValue(":book_id", self.book_id)
if not query.exec():
query_error(query)
def_format = QTextCharFormat()
def_format.setFontUnderline(True)
cursor = QTextCursor(self.paraEdit.document())
while query.next():
start = query.value("start")
end = query.value("end")
cursor.setPosition(start, QTextCursor.MoveMode.MoveAnchor)
cursor.setPosition(end, QTextCursor.MoveMode.KeepAnchor)
cursor.setCharFormat(def_format)
cursor.setPosition(0)
self.sections.append(query.value("content"))
self.section_map[query.value("section_id")] = query.value("sequence")
self.sequence_map[query.value("sequence")] = query.value("section_id")
return
def show_section(self, section_id, start=True):
sequence = self.section_map[section_id]
self.setWindowTitle(f"Section {sequence}")
self.paraEdit.clear()
cursor = self.paraEdit.textCursor()
cursor.insertHtml(self.sections[sequence])
if start:
self.block = 0
else:
self.block = self.paraEdit.document().blockCount() - 1
textBlock = self.paraEdit.document().findBlockByNumber(self.block)
cursor.setPosition(textBlock.position())
self.paraEdit.setTextCursor(cursor)
self.paraEdit.ensureCursorVisible()
return
def mousePressEvent(self, event):
return
def paintEvent(self, e):
position = self.paraEdit.document().findBlockByNumber(self.block).position()
cursor = self.paraEdit.textCursor()
cursor.setPosition(position)
rect = self.paraEdit.cursorRect(cursor)
# print(rect)
pos = self.paraEdit.mapToParent(self.paraEdit.pos())
painter = QPainter(self)
brush = QBrush()
brush.setColor(QColor("green"))
brush.setStyle(Qt.BrushStyle.SolidPattern)
path = QPainterPath()
path.moveTo(0, 0)
path.lineTo(pos.x() - 1.0, pos.y() / 2.0)
path.lineTo(0, pos.y())
path.lineTo(0, 0)
# XXX - Replace the guess with a calculated value
painter.translate(1.0, pos.y() + rect.y() + 12)
painter.fillPath(path, brush)
@pyqtSlot()
def nextParaAction(self) -> None:
self.block += 1
if self.block >= self.paraEdit.document().blockCount():
self.nextAction()
return
self.savePosition()
return
@pyqtSlot()
@@ -234,44 +287,58 @@ class EditDialog(QDialog, Ui_Dialog):
self.display_definition()
return
def scrollTo(self, position):
cursor = self.paraEdit.textCursor()
cursor.setPosition(position)
rect = self.paraEdit.cursorRect(cursor)
print(rect)
return
def savePosition(self) -> None:
cursor = self.paraEdit.textCursor()
cursor.setPosition(
self.paraEdit.document().findBlockByNumber(self.block).position()
)
self.paraEdit.setTextCursor(cursor)
self.scrollTo(cursor.position())
self.paraEdit.ensureCursorVisible()
self.update()
query = QSqlQuery()
query.prepare(
"UPDATE person_book SET section_id = :section_id, "
"block = :block "
"WHERE book_id = :book_id "
"AND person_id = :person_id"
)
query.bindValue(":section_id", self.section_id)
query.bindValue(":block", self.block)
query.bindValue(":book_id", self.book_id)
query.bindValue(":person_id", self.person_id)
if not query.exec():
query_error(query)
return
@pyqtSlot()
def nextAction(self) -> None:
if self.stackedWidget.currentIndex() == 1:
self.nextDefinition()
return
paraQuery = QSqlQuery()
paraQuery.prepare(
"SELECT * FROM paragraphs WHERE "
"section_id=:section_id AND sequence = :sequence"
)
paraQuery.bindValue(":section_id", self.section_id)
paraQuery.bindValue(":sequence", self.paraSequence + 1)
if not paraQuery.exec():
query_error(paraQuery)
if not paraQuery.next():
secQuery = QSqlQuery()
secQuery.prepare(
"SELECT * FROM sections WHERE book_id=:book_id "
"AND sequence = "
"(SELECT sequence FROM sections WHERE "
"section_id = :section_id)+1"
)
secQuery.bindValue(":book_id", self.book_id)
secQuery.bindValue(":section_id", self.section_id)
if not secQuery.exec():
query_error(secQuery)
if not secQuery.next():
return
self.paraSequence = 0
self.section_id = secQuery.value("section_id")
paraQuery.bindValue(":section_id", self.section_id)
paraQuery.bindValue(":sequence", self.paraSequence)
if not paraQuery.exec():
query_error(paraQuery)
paraQuery.next()
self.display_paragraph(paraQuery.value("paragraph_id"))
sequence = self.section_map[self.section_id]
sequence += 1
self.section_id = self.sequence_map[sequence]
self.show_section(self.section_id)
self.savePosition()
return
@pyqtSlot()
def prevAction(self) -> None:
if self.stackedWidget.currentIndex() == 1:
return
sequence = self.section_map[self.section_id]
if sequence < 1:
return
sequence -= 1
self.section_id = self.sequence_map[sequence]
self.show_section(self.section_id, False)
self.savePosition()
return