/* * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #ifdef _WIN32 #include #else #include #endif #ifdef THORVG_LOG_ENABLED #include #endif #include "tvgXmlParser.h" #ifdef THORVG_LOG_ENABLED string _nodeTypeToString(SvgNodeType type) { switch (type) { case SvgNodeType::Doc: return "Doc"; case SvgNodeType::G: return "G"; case SvgNodeType::Defs: return "Defs"; case SvgNodeType::Animation: return "Animation"; case SvgNodeType::Arc: return "Arc"; case SvgNodeType::Circle: return "Circle"; case SvgNodeType::Ellipse: return "Ellipse"; case SvgNodeType::Image: return "Image"; case SvgNodeType::Line: return "Line"; case SvgNodeType::Path: return "Path"; case SvgNodeType::Polygon: return "Polygon"; case SvgNodeType::Polyline: return "Polyline"; case SvgNodeType::Rect: return "Rect"; case SvgNodeType::Text: return "Text"; case SvgNodeType::TextArea: return "TextArea"; case SvgNodeType::Tspan: return "Tspan"; case SvgNodeType::Use: return "Use"; case SvgNodeType::Video: return "Video"; case SvgNodeType::ClipPath: return "ClipPath"; default: return "Unknown"; } return "Unknown"; } #endif static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd) { for (; itr < itrEnd; itr++) { if (isspace((unsigned char)*itr)) break; } return itr; } static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd) { for (; itr < itrEnd; itr++) { if (!isspace((unsigned char)*itr)) break; } return itr; } static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart) { for (itr--; itr > itrStart; itr--) { if (!isspace((unsigned char)*itr)) break; } return itr + 1; } static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd) { return (const char*)memchr(itr, '<', itrEnd - itr); } static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd) { bool insideQuote = false; for (; itr < itrEnd; itr++) { if (*itr == '"') insideQuote = !insideQuote; if (!insideQuote) { if ((*itr == '>') || (*itr == '<')) return itr; } } return nullptr; } static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd) { for (; itr < itrEnd; itr++) { if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2; } return nullptr; } static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd) { for (; itr < itrEnd; itr++) { if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2; } return nullptr; } static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd) { for (; itr < itrEnd; itr++) { if (*itr == '>') return itr; } return nullptr; } bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data) { const char *itr = buf, *itrEnd = buf + bufLength; char* tmpBuf = (char*)alloca(bufLength + 1); if (!buf) return false; if (!func) return false; while (itr < itrEnd) { const char* p = _simpleXmlSkipWhiteSpace(itr, itrEnd); const char *key, *keyEnd, *value, *valueEnd; char* tval; if (p == itrEnd) return true; key = p; for (keyEnd = key; keyEnd < itrEnd; keyEnd++) { if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break; } if (keyEnd == itrEnd) return false; if (keyEnd == key) continue; if (*keyEnd == '=') value = keyEnd + 1; else { value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd); if (!value) return false; value++; } for (; value < itrEnd; value++) { if (!isspace((unsigned char)*value)) break; } if (value == itrEnd) return false; if ((*value == '"') || (*value == '\'')) { valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value); if (!valueEnd) return false; value++; } else { valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd); } memcpy(tmpBuf, key, keyEnd - key); tmpBuf[keyEnd - key] = '\0'; tval = tmpBuf + (keyEnd - key) + 1; memcpy(tval, value, valueEnd - value); tval[valueEnd - value] = '\0'; #ifdef THORVG_LOG_ENABLED if (!func((void*)data, tmpBuf, tval)) printf("SVG: Unsupported attributes used [Elements type: %s][Attribute: %s]\n", _nodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type).c_str(), tmpBuf); #else func((void*)data, tmpBuf, tval); #endif itr = valueEnd + 1; } return true; } bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data) { const char *itr = buf, *itrEnd = buf + bufLength; if (!buf) return false; if (!func) return false; #define CB(type, start, end) \ do { \ size_t _sz = end - start; \ bool _ret; \ _ret = func((void*)data, type, start, _sz); \ if (!_ret) \ return false; \ } while (0) while (itr < itrEnd) { if (itr[0] == '<') { if (itr + 1 >= itrEnd) { CB(SimpleXMLType::Error, itr, itrEnd); return false; } else { SimpleXMLType type; size_t toff; const char* p; if (itr[1] == '/') { type = SimpleXMLType::Close; toff = 1; } else if (itr[1] == '?') { type = SimpleXMLType::Processing; toff = 1; } else if (itr[1] == '!') { if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) { type = SimpleXMLType::Doctype; toff = sizeof("!DOCTYPE") - 1; } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) { type = SimpleXMLType::Comment; toff = sizeof("!--") - 1; } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) { type = SimpleXMLType::CData; toff = sizeof("![CDATA[") - 1; } else if (itr + sizeof("") - 1 < itrEnd) { type = SimpleXMLType::DoctypeChild; toff = sizeof("!") - 1; } else { type = SimpleXMLType::Open; toff = 0; } } else { type = SimpleXMLType::Open; toff = 0; } if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd); else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd); else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd); else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd); if ((p) && (*p == '<')) { type = SimpleXMLType::Error; toff = 0; } if (p) { const char *start, *end; start = itr + 1 + toff; end = p; switch (type) { case SimpleXMLType::Open: { if (p[-1] == '/') { type = SimpleXMLType::OpenEmpty; end--; } break; } case SimpleXMLType::CData: { if (!memcmp(p - 2, "]]", 2)) end -= 2; break; } case SimpleXMLType::Processing: { if (p[-1] == '?') end--; break; } case SimpleXMLType::Comment: { if (!memcmp(p - 2, "--", 2)) end -= 2; break; } default: { break; } } if ((strip) && (type != SimpleXMLType::Error) && (type != SimpleXMLType::CData)) { start = _simpleXmlSkipWhiteSpace(start, end); end = _simpleXmlUnskipWhiteSpace(end, start + 1); } CB(type, start, end); if (type != SimpleXMLType::Error) itr = p + 1; else itr = p; } else { CB(SimpleXMLType::Error, itr, itrEnd); return false; } } } else { const char *p, *end; if (strip) { p = _simpleXmlSkipWhiteSpace(itr, itrEnd); if (p) { CB(SimpleXMLType::Ignored, itr, p); itr = p; } } p = _simpleXmlFindStartTag(itr, itrEnd); if (!p) p = itrEnd; end = p; if (strip) end = _simpleXmlUnskipWhiteSpace(end, itr); if (itr != end) CB(SimpleXMLType::Data, itr, end); if ((strip) && (end < p)) CB(SimpleXMLType::Ignored, end, p); itr = p; } } #undef CB return true; } bool simpleXmlParseW3CAttribute(const char* buf, simpleXMLAttributeCb func, const void* data) { const char* end; char* key; char* val; char* next; if (!buf) return false; end = buf + strlen(buf); key = (char*)alloca(end - buf + 1); val = (char*)alloca(end - buf + 1); if (buf == end) return true; do { char* sep = (char*)strchr(buf, ':'); next = (char*)strchr(buf, ';'); key[0] = '\0'; val[0] = '\0'; if (next == nullptr && sep != nullptr) { memcpy(key, buf, sep - buf); key[sep - buf] = '\0'; memcpy(val, sep + 1, end - sep - 1); val[end - sep - 1] = '\0'; } else if (sep < next && sep != nullptr) { memcpy(key, buf, sep - buf); key[sep - buf] = '\0'; memcpy(val, sep + 1, next - sep - 1); val[next - sep - 1] = '\0'; } else if (next) { memcpy(key, buf, next - buf); key[next - buf] = '\0'; } if (key[0]) { if (!func((void*)data, key, val)) return false; } buf = next + 1; } while (next != nullptr); return true; } const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength) { const char *itr = buf, *itrEnd = buf + bufLength; for (; itr < itrEnd; itr++) { if (!isspace((unsigned char)*itr)) { //User skip tagname and already gave it the attributes. if (*itr == '=') return buf; } else { itr = _simpleXmlSkipWhiteSpace(itr + 1, itrEnd); if (itr == itrEnd) return nullptr; return itr; } } return nullptr; }