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.

896 lines
26KB

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