diff --git a/lib/books.py b/lib/books.py
index 05319ca..02e2eb8 100644
--- a/lib/books.py
+++ b/lib/books.py
@@ -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
diff --git a/lib/read.py b/lib/read.py
index fde328c..9e7b8db 100644
--- a/lib/read.py
+++ b/lib/read.py
@@ -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
diff --git a/main.py b/main.py
index 88b8d2b..d48504f 100755
--- a/main.py
+++ b/main.py
@@ -5,6 +5,7 @@ import sys
from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtGui import (
+ QAction,
QFont,
QTextCharFormat,
QTextCursor,
@@ -33,8 +34,6 @@ def query_error(query: QSqlQuery) -> None:
class MainWindow(QMainWindow, Ui_MainWindow):
- book_id = 0
-
def __init__(self) -> None:
super(MainWindow, self).__init__()
self.setupUi(self)
@@ -47,64 +46,77 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# self.wordButton.clicked.connect(self.wordAction)
self.ReadButton.clicked.connect(self.readAction)
self.bookBtn.clicked.connect(self.bookAction)
+ self.createActions()
self.show()
return
+ def createActions(self):
+ query = QSqlQuery()
+ query.prepare("SELECT * FROM books ORDER BY title")
+ if not query.exec():
+ query_error(query)
+ while query.next():
+ action = QAction(query.value("title"), self)
+ action.setData(query.value("book_id"))
+ action.triggered.connect(self.setBookAction)
+ self.menuBooks.addAction(action)
+ return
+
+ @pyqtSlot()
+ def setBookAction(self):
+ action = self.sender()
+ print(action)
+ book_id = action.data()
+ print(book_id)
+ indexes = self.peopleView.selectedIndexes()
+ if len(indexes) < 1:
+ return
+ person_id = indexes[0].siblingAtColumn(0).data()
+ print(person_id)
+ query = QSqlQuery()
+ query.prepare(
+ "UPDATE people SET book_id = :book_id " "WHERE person_id = :person_id"
+ )
+ query.bindValue(":book_id", book_id)
+ query.bindValue(":person_id", person_id)
+ if not query.exec():
+ query_error(query)
+ query.prepare(
+ "SELECT * FROM person_book "
+ "WHERE person_id = :person_id "
+ "AND book_id = :book_id"
+ )
+ query.bindValue(":person_id", person_id)
+ query.bindValue(":book_id", book_id)
+ if not query.exec():
+ query_error(query)
+ if query.next():
+ return
+ query.prepare(
+ "SELECT * FROM sections WHERE sequence = 0 " "AND book_id = :book_id"
+ )
+ query.bindValue(":book_id", book_id)
+ if not query.exec():
+ query_error(query)
+ if not query.next():
+ raise Exception(f"book_id: {book_id} has no section 0!")
+ section_id = query.value("section_id")
+ query.prepare(
+ "INSERT INTO person_book " "VALUES (:person_id, :book_id, :section_id, 0)"
+ )
+ query.bindValue(":person_id", person_id)
+ query.bindValue(":book_id", book_id)
+ query.bindValue(":section_id", section_id)
+ if not query.exec():
+ query_error(query)
+ return
+
@pyqtSlot()
def bookAction(self) -> None:
directory = QFileDialog.getExistingDirectory()
- book = Book(directory)
- self.book_id = self.store_book(book)
+ self.book = Book(directory)
return
- def store_book(self, book: Book) -> int:
- uuid = book.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", book.metadata["title"])
- query.bindValue(":author", book.metadata["creator"])
- query.bindValue(":uuid", uuid)
- if not query.exec():
- query_error(query)
- book_id = query.lastInsertId()
- section_query = QSqlQuery()
- section_query.prepare(
- "INSERT INTO sections (title, sequence, book_id) "
- "VALUES (:title, :sequence, :book_id)"
- )
- section_query.bindValue(":book_id", book_id)
- para_query = QSqlQuery()
- para_query.prepare(
- "INSERT INTO paragraphs (section_id, sequence, content) "
- "VALUES (:section_id, :sequence, :content)"
- )
- for seq, section in enumerate(book.sections):
- section_query.bindValue(":title", section["title"])
- section_query.bindValue(":sequence", seq)
- if not section_query.exec():
- query_error(section_query)
- section_id = query.lastInsertId()
- para_query.bindValue(":section_id", section_id)
- for ps, paragraph in enumerate(section["paragraphs"]):
- para_query.bindValue(":sequence", ps)
- para_query.bindValue(":content", paragraph)
- if not para_query.exec():
- query_error(para_query)
- return book_id
-
def load_definition(self, word: str, definition: dict) -> None:
document = None # self.textEdit.document()
myCursor = QTextCursor(document)
@@ -144,9 +156,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if len(indexes) < 1:
return
person_id = indexes[0].siblingAtColumn(0).data()
- name = indexes[0].data()
- print(person_id, name)
- dlg = EditDialog(self.book_id, person_id)
+ dlg = EditDialog(person_id)
dlg.exec()
return
@@ -161,24 +171,20 @@ SQL_CMDS = [
"uuid TEXT, level INTEGER)",
"CREATE TABLE IF NOT EXISTS sections "
"(section_id INTEGER PRIMARY KEY AUTOINCREMENT, "
- "title TEXT, sequence INTEGER, "
+ "sequence INTEGER, content TEXT, "
"book_id INTEGER REFERENCES books ON DELETE CASCADE) ",
- "CREATE TABLE IF NOT EXISTS paragraphs "
- "(paragraph_id INTEGER PRIMARY KEY AUTOINCREMENT, "
- "section_id INTEGER REFERENCES sections ON DELETE CASCADE, "
- "sequence INTEGER NOT NULL DEFAULT 0, "
- "content TEXT)",
"CREATE TABLE IF NOT EXISTS people "
"(person_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, "
- "organization TEXT, "
- "paragraph_id INTEGER REFERENCES paragraphs ON DELETE CASCADE)",
+ "organization TEXT, book_id INTEGER REFERENCES books ON DELETE CASCADE) ",
"CREATE TABLE IF NOT EXISTS person_book "
"(person_id INTEGER REFERENCES people ON DELETE CASCADE, "
- "book_id INTEGER REFERENCES books ON DELETE CASCADE)",
- "CREATE TABLE IF NOT EXISTS word_paragraph "
+ "book_id INTEGER REFERENCES books ON DELETE CASCADE, "
+ "section_id INTEGER REFERENCES sections, "
+ "block INTEGER)",
+ "CREATE TABLE IF NOT EXISTS word_block "
"(word_id INTEGER REFERENCES words ON DELETE CASCADE, "
- "paragraph_id INTEGER REFERENCES paragraphs ON DELETE CASCADE, "
- "start INTEGER NOT NULL, end INTEGER NOT NULL)",
+ "section_id INTEGER REFERENCES sections ON DELETE CASCADE, "
+ "block INTEGER NOT NULL, start INTEGER NOT NULL, end INTEGER NOT NULL)",
]
diff --git a/ui/MainWindow.py b/ui/MainWindow.py
index 65633e4..4ca2a6d 100644
--- a/ui/MainWindow.py
+++ b/ui/MainWindow.py
@@ -38,19 +38,31 @@ class Ui_MainWindow(object):
self.horizontalLayout.addWidget(self.widget)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
- self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 32))
self.menubar.setObjectName("menubar")
+ self.menuFile = QtWidgets.QMenu(parent=self.menubar)
+ self.menuFile.setObjectName("menuFile")
+ self.menuBooks = QtWidgets.QMenu(parent=self.menubar)
+ self.menuBooks.setObjectName("menuBooks")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
+ self.actionQuit = QtGui.QAction(parent=MainWindow)
+ self.actionQuit.setObjectName("actionQuit")
+ self.menuFile.addAction(self.actionQuit)
+ self.menubar.addAction(self.menuFile.menuAction())
+ self.menubar.addAction(self.menuBooks.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
- MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
+ MainWindow.setWindowTitle(_translate("MainWindow", "Reading Helper"))
self.WordButton.setText(_translate("MainWindow", "Words"))
self.ReadButton.setText(_translate("MainWindow", "Read"))
self.bookBtn.setText(_translate("MainWindow", "Add Book"))
+ self.menuFile.setTitle(_translate("MainWindow", "File"))
+ self.menuBooks.setTitle(_translate("MainWindow", "Books"))
+ self.actionQuit.setText(_translate("MainWindow", "Quit"))
diff --git a/ui/MainWindow.ui b/ui/MainWindow.ui
index 8335de1..b999766 100644
--- a/ui/MainWindow.ui
+++ b/ui/MainWindow.ui
@@ -11,7 +11,7 @@
- MainWindow
+ Reading Helper
@@ -66,11 +66,29 @@
0
0
800
- 22
+ 32
+
+
+
+
+
+
+ Quit
+
+