Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XmlDocument.cpp 26KB

6 years ago
6 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. /*
  2. ==============================================================================
  3. This file is part of the Water library.
  4. Copyright (c) 2016 ROLI Ltd.
  5. Copyright (C) 2017-2018 Filipe Coelho <falktx@falktx.com>
  6. Permission is granted to use this software under the terms of the ISC license
  7. http://www.isc.org/downloads/software-support-policy/isc-license/
  8. Permission to use, copy, modify, and/or distribute this software for any
  9. purpose with or without fee is hereby granted, provided that the above
  10. copyright notice and this permission notice appear in all copies.
  11. THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
  12. TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  13. FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
  14. OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
  15. USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
  16. TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  17. OF THIS SOFTWARE.
  18. ==============================================================================
  19. */
  20. #include "XmlDocument.h"
  21. #include "XmlElement.h"
  22. #include "../containers/LinkedListPointer.h"
  23. #include "../streams/FileInputSource.h"
  24. #include "../streams/InputStream.h"
  25. #include "../streams/MemoryOutputStream.h"
  26. namespace water {
  27. XmlDocument::XmlDocument (const String& documentText)
  28. : originalText (documentText),
  29. input (nullptr),
  30. outOfData (false),
  31. errorOccurred (false),
  32. needToLoadDTD (false),
  33. ignoreEmptyTextElements (true)
  34. {
  35. }
  36. XmlDocument::XmlDocument (const File& file)
  37. : input (nullptr),
  38. outOfData (false),
  39. errorOccurred (false),
  40. needToLoadDTD (false),
  41. ignoreEmptyTextElements (true),
  42. inputSource (new FileInputSource (file))
  43. {
  44. }
  45. XmlDocument::~XmlDocument()
  46. {
  47. }
  48. XmlElement* XmlDocument::parse (const File& file)
  49. {
  50. XmlDocument doc (file);
  51. return doc.getDocumentElement();
  52. }
  53. XmlElement* XmlDocument::parse (const String& xmlData)
  54. {
  55. XmlDocument doc (xmlData);
  56. return doc.getDocumentElement();
  57. }
  58. void XmlDocument::setInputSource (FileInputSource* const newSource) noexcept
  59. {
  60. inputSource = newSource;
  61. }
  62. void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) noexcept
  63. {
  64. ignoreEmptyTextElements = shouldBeIgnored;
  65. }
  66. namespace XmlIdentifierChars
  67. {
  68. static bool isIdentifierCharSlow (const water_uchar c) noexcept
  69. {
  70. return CharacterFunctions::isLetterOrDigit (c)
  71. || c == '_' || c == '-' || c == ':' || c == '.';
  72. }
  73. static bool isIdentifierChar (const water_uchar c) noexcept
  74. {
  75. static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 };
  76. return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0)
  77. : isIdentifierCharSlow (c);
  78. }
  79. /*static void generateIdentifierCharConstants()
  80. {
  81. uint32 n[8] = { 0 };
  82. for (int i = 0; i < 256; ++i)
  83. if (isIdentifierCharSlow (i))
  84. n[i >> 5] |= (1 << (i & 31));
  85. String s;
  86. for (int i = 0; i < 8; ++i)
  87. s << "0x" << String::toHexString ((int) n[i]) << ", ";
  88. DBG (s);
  89. }*/
  90. static String::CharPointerType findEndOfToken (String::CharPointerType p)
  91. {
  92. while (isIdentifierChar (*p))
  93. ++p;
  94. return p;
  95. }
  96. }
  97. XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement)
  98. {
  99. if (originalText.isEmpty() && inputSource != nullptr)
  100. {
  101. ScopedPointer<InputStream> in (inputSource->createInputStream());
  102. if (in != nullptr)
  103. {
  104. MemoryOutputStream data;
  105. data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1);
  106. if (data.getDataSize() > 2)
  107. {
  108. data.writeByte (0);
  109. const char* text = static_cast<const char*> (data.getData());
  110. if (CharPointer_UTF8::isByteOrderMark (text))
  111. text += 3;
  112. // parse the input buffer directly to avoid copying it all to a string..
  113. return parseDocumentElement (String::CharPointerType (text), onlyReadOuterDocumentElement);
  114. }
  115. }
  116. }
  117. return parseDocumentElement (originalText.getCharPointer(), onlyReadOuterDocumentElement);
  118. }
  119. const String& XmlDocument::getLastParseError() const noexcept
  120. {
  121. return lastError;
  122. }
  123. void XmlDocument::setLastError (const String& desc, const bool carryOn)
  124. {
  125. lastError = desc;
  126. errorOccurred = ! carryOn;
  127. }
  128. String XmlDocument::getFileContents (const String& filename) const
  129. {
  130. if (inputSource != nullptr)
  131. {
  132. const ScopedPointer<InputStream> in (inputSource->createInputStreamFor (filename.trim().unquoted()));
  133. if (in != nullptr)
  134. return in->readEntireStreamAsString();
  135. }
  136. return String();
  137. }
  138. water_uchar XmlDocument::readNextChar() noexcept
  139. {
  140. const water_uchar c = input.getAndAdvance();
  141. if (c == 0)
  142. {
  143. outOfData = true;
  144. --input;
  145. }
  146. return c;
  147. }
  148. XmlElement* XmlDocument::parseDocumentElement (String::CharPointerType textToParse,
  149. const bool onlyReadOuterDocumentElement)
  150. {
  151. input = textToParse;
  152. errorOccurred = false;
  153. outOfData = false;
  154. needToLoadDTD = true;
  155. if (textToParse.isEmpty())
  156. {
  157. lastError = "not enough input";
  158. }
  159. else if (! parseHeader())
  160. {
  161. lastError = "malformed header";
  162. }
  163. else if (! parseDTD())
  164. {
  165. lastError = "malformed DTD";
  166. }
  167. else
  168. {
  169. lastError.clear();
  170. ScopedPointer<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement));
  171. if (! errorOccurred)
  172. return result.release();
  173. }
  174. return nullptr;
  175. }
  176. bool XmlDocument::parseHeader()
  177. {
  178. skipNextWhiteSpace();
  179. if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<?xml"), 5) == 0)
  180. {
  181. const String::CharPointerType headerEnd (CharacterFunctions::find (input, CharPointer_UTF8 ("?>")));
  182. if (headerEnd.isEmpty())
  183. return false;
  184. const String encoding (String (input, headerEnd)
  185. .fromFirstOccurrenceOf ("encoding", false, true)
  186. .fromFirstOccurrenceOf ("=", false, false)
  187. .fromFirstOccurrenceOf ("\"", false, false)
  188. .upToFirstOccurrenceOf ("\"", false, false).trim());
  189. /* If you load an XML document with a non-UTF encoding type, it may have been
  190. loaded wrongly.. Since all the files are read via the normal water file streams,
  191. they're treated as UTF-8, so by the time it gets to the parser, the encoding will
  192. have been lost. Best plan is to stick to utf-8 or if you have specific files to
  193. read, use your own code to convert them to a unicode String, and pass that to the
  194. XML parser.
  195. */
  196. CARLA_SAFE_ASSERT_RETURN (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-"), false);
  197. input = headerEnd + 2;
  198. skipNextWhiteSpace();
  199. }
  200. return true;
  201. }
  202. bool XmlDocument::parseDTD()
  203. {
  204. if (CharacterFunctions::compareUpTo (input, CharPointer_UTF8 ("<!DOCTYPE"), 9) == 0)
  205. {
  206. input += 9;
  207. const String::CharPointerType dtdStart (input);
  208. for (int n = 1; n > 0;)
  209. {
  210. const water_uchar c = readNextChar();
  211. if (outOfData)
  212. return false;
  213. if (c == '<')
  214. ++n;
  215. else if (c == '>')
  216. --n;
  217. }
  218. dtdText = String (dtdStart, input - 1).trim();
  219. }
  220. return true;
  221. }
  222. void XmlDocument::skipNextWhiteSpace()
  223. {
  224. for (;;)
  225. {
  226. input = input.findEndOfWhitespace();
  227. if (input.isEmpty())
  228. {
  229. outOfData = true;
  230. break;
  231. }
  232. if (*input == '<')
  233. {
  234. if (input[1] == '!'
  235. && input[2] == '-'
  236. && input[3] == '-')
  237. {
  238. input += 4;
  239. const int closeComment = input.indexOf (CharPointer_UTF8 ("-->"));
  240. if (closeComment < 0)
  241. {
  242. outOfData = true;
  243. break;
  244. }
  245. input += closeComment + 3;
  246. continue;
  247. }
  248. if (input[1] == '?')
  249. {
  250. input += 2;
  251. const int closeBracket = input.indexOf (CharPointer_UTF8 ("?>"));
  252. if (closeBracket < 0)
  253. {
  254. outOfData = true;
  255. break;
  256. }
  257. input += closeBracket + 2;
  258. continue;
  259. }
  260. }
  261. break;
  262. }
  263. }
  264. void XmlDocument::readQuotedString (String& result)
  265. {
  266. const water_uchar quote = readNextChar();
  267. while (! outOfData)
  268. {
  269. const water_uchar c = readNextChar();
  270. if (c == quote)
  271. break;
  272. --input;
  273. if (c == '&')
  274. {
  275. readEntity (result);
  276. }
  277. else
  278. {
  279. const String::CharPointerType start (input);
  280. for (;;)
  281. {
  282. const water_uchar character = *input;
  283. if (character == quote)
  284. {
  285. result.appendCharPointer (start, input);
  286. ++input;
  287. return;
  288. }
  289. else if (character == '&')
  290. {
  291. result.appendCharPointer (start, input);
  292. break;
  293. }
  294. else if (character == 0)
  295. {
  296. setLastError ("unmatched quotes", false);
  297. outOfData = true;
  298. break;
  299. }
  300. ++input;
  301. }
  302. }
  303. }
  304. }
  305. XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements)
  306. {
  307. XmlElement* node = nullptr;
  308. skipNextWhiteSpace();
  309. if (outOfData)
  310. return nullptr;
  311. if (*input == '<')
  312. {
  313. ++input;
  314. String::CharPointerType endOfToken (XmlIdentifierChars::findEndOfToken (input));
  315. if (endOfToken == input)
  316. {
  317. // no tag name - but allow for a gap after the '<' before giving an error
  318. skipNextWhiteSpace();
  319. endOfToken = XmlIdentifierChars::findEndOfToken (input);
  320. if (endOfToken == input)
  321. {
  322. setLastError ("tag name missing", false);
  323. return node;
  324. }
  325. }
  326. node = new XmlElement (input, endOfToken);
  327. input = endOfToken;
  328. LinkedListPointer<XmlElement::XmlAttributeNode>::Appender attributeAppender (node->attributes);
  329. // look for attributes
  330. for (;;)
  331. {
  332. skipNextWhiteSpace();
  333. const water_uchar c = *input;
  334. // empty tag..
  335. if (c == '/' && input[1] == '>')
  336. {
  337. input += 2;
  338. break;
  339. }
  340. // parse the guts of the element..
  341. if (c == '>')
  342. {
  343. ++input;
  344. if (alsoParseSubElements)
  345. readChildElements (*node);
  346. break;
  347. }
  348. // get an attribute..
  349. if (XmlIdentifierChars::isIdentifierChar (c))
  350. {
  351. String::CharPointerType attNameEnd (XmlIdentifierChars::findEndOfToken (input));
  352. if (attNameEnd != input)
  353. {
  354. const String::CharPointerType attNameStart (input);
  355. input = attNameEnd;
  356. skipNextWhiteSpace();
  357. if (readNextChar() == '=')
  358. {
  359. skipNextWhiteSpace();
  360. const water_uchar nextChar = *input;
  361. if (nextChar == '"' || nextChar == '\'')
  362. {
  363. XmlElement::XmlAttributeNode* const newAtt
  364. = new XmlElement::XmlAttributeNode (attNameStart, attNameEnd);
  365. readQuotedString (newAtt->value);
  366. attributeAppender.append (newAtt);
  367. continue;
  368. }
  369. }
  370. else
  371. {
  372. setLastError ("expected '=' after attribute '"
  373. + String (attNameStart, attNameEnd) + "'", false);
  374. return node;
  375. }
  376. }
  377. }
  378. else
  379. {
  380. if (! outOfData)
  381. setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false);
  382. }
  383. break;
  384. }
  385. }
  386. return node;
  387. }
  388. void XmlDocument::readChildElements (XmlElement& parent)
  389. {
  390. LinkedListPointer<XmlElement>::Appender childAppender (parent.firstChildElement);
  391. for (;;)
  392. {
  393. const String::CharPointerType preWhitespaceInput (input);
  394. skipNextWhiteSpace();
  395. if (outOfData)
  396. {
  397. setLastError ("unmatched tags", false);
  398. break;
  399. }
  400. if (*input == '<')
  401. {
  402. const water_uchar c1 = input[1];
  403. if (c1 == '/')
  404. {
  405. // our close tag..
  406. const int closeTag = input.indexOf ((water_uchar) '>');
  407. if (closeTag >= 0)
  408. input += closeTag + 1;
  409. break;
  410. }
  411. if (c1 == '!' && CharacterFunctions::compareUpTo (input + 2, CharPointer_UTF8 ("[CDATA["), 7) == 0)
  412. {
  413. input += 9;
  414. const String::CharPointerType inputStart (input);
  415. for (;;)
  416. {
  417. const water_uchar c0 = *input;
  418. if (c0 == 0)
  419. {
  420. setLastError ("unterminated CDATA section", false);
  421. outOfData = true;
  422. break;
  423. }
  424. else if (c0 == ']'
  425. && input[1] == ']'
  426. && input[2] == '>')
  427. {
  428. childAppender.append (XmlElement::createTextElement (String (inputStart, input)));
  429. input += 3;
  430. break;
  431. }
  432. ++input;
  433. }
  434. }
  435. else
  436. {
  437. // this is some other element, so parse and add it..
  438. if (XmlElement* const n = readNextElement (true))
  439. childAppender.append (n);
  440. else
  441. break;
  442. }
  443. }
  444. else // must be a character block
  445. {
  446. input = preWhitespaceInput; // roll back to include the leading whitespace
  447. MemoryOutputStream textElementContent;
  448. bool contentShouldBeUsed = ! ignoreEmptyTextElements;
  449. for (;;)
  450. {
  451. const water_uchar c = *input;
  452. if (c == '<')
  453. {
  454. if (input[1] == '!' && input[2] == '-' && input[3] == '-')
  455. {
  456. input += 4;
  457. const int closeComment = input.indexOf (CharPointer_UTF8 ("-->"));
  458. if (closeComment < 0)
  459. {
  460. setLastError ("unterminated comment", false);
  461. outOfData = true;
  462. return;
  463. }
  464. input += closeComment + 3;
  465. continue;
  466. }
  467. break;
  468. }
  469. if (c == 0)
  470. {
  471. setLastError ("unmatched tags", false);
  472. outOfData = true;
  473. return;
  474. }
  475. if (c == '&')
  476. {
  477. String entity;
  478. readEntity (entity);
  479. if (entity.startsWithChar ('<') && entity [1] != 0)
  480. {
  481. const String::CharPointerType oldInput (input);
  482. const bool oldOutOfData = outOfData;
  483. input = entity.getCharPointer();
  484. outOfData = false;
  485. while (XmlElement* n = readNextElement (true))
  486. childAppender.append (n);
  487. input = oldInput;
  488. outOfData = oldOutOfData;
  489. }
  490. else
  491. {
  492. textElementContent << entity;
  493. contentShouldBeUsed = contentShouldBeUsed || entity.containsNonWhitespaceChars();
  494. }
  495. }
  496. else
  497. {
  498. for (;; ++input)
  499. {
  500. water_uchar nextChar = *input;
  501. if (nextChar == '\r')
  502. {
  503. nextChar = '\n';
  504. if (input[1] == '\n')
  505. continue;
  506. }
  507. if (nextChar == '<' || nextChar == '&')
  508. break;
  509. if (nextChar == 0)
  510. {
  511. setLastError ("unmatched tags", false);
  512. outOfData = true;
  513. return;
  514. }
  515. textElementContent.appendUTF8Char (nextChar);
  516. contentShouldBeUsed = contentShouldBeUsed || ! CharacterFunctions::isWhitespace (nextChar);
  517. }
  518. }
  519. }
  520. if (contentShouldBeUsed)
  521. childAppender.append (XmlElement::createTextElement (textElementContent.toUTF8()));
  522. }
  523. }
  524. }
  525. void XmlDocument::readEntity (String& result)
  526. {
  527. // skip over the ampersand
  528. ++input;
  529. if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("amp;"), 4) == 0)
  530. {
  531. input += 4;
  532. result += '&';
  533. }
  534. else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("quot;"), 5) == 0)
  535. {
  536. input += 5;
  537. result += '"';
  538. }
  539. else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("apos;"), 5) == 0)
  540. {
  541. input += 5;
  542. result += '\'';
  543. }
  544. else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("lt;"), 3) == 0)
  545. {
  546. input += 3;
  547. result += '<';
  548. }
  549. else if (input.compareIgnoreCaseUpTo (CharPointer_UTF8 ("gt;"), 3) == 0)
  550. {
  551. input += 3;
  552. result += '>';
  553. }
  554. else if (*input == '#')
  555. {
  556. int charCode = 0;
  557. ++input;
  558. if (*input == 'x' || *input == 'X')
  559. {
  560. ++input;
  561. int numChars = 0;
  562. while (input[0] != ';')
  563. {
  564. const int hexValue = CharacterFunctions::getHexDigitValue (input[0]);
  565. if (hexValue < 0 || ++numChars > 8)
  566. {
  567. setLastError ("illegal escape sequence", true);
  568. break;
  569. }
  570. charCode = (charCode << 4) | hexValue;
  571. ++input;
  572. }
  573. ++input;
  574. }
  575. else if (input[0] >= '0' && input[0] <= '9')
  576. {
  577. int numChars = 0;
  578. while (input[0] != ';')
  579. {
  580. if (++numChars > 12)
  581. {
  582. setLastError ("illegal escape sequence", true);
  583. break;
  584. }
  585. charCode = charCode * 10 + ((int) input[0] - '0');
  586. ++input;
  587. }
  588. ++input;
  589. }
  590. else
  591. {
  592. setLastError ("illegal escape sequence", true);
  593. result += '&';
  594. return;
  595. }
  596. result << (water_uchar) charCode;
  597. }
  598. else
  599. {
  600. const String::CharPointerType entityNameStart (input);
  601. const int closingSemiColon = input.indexOf ((water_uchar) ';');
  602. if (closingSemiColon < 0)
  603. {
  604. outOfData = true;
  605. result += '&';
  606. }
  607. else
  608. {
  609. input += closingSemiColon + 1;
  610. result += expandExternalEntity (String (entityNameStart, (size_t) closingSemiColon));
  611. }
  612. }
  613. }
  614. String XmlDocument::expandEntity (const String& ent)
  615. {
  616. if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&');
  617. if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"');
  618. if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\'');
  619. if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<');
  620. if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>');
  621. if (ent[0] == '#')
  622. {
  623. const water_uchar char1 = ent[1];
  624. if (char1 == 'x' || char1 == 'X')
  625. return String::charToString (static_cast<water_uchar> (ent.substring (2).getHexValue32()));
  626. if (char1 >= '0' && char1 <= '9')
  627. return String::charToString (static_cast<water_uchar> (ent.substring (1).getIntValue()));
  628. setLastError ("illegal escape sequence", false);
  629. return String::charToString ('&');
  630. }
  631. return expandExternalEntity (ent);
  632. }
  633. String XmlDocument::expandExternalEntity (const String& entity)
  634. {
  635. if (needToLoadDTD)
  636. {
  637. if (dtdText.isNotEmpty())
  638. {
  639. dtdText = dtdText.trimCharactersAtEnd (">");
  640. tokenisedDTD.addTokens (dtdText, true);
  641. if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system")
  642. && tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString())
  643. {
  644. const String fn (tokenisedDTD [tokenisedDTD.size() - 1]);
  645. tokenisedDTD.clear();
  646. tokenisedDTD.addTokens (getFileContents (fn), true);
  647. }
  648. else
  649. {
  650. tokenisedDTD.clear();
  651. const int openBracket = dtdText.indexOfChar ('[');
  652. if (openBracket > 0)
  653. {
  654. const int closeBracket = dtdText.lastIndexOfChar (']');
  655. if (closeBracket > openBracket)
  656. tokenisedDTD.addTokens (dtdText.substring (openBracket + 1,
  657. closeBracket), true);
  658. }
  659. }
  660. for (int i = tokenisedDTD.size(); --i >= 0;)
  661. {
  662. if (tokenisedDTD[i].startsWithChar ('%')
  663. && tokenisedDTD[i].endsWithChar (';'))
  664. {
  665. const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1)));
  666. StringArray newToks;
  667. newToks.addTokens (parsed, true);
  668. tokenisedDTD.remove (i);
  669. for (int j = newToks.size(); --j >= 0;)
  670. tokenisedDTD.insert (i, newToks[j]);
  671. }
  672. }
  673. }
  674. needToLoadDTD = false;
  675. }
  676. for (int i = 0; i < tokenisedDTD.size(); ++i)
  677. {
  678. if (tokenisedDTD[i] == entity)
  679. {
  680. if (tokenisedDTD[i - 1].equalsIgnoreCase ("<!entity"))
  681. {
  682. String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">").trim().unquoted());
  683. // check for sub-entities..
  684. int ampersand = ent.indexOfChar ('&');
  685. while (ampersand >= 0)
  686. {
  687. const int semiColon = ent.indexOf (i + 1, ";");
  688. if (semiColon < 0)
  689. {
  690. setLastError ("entity without terminating semi-colon", false);
  691. break;
  692. }
  693. const String resolved (expandEntity (ent.substring (i + 1, semiColon)));
  694. ent = ent.substring (0, ampersand)
  695. + resolved
  696. + ent.substring (semiColon + 1);
  697. ampersand = ent.indexOfChar (semiColon + 1, '&');
  698. }
  699. return ent;
  700. }
  701. }
  702. }
  703. setLastError ("unknown entity", true);
  704. return entity;
  705. }
  706. String XmlDocument::getParameterEntity (const String& entity)
  707. {
  708. for (int i = 0; i < tokenisedDTD.size(); ++i)
  709. {
  710. if (tokenisedDTD[i] == entity
  711. && tokenisedDTD [i - 1] == "%"
  712. && tokenisedDTD [i - 2].equalsIgnoreCase ("<!entity"))
  713. {
  714. const String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">"));
  715. if (ent.equalsIgnoreCase ("system"))
  716. return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">"));
  717. return ent.trim().unquoted();
  718. }
  719. }
  720. return entity;
  721. }
  722. }