2 * Copyright (c) 2020 - 2022 Samsung Electronics Co., Ltd. All rights reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 #elif defined(__linux__)
35 #include "tvgXmlParser.h"
37 /************************************************************************/
38 /* Internal Class Implementation */
39 /************************************************************************/
41 bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue)
43 #ifdef THORVG_LOG_ENABLED
44 const auto attributesNum = 6;
48 bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*)
51 {"id", false, nullptr},
52 {"data-name", false, nullptr},
53 {"overflow", false, "visible"},
54 {"version", false, nullptr},
55 {"xmlns", true, nullptr},
56 {"xml:space", false, nullptr},
59 for (unsigned int i = 0; i < attributesNum; ++i) {
60 if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) {
61 if (attributes[i].value && tagValue) {
62 if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) {
75 static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd)
77 for (; itr < itrEnd; itr++) {
78 if (isspace((unsigned char)*itr)) break;
84 static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd)
86 for (; itr < itrEnd; itr++) {
87 if (!isspace((unsigned char)*itr)) break;
93 static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart)
95 for (itr--; itr > itrStart; itr--) {
96 if (!isspace((unsigned char)*itr)) break;
102 static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd)
105 while (itr < itrEnd && *itr == '&') {
106 for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
107 if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) {
108 itr += xmlEntityLength[i];
119 static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart)
122 while (itr > itrStart && *(itr - 1) == ';') {
123 for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
124 if (itr - xmlEntityLength[i] > itrStart &&
125 strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) {
126 itr -= xmlEntityLength[i];
137 static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd)
139 itr = _simpleXmlSkipWhiteSpace(itr, itrEnd);
142 if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr;
144 if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr;
151 static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart)
153 itr = _simpleXmlUnskipWhiteSpace(itr, itrStart);
156 if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr;
158 if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr;
165 static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd)
167 return (const char*)memchr(itr, '<', itrEnd - itr);
171 static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd)
173 bool insideQuote = false;
174 for (; itr < itrEnd; itr++) {
175 if (*itr == '"') insideQuote = !insideQuote;
177 if ((*itr == '>') || (*itr == '<'))
185 static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd)
187 for (; itr < itrEnd; itr++) {
188 if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
194 static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd)
196 for (; itr < itrEnd; itr++) {
197 if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
203 static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd)
205 for (; itr < itrEnd; itr++) {
206 if (*itr == '>') return itr;
212 static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff)
217 return SimpleXMLType::Close;
218 } else if (itr[1] == '?') {
220 return SimpleXMLType::Processing;
221 } else if (itr[1] == '!') {
222 if ((itr + sizeof("<!DOCTYPE>") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) {
223 toff = sizeof("!DOCTYPE") - 1;
224 return SimpleXMLType::Doctype;
225 } else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) {
226 toff = sizeof("![CDATA[") - 1;
227 return SimpleXMLType::CData;
228 } else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) {
229 toff = sizeof("!--") - 1;
230 return SimpleXMLType::Comment;
231 } else if (itr + sizeof("<!>") - 1 < itrEnd) {
232 toff = sizeof("!") - 1;
233 return SimpleXMLType::DoctypeChild;
235 return SimpleXMLType::Open;
237 return SimpleXMLType::Open;
241 static char* _strndup(const char* src, unsigned len)
243 auto ret = (char*)malloc(len + 1);
244 if (!ret) return nullptr;
246 return (char*)memcpy(ret, src, len);
249 /************************************************************************/
250 /* External Class Implementation */
251 /************************************************************************/
253 const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
255 #ifdef THORVG_LOG_ENABLED
256 static const char* TYPE_NAMES[] = {
280 return TYPE_NAMES[(int) type];
286 bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
288 #ifdef THORVG_LOG_ENABLED
289 const auto elementsNum = 1;
290 const char* const elements[] = { "title" };
292 for (unsigned int i = 0; i < elementsNum; ++i) {
293 if (!strncmp(tagName, elements[i], strlen(tagName))) {
304 bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
306 const char *itr = buf, *itrEnd = buf + bufLength;
307 char* tmpBuf = (char*)malloc(bufLength + 1);
309 if (!buf || !func || !tmpBuf) goto error;
311 while (itr < itrEnd) {
312 const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
313 const char *key, *keyEnd, *value, *valueEnd;
316 if (p == itrEnd) goto success;
319 for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
320 if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
322 if (keyEnd == itrEnd) goto error;
323 if (keyEnd == key) continue;
325 if (*keyEnd == '=') value = keyEnd + 1;
327 value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
328 if (!value) goto error;
331 keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
333 value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
334 if (value == itrEnd) goto error;
336 if ((*value == '"') || (*value == '\'')) {
337 valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
338 if (!valueEnd) goto error;
341 valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
346 value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
347 valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
349 memcpy(tmpBuf, key, keyEnd - key);
350 tmpBuf[keyEnd - key] = '\0';
352 tval = tmpBuf + (keyEnd - key) + 1;
354 while (value < valueEnd) {
355 value = _simpleXmlSkipXmlEntities(value, valueEnd);
361 if (!func((void*)data, tmpBuf, tval)) {
362 if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
363 TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", tmpBuf, tval ? tval : "NONE");
378 bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
380 const char *itr = buf, *itrEnd = buf + bufLength;
382 if (!buf || !func) return false;
384 while (itr < itrEnd) {
387 if (itr + 1 >= itrEnd) return false;
390 SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
393 if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
394 else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
395 else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
396 else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
399 //Invalid case: '<' nested
400 if (*p == '<' && type != SimpleXMLType::Doctype) return false;
401 const char *start, *end;
403 start = itr + 1 + toff;
407 case SimpleXMLType::Open: {
409 type = SimpleXMLType::OpenEmpty;
414 case SimpleXMLType::CData: {
415 if (!memcmp(p - 2, "]]", 2)) end -= 2;
418 case SimpleXMLType::Processing: {
419 if (p[-1] == '?') end--;
422 case SimpleXMLType::Comment: {
423 if (!memcmp(p - 2, "--", 2)) end -= 2;
431 if (strip && (type != SimpleXMLType::CData)) {
432 start = _skipWhiteSpacesAndXmlEntities(start, end);
433 end = _unskipWhiteSpacesAndXmlEntities(end, start);
436 if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
447 p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
449 if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
454 p = _simpleXmlFindStartTag(itr, itrEnd);
458 if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
460 if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
462 if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
471 bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
478 if (!buf) return false;
480 end = buf + bufLength;
481 key = (char*)alloca(end - buf + 1);
482 val = (char*)alloca(end - buf + 1);
484 if (buf == end) return true;
487 char* sep = (char*)strchr(buf, ':');
488 next = (char*)strchr(buf, ';');
493 if (next >= end) next = nullptr;
498 if (next == nullptr && sep != nullptr) {
499 memcpy(key, buf, sep - buf);
500 key[sep - buf] = '\0';
502 memcpy(val, sep + 1, end - sep - 1);
503 val[end - sep - 1] = '\0';
504 } else if (sep < next && sep != nullptr) {
505 memcpy(key, buf, sep - buf);
506 key[sep - buf] = '\0';
508 memcpy(val, sep + 1, next - sep - 1);
509 val[next - sep - 1] = '\0';
511 memcpy(key, buf, next - buf);
512 key[next - buf] = '\0';
516 key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
517 key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
518 val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
519 val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
521 if (!func((void*)data, key, val)) {
522 if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
523 TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", key, val ? val : "NONE");
529 } while (next != nullptr);
537 * tag {}, .name {}, tag.name{}
539 const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength)
541 if (!buf) return nullptr;
543 *tag = *name = nullptr;
546 auto itr = _simpleXmlSkipWhiteSpace(buf, buf + bufLength);
547 auto itrEnd = (const char*)memchr(buf, '{', bufLength);
549 if (!itrEnd || itr == itrEnd) return nullptr;
551 auto nextElement = (const char*)memchr(itrEnd, '}', bufLength - (itrEnd - buf));
552 if (!nextElement) return nullptr;
555 *attrsLength = nextElement - *attrs;
559 itrEnd = _simpleXmlUnskipWhiteSpace(itrEnd, itr);
560 if (*(itrEnd - 1) == '.') return nullptr;
562 for (p = itr; p < itrEnd; p++) {
563 if (*p == '.') break;
566 if (p == itr) *tag = strdup("all");
567 else *tag = _strndup(itr, p - itr);
569 if (p == itrEnd) *name = nullptr;
570 else *name = _strndup(p + 1, itrEnd - p - 1);
572 return (nextElement ? nextElement + 1 : nullptr);
576 const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
578 const char *itr = buf, *itrEnd = buf + bufLength;
580 for (; itr < itrEnd; itr++) {
581 if (!isspace((unsigned char)*itr)) {
582 //User skip tagname and already gave it the attributes.
583 if (*itr == '=') return buf;
585 itr = _simpleXmlUnskipXmlEntities(itr, buf);
586 if (itr == itrEnd) return nullptr;