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() == QMetaType::QString && 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 const QByteArray t = dev.read(3);
266 if (t == "rue") {
267 val = true;
268 } else {
269 return false;
270 }
271
272 } else if (c == 'f') {
273 const QByteArray t = dev.read(4);
274 if (t == "alse") {
275 val = false;
276 } else {
277 return false;
278 }
279 } else if (c == 'n') {
280 const QByteArray t = dev.read(3);
281 if (t == "ull") {
282 val = QVariant();
283 } else {
284 return false;
285 }
286 } else {
287 dev.ungetChar(c);
288 if (!parseNumber(dev, val)) {
289 return false;
290 }
291 }
292
293 return true;
294}
295
296QVariantHash KisCosParser::parseCosToJson(QByteArray *ba)
297{
298 QVariant root;
299 QBuffer dev(ba);
300 if (dev.open(QIODevice::ReadOnly)) {
301
302 eatSpace(dev);
303 char c;
304 dev.peek(&c, 1);
305 if (c == BeginObject) {
306 if (!parseValue(dev, root)) {
307 qWarning() << "dev not at end";
308 }
309 } else {
310 QVariantHash b;
311 if (!parseObject(dev, b, false)) {
312 qWarning() << "txt2 dev not at end";
313 }
314 root = b;
315 }
316 dev.close();
317 }
318 return root.toHash();
319}
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