Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_cos_parser.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2023 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "kis_cos_parser.h"
7
8#include <QTextCodec>
9#include <QVariantHash>
10#include <QVariantList>
11#include <QBuffer>
12#include <QVariant>
13#include <QDebug>
14
15enum {
16 Null = 0x00,
17 Space = 0x20,
18 Tab = 0x09,
19 LineFeed = 0x0a,
20 FormFeed = 0x0c,
21 Return = 0x0d,
22 BeginArray = 0x5b, // [
23 BeginObject = 0x3c, // <
24 EndArray = 0x5d, // ]
25 EndObject = 0x3e, // >
26 BeginName = 0x2f, // /
27 BeginString = 0x28, // (
28 EndString = 0x29, // )
29 ByteOrderMark = 0xfe
30};
31
32bool isWhiteSpace(char c) {
33 switch (c) {
34 case Null:
35 case Tab:
36 case LineFeed:
37 case FormFeed:
38 case Return:
39 case Space:
40 return true;
41 default:
42 return false;
43 }
44}
45
46void eatSpace(QIODevice &dev) {
47 char c;
48 dev.peek(&c, 1);
49 while (isWhiteSpace(c) && !dev.atEnd()) {
50 dev.skip(1);
51 dev.peek(&c, 1);
52 }
53}
54
55bool parseName(QIODevice &dev, QVariant &val) {
56 char c;
57 dev.getChar(&c);
58 QString name = "/"; // prepending with / so that we know this is a name.
59
60 while (c >= 0x21 && c<0x7e && !isWhiteSpace(c) && c != BeginName && !dev.atEnd()) {
61 name.append(c);
62 dev.getChar(&c);
63 }
64
65 if (c == BeginName) {
66 dev.ungetChar(c);
67 }
68 val = QVariant(name);
69 return true;
70}
71
72const QMap<char, char> escaped = {
73 {'n', LineFeed},
74 {'r', Return},
75 {'t', Tab},
76 {'b', 0x08}, // backspace
77 {'f', FormFeed},
78 {'(', BeginString},
79 {')', EndString},
80 {'\\', 0x5c} // reverse solidus/backslash
81};
82
83bool parseString(QIODevice &dev, QVariant &val) {
84 char c;
85 dev.getChar(&c);
86 QByteArray text;
87
88 while(c != EndString && !dev.atEnd()) {
89 if (c == '\\') {
90
91 // Try to parse PDF \ddd notation.
92 char c2;
93 dev.peek(&c2, 1);
94
95 if (escaped.keys().contains(c2)) {
96 text.append(escaped.value(c2));
97 dev.skip(1);
98 } else {
99 QByteArray octal;
100 for (int i=0; i<3; i++) {
101 char c2;
102 dev.peek(&c2, 1);
103 if (c2 >= '0' && c2 <= '9') {
104 octal.append(c2);
105 dev.skip(1);
106 }
107 }
108 bool ok;
109 int val = octal.toInt(&ok, 8);
110 if (ok) {
111 qWarning() << "escaped octal" << val;
112 // don't know how to actually interpret this as a char...
113 } else {
114 text.append(c);
115 text.append(octal);
116
117 }
118 }
119 } else {
120 text.append(c);
121 }
122 dev.getChar(&c);
123 }
124
125
126 if (text.startsWith(ByteOrderMark)) {
127 QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE");
128 val = Utf16Codec->toUnicode(text);
129 } else {
130 val = QString::fromLatin1(text);
131 }
132
133 return true;
134}
135
136bool parseHexString(QIODevice &dev, QVariant &val) {
137 char c;
138 dev.getChar(&c);
139 QByteArray hex;
140
141 while (c!= EndObject && !dev.atEnd()) {
142 hex.append(c);
143 dev.getChar(&c);
144 }
145
146 val = QString("<"+QString::fromLatin1(hex)+">");
147 return true;
148}
149
150bool parseNumber(QIODevice &dev, QVariant &val) {
151 char c;
152 bool isDouble = false;
153 dev.getChar(&c);
154 QString number;
155 if (c == '-' || c == '+') {
156 number.append(c);
157 dev.getChar(&c);
158 }
159 while (c >= '0' && c <= '9') {
160 number.append(c);
161 dev.getChar(&c);
162 }
163 if (c == '.') {
164 isDouble = true;
165 number.append(c);
166 dev.getChar(&c);
167 while (c >= '0' && c <= '9') {
168 number.append(c);
169 dev.getChar(&c);
170 }
171 }
172
173 bool ok;
174 if (isDouble) {
175 val = number.toDouble(&ok);
176 } else {
177 val = number.toInt(&ok);
178 }
179 return ok;
180}
181
182bool KisCosParser::parseObject(QIODevice &dev, QVariantHash &object, bool checkEnd) {
183 eatSpace(dev);
184
185 QVariant key;
186 QVariant val;
187 while (parseValue(dev, key)) {
188 object.insert(key.toString(), QVariant());
189 if (key.type() == QVariant::String && parseValue(dev, val)) {
190 object.insert(key.toString(), val);
191 } else {
192 return false;
193 }
194 }
195 char c;
196 dev.getChar(&c);
197 if (c == EndObject) {
198 dev.skip(1);
199 return true;
200 } else if (checkEnd) {
201 return false;
202 }
203
204 return true;
205}
206
207bool KisCosParser::parseArray(QIODevice &dev, QVariantList &array)
208{
209 eatSpace(dev);
210
211 QVariant val;
212 while (parseValue(dev, val)) {
213 array.append(val);
214 }
215 char c;
216 dev.getChar(&c);
217 if (c == EndArray) {
218 return true;
219 } else {
220 return false;
221 }
222
223 return true;
224}
225
226bool KisCosParser::parseValue(QIODevice &dev, QVariant &val) {
227
228 eatSpace(dev);
229 char c;
230 dev.getChar(&c);
231
232 if (c == BeginObject) {
233 char c2;
234 dev.peek(&c2, 1);
235 if (c2 == BeginObject) {
236 QVariantHash object = QVariantHash();
237 dev.skip(1);
238 if (!parseObject(dev, object)) {
239 return false;
240 }
241 val = object;
242 } else {
243 if (!parseHexString(dev, val)) {
244 return false;
245 }
246 }
247 } else if (c == BeginArray) {
248 QVariantList array = QVariantList();
249 if (!parseArray(dev, array)) {
250 return false;
251 }
252 val = array;
253 } else if (c == BeginName) {
254 if (!parseName(dev, val)) {
255 return false;
256 }
257 } else if (c == BeginString) {
258 if (!parseString(dev, val)) {
259 return false;
260 }
261 } else if (c == EndObject || c == EndArray) {
262 dev.ungetChar(c);
263 return false;
264 } else if (c == 't') {
265 QByteArray t;
266 dev.read(t.data(), 3);
267 if (t[0] == 'r' && t[1] == 'u' && t[2] == 'e') {
268 val = true;
269 } else {
270 return false;
271 }
272
273 } else if (c == 'f') {
274 QByteArray t;
275 dev.read(t.data(), 4);
276 if (t[0] == 'a' && t[1] == 'l' && t[2] == 's' && t[3] == 'e') {
277 val = false;
278 } else {
279 return false;
280 }
281 } else if (c == 'n') {
282 QByteArray t;
283 dev.read(t.data(), 3);
284 if (t[0] == 'u' && t[1] == 'l' && t[2] == 'l') {
285 val = QVariant();
286 } else {
287 return false;
288 }
289 } else {
290 dev.ungetChar(c);
291 if (!parseNumber(dev, val)) {
292 return false;
293 }
294 }
295
296 return true;
297}
298
299QVariantHash KisCosParser::parseCosToJson(QByteArray *ba)
300{
301 QVariant root;
302 QBuffer dev(ba);
303 if (dev.open(QIODevice::ReadOnly)) {
304
305 eatSpace(dev);
306 char c;
307 dev.peek(&c, 1);
308 if (c == BeginObject) {
309 if (!parseValue(dev, root)) {
310 qWarning() << "dev not at end";
311 }
312 } else {
313 QVariantHash b;
314 if (!parseObject(dev, b, false)) {
315 qWarning() << "txt2 dev not at end";
316 }
317 root = b;
318 }
319 dev.close();
320 }
321 return root.toHash();
322}
bool parseArray(QIODevice &dev, QVariantList &array)
QVariantHash parseCosToJson(QByteArray *ba)
bool parseValue(QIODevice &dev, QVariant &val)
bool parseObject(QIODevice &dev, QVariantHash &object, bool checkEnd=true)
bool parseName(QIODevice &dev, QVariant &val)
bool parseString(QIODevice &dev, QVariant &val)
bool parseHexString(QIODevice &dev, QVariant &val)
const QMap< char, char > escaped
void eatSpace(QIODevice &dev)
bool isWhiteSpace(char c)
bool parseNumber(QIODevice &dev, QVariant &val)
@ Tab
@ Space
@ FormFeed
@ EndArray
@ BeginArray
@ LineFeed
@ ByteOrderMark
@ EndObject
@ Null
@ BeginObject
@ BeginString
@ Return
@ EndString
@ BeginName