Skip to content

Commit d3763ae

Browse files
committed
src/anki: make memento glossaries track closer to yomitan's
Yomitan has expanded structured content quite a bit. This commit bring's Memento's structured content parser up to parity with Yomitan's by adding support to styles.css files which are now in DictionaryInfos and all the new fields supported by structured content. This moves away from using inline styles and instead adds a default stylesheet.
1 parent 6fa026e commit d3763ae

8 files changed

Lines changed: 709 additions & 147 deletions

File tree

src/anki/glossarybuilder.cpp

Lines changed: 484 additions & 109 deletions
Large diffs are not rendered by default.

src/anki/glossarybuilder.h

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020

2121
#pragma once
2222

23-
#include <QString>
24-
#include <QPair>
25-
#include <QList>
2623
#include <QJsonArray>
24+
#include <QJsonObject>
25+
#include <QList>
26+
#include <QPair>
27+
#include <QSet>
28+
#include <QString>
2729

2830
/**
2931
* @brief Builds Anki-compatible glossaries.
@@ -60,6 +62,33 @@ class GlossaryBuilder
6062
private:
6163
GlossaryBuilder() {}
6264

65+
/**
66+
* @brief Escape a string for use in HTML.
67+
*
68+
* @param str The string to escape.
69+
* @return An HTML escaped string.
70+
*/
71+
[[nodiscard]]
72+
static QString escapeHtml(const QString &str);
73+
74+
/**
75+
* @brief Convert a structured data key to an HTML data attribute name.
76+
*
77+
* @param key The structured data key.
78+
* @return The HTML data attribute name.
79+
*/
80+
[[nodiscard]]
81+
static QString structuredDataAttributeName(const QString &key);
82+
83+
/**
84+
* @brief Check if a structured content tag is supported.
85+
*
86+
* @param tag The structured content tag to check.
87+
* @return true if the tag is supported, false otherwise.
88+
*/
89+
[[nodiscard]]
90+
static bool isSupportedStructuredTag(const QString &tag);
91+
6392
/**
6493
* @brief Add structured data attributes to the string.
6594
*

src/anki/notebuilder.cpp

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include <QDir>
2626
#include <QHash>
27+
#include <QSet>
2728

2829
#include "anki/ankiconnect.h"
2930
#include "anki/marker.h"
@@ -536,6 +537,10 @@ static std::optional<AudioMediaParams> getAudioMediaParams(
536537
{
537538
std::optional<TimingSource> source =
538539
getTimingSource(marker.args[SOURCE_KEY]);
540+
if (!source)
541+
{
542+
return {};
543+
}
539544
params.source = *source;
540545
}
541546

@@ -629,6 +634,10 @@ static std::optional<VideoParams> getVideoParams(
629634
{
630635
std::optional<TimingSource> source =
631636
getTimingSource(marker.args[SOURCE_KEY]);
637+
if (!source)
638+
{
639+
return {};
640+
}
632641
params.source = *source;
633642
}
634643

@@ -1099,31 +1108,71 @@ static int getFrequencyAverage(const QList<Frequency *> &frequencies)
10991108
[[nodiscard]]
11001109
static GlossaryData getGlossary(const QList<TermDefinition *> &definitions)
11011110
{
1111+
constexpr const char *GLOSSARY_OPEN =
1112+
"<div class=\"yomitan-glossary memento-glossary\">"
1113+
"<ol>";
1114+
constexpr const char *GLOSSARY_CLOSE =
1115+
"<style>"
1116+
".memento-glossary {"
1117+
"text-align: left;"
1118+
"}"
1119+
1120+
".memento-glossary img {"
1121+
"display: inline-block;"
1122+
"}"
1123+
1124+
".memento-glossary table {"
1125+
"table-layout: auto;"
1126+
"border-collapse: collapse;"
1127+
"}"
1128+
1129+
".memento-glossary th {"
1130+
"font-weight: bold;"
1131+
"border-style: solid;"
1132+
"padding: 0.25em;"
1133+
"vertical-align: top;"
1134+
"border-width: 1px;"
1135+
"border-color: currentColor;"
1136+
"}"
1137+
1138+
".memento-glossary td {"
1139+
"border-style: solid;"
1140+
"padding: 0.25em;"
1141+
"vertical-align: top;"
1142+
"border-width: 1px;"
1143+
"border-color: currentColor;"
1144+
"}"
1145+
"</style></ol></div>";
1146+
static const QString LIST_ITEM_DICTIONARY = "<li data-dictionary=\"%1\">";
1147+
11021148
if (definitions.empty())
11031149
{
11041150
return {};
11051151
}
11061152

11071153
GlossaryData data;
1154+
QSet<int64_t> dictionariesWithStyles;
11081155

11091156
QString basepath =
11101157
DirectoryUtils::getDictionaryResourceDir() + QDir::separator();
11111158

1112-
data.glossary += "<div style=\"text-align: left;\"><ol>";
1113-
data.glossaryBrief += "<div style=\"text-align: left;\"><ol>";
1114-
data.glossaryCompact += "<div style=\"text-align: left;\"><ol>";
1159+
data.glossary += GLOSSARY_OPEN;
1160+
data.glossaryBrief += GLOSSARY_OPEN;
1161+
data.glossaryCompact += GLOSSARY_OPEN;
11151162

1116-
for (const TermDefinition *def : definitions)
1163+
for (qsizetype i = 0; i < definitions.size(); ++i)
11171164
{
1165+
const TermDefinition *def = definitions[i];
1166+
11181167
if (!def->selected())
11191168
{
11201169
continue;
11211170
}
11221171

11231172
data.glossary +=
1124-
"<li data-dictionary=\"" + def->dictionaryInfo()->name() + "\">";
1173+
LIST_ITEM_DICTIONARY.arg(def->dictionaryInfo()->name());
11251174
data.glossaryCompact +=
1126-
"<li data-dictionary=\"" + def->dictionaryInfo()->name() + "\">";
1175+
LIST_ITEM_DICTIONARY.arg(def->dictionaryInfo()->name());
11271176

11281177
data.glossary += "<i>(";
11291178
data.glossaryCompact += "<i>(";
@@ -1137,7 +1186,7 @@ static GlossaryData getGlossary(const QList<TermDefinition *> &definitions)
11371186
data.glossaryCompact += def->dictionaryInfo()->name();
11381187
data.glossaryCompact += ")</i> ";
11391188

1140-
data.glossary += "<span><ul>";
1189+
data.glossary += "<span>";
11411190
data.glossaryCompact += "<span>";
11421191

11431192
QStringList items = GlossaryBuilder::buildGlossary(
@@ -1147,23 +1196,39 @@ static GlossaryData getGlossary(const QList<TermDefinition *> &definitions)
11471196
);
11481197
for (const QString &item : items)
11491198
{
1150-
data.glossary += "<li>";
11511199
data.glossary += item;
1152-
data.glossary += "</li>";
11531200

11541201
data.glossaryBrief += "<li>";
11551202
data.glossaryBrief += item;
11561203
data.glossaryBrief += "</li>";
11571204
}
11581205
data.glossaryCompact += items.join(" | ");
11591206

1160-
data.glossary += "</ul></span></li>";
1207+
data.glossary += "</span></li>";
11611208
data.glossaryCompact += "</span></li>";
1209+
1210+
if (!def->dictionaryInfo()->styles().isEmpty() &&
1211+
!dictionariesWithStyles.contains(def->dictionaryInfo()->id()))
1212+
{
1213+
dictionariesWithStyles.insert(def->dictionaryInfo()->id());
1214+
1215+
data.glossary += "<style>";
1216+
data.glossary += def->dictionaryInfo()->styles();
1217+
data.glossary += "</style>";
1218+
1219+
data.glossaryBrief += "<style>";
1220+
data.glossaryBrief += def->dictionaryInfo()->styles();
1221+
data.glossaryBrief += "</style>";
1222+
1223+
data.glossaryCompact += "<style>";
1224+
data.glossaryCompact += def->dictionaryInfo()->styles();
1225+
data.glossaryCompact += "</style>";
1226+
}
11621227
}
11631228

1164-
data.glossary += "</ol></div>";
1165-
data.glossaryBrief += "</ol></div>";
1166-
data.glossaryCompact += "</ol></div>";
1229+
data.glossary += GLOSSARY_CLOSE;
1230+
data.glossaryBrief += GLOSSARY_CLOSE;
1231+
data.glossaryCompact += GLOSSARY_CLOSE;
11671232
return data;
11681233
}
11691234

src/dict/data/dictionaryinfo.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ DictionaryInfo *DictionaryInfo::clone(QObject *parent) const
3636
copy->setId(id());
3737
copy->setName(name());
3838
copy->setEnabled(enabled());
39+
copy->setStyles(styles());
3940
return copy;
4041
}
4142

@@ -83,3 +84,18 @@ void DictionaryInfo::setEnabled(bool value)
8384
m_enabled = value;
8485
emit enabledChanged(m_enabled);
8586
}
87+
88+
const QString &DictionaryInfo::styles() const noexcept
89+
{
90+
return m_styles;
91+
}
92+
93+
void DictionaryInfo::setStyles(const QString &value)
94+
{
95+
if (m_styles == value)
96+
{
97+
return;
98+
}
99+
m_styles = value;
100+
emit stylesChanged(m_styles);
101+
}

src/dict/data/dictionaryinfo.h

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,52 +52,59 @@ class DictionaryInfo : public QObject
5252
NOTIFY enabledChanged
5353
)
5454

55+
Q_PROPERTY(
56+
QString styles
57+
READ styles
58+
WRITE setStyles
59+
NOTIFY stylesChanged
60+
)
61+
5562
friend class DictionarySearch;
5663

5764
public:
5865
DictionaryInfo(QObject *parent = nullptr);
5966
virtual ~DictionaryInfo();
6067

6168
/**
62-
* @brief Creates a deep copy of this object.
69+
* @brief Create a deep copy of this object.
6370
*
6471
* @param parent The parent of the new object.
6572
* @return The cloned DictionaryInfo.
6673
*/
6774
DictionaryInfo *clone(QObject *parent = nullptr) const;
6875

6976
/**
70-
* @brief Gets the ID of the dictionary.
77+
* @brief Get the ID of the dictionary.
7178
*
7279
* @return The dictionary id.
7380
*/
7481
[[nodiscard]]
7582
int64_t id() const noexcept;
7683

7784
/**
78-
* @brief Sets the ID of the dictionary.
85+
* @brief Set the ID of the dictionary.
7986
*
8087
* @param value The new ID of the dictionary.
8188
*/
8289
void setId(int64_t value);
8390

8491
/**
85-
* @brief Gets the name of the dictionary.
92+
* @brief Get the name of the dictionary.
8693
*
8794
* @return The name of the dictionary.
8895
*/
8996
[[nodiscard]]
9097
const QString &name() const noexcept;
9198

9299
/**
93-
* @brief Sets the name of the dictionary.
100+
* @brief Set the name of the dictionary.
94101
*
95102
* @param value The new name of the dictionary.
96103
*/
97104
void setName(const QString &value);
98105

99106
/**
100-
* @brief Gets if the dictionary is enabled.
107+
* @brief Get if the dictionary is enabled.
101108
*
102109
* @return true if the dictionary is enabled,
103110
* @return false otherwise.
@@ -106,12 +113,27 @@ class DictionaryInfo : public QObject
106113
bool enabled() const noexcept;
107114

108115
/**
109-
* @brief Sets if the dictionary is enabled.
116+
* @brief Set if the dictionary is enabled.
110117
*
111118
* @param value true if the dictionary should be enabled, false otherwise.
112119
*/
113120
void setEnabled(bool value);
114121

122+
/**
123+
* @brief Get the CSS stylesheet for this dictionary.
124+
*
125+
* @return The CSS stylesheet for this dictionary.
126+
*/
127+
[[nodiscard]]
128+
const QString &styles() const noexcept;
129+
130+
/**
131+
* @brief Set the CSS stylesheet for this dictionary.
132+
*
133+
* @param value The CSS stylesheet for this dictionary.
134+
*/
135+
void setStyles(const QString &value);
136+
115137
signals:
116138
/**
117139
* @brief Emitted when the ID of the dictionary changes.
@@ -134,6 +156,13 @@ class DictionaryInfo : public QObject
134156
*/
135157
void enabledChanged(bool value);
136158

159+
/**
160+
* @brief Emitted when the CSS stylesheet is changed.
161+
*
162+
* @param value The new stylesheet.
163+
*/
164+
void stylesChanged(const QString &value);
165+
137166
protected:
138167
/* ID of the dictionary */
139168
int64_t m_id{0};
@@ -143,4 +172,7 @@ class DictionaryInfo : public QObject
143172

144173
/* true if this dictionary is enabled, false otherwise */
145174
bool m_enabled{false};
175+
176+
/* The CSS stylesheet of this dictionary */
177+
QString m_styles;
146178
};

0 commit comments

Comments
 (0)