1. /* This file is part of NanoXML.
  2. *
  3. * $Revision: 1.1 $
  4. * $Date: 2007/05/09 15:54:52 $
  5. * $Name: $
  6. *
  7. * Copyright (C) 2000 Marc De Scheemaecker, All Rights Reserved.
  8. *
  9. * This software is provided 'as-is', without any express or implied warranty.
  10. * In no event will the authors be held liable for any damages arising from the
  11. * use of this software.
  12. *
  13. * Permission is granted to anyone to use this software for any purpose,
  14. * including commercial applications, and to alter it and redistribute it
  15. * freely, subject to the following restrictions:
  16. *
  17. * 1. The origin of this software must not be misrepresented; you must not
  18. * claim that you wrote the original software. If you use this software in
  19. * a product, an acknowledgment in the product documentation would be
  20. * appreciated but is not required.
  21. *
  22. * 2. Altered source versions must be plainly marked as such, and must not be
  23. * misrepresented as being the original software.
  24. *
  25. * 3. This notice may not be removed or altered from any source distribution.
  26. */
  27. package nanoxml;
  28. import java.io.IOException;
  29. import java.io.Reader;
  30. import java.io.Writer;
  31. import java.util.Enumeration;
  32. import java.util.Hashtable;
  33. import java.util.Vector;
  34. /**
  35. * kXMLElement is a representation of an XML object. The object is able to parse
  36. * XML code.
  37. * <P>
  38. * Note that NanoXML is not 100% XML 1.0 compliant:
  39. * <UL><LI>The parser is non-validating.
  40. * <LI>The DTD is fully ignored, including <CODE><!ENTITY...></CODE>.
  41. * <LI>There is no support for mixed content (elements containing both
  42. * subelements and CDATA elements)
  43. * </UL>
  44. * <P>
  45. * You can opt to use a SAX compatible API, by including both
  46. * <CODE>nanoxml.jar</CODE> and <CODE>nanoxml-sax.jar</CODE> in your classpath
  47. * and setting the property <CODE>org.xml.sax.parser</CODE> to
  48. * <CODE>nanoxml.sax.SAXParser</CODE>
  49. * <P>
  50. * $Revision: 1.1 $<BR>
  51. * $Date: 2007/05/09 15:54:52 $<P>
  52. *
  53. * @see nanoxml.XMLParseException
  54. *
  55. * @author Marc De Scheemaecker
  56. * <<A HREF="mailto:Marc.DeScheemaecker@advalvas.be"
  57. * >Marc.DeScheemaecker@advalvas.be</A>>
  58. * @version 1.0.7
  59. */
  60. public class kXMLElement
  61. {
  62. /**
  63. * Major version of NanoXML.
  64. */
  65. public static final int NANOXML_MAJOR_VERSION = 1;
  66. /**
  67. * Minor version of NanoXML.
  68. */
  69. public static final int NANOXML_MINOR_VERSION = 6;
  70. /**
  71. * The attributes given to the object.
  72. */
  73. private FakeProperties attributes;
  74. /**
  75. * Subobjects of the object. The subobjects are of class kXMLElement
  76. * themselves.
  77. */
  78. private Vector children;
  79. /**
  80. * The class of the object (the name indicated in the tag).
  81. */
  82. private String tagName;
  83. /**
  84. * The #PCDATA content of the object. If there is no such content, this
  85. * field is null.
  86. */
  87. private String contents;
  88. /**
  89. * Conversion table for &...; tags.
  90. */
  91. private FakeProperties conversionTable;
  92. /**
  93. * Whether to skip leading whitespace in CDATA.
  94. */
  95. private boolean skipLeadingWhitespace;
  96. /**
  97. * The line number where the element starts.
  98. */
  99. private int lineNr;
  100. /**
  101. * Whether the parsing is case sensitive.
  102. */
  103. private boolean ignoreCase;
  104. /**
  105. * Creates a new XML element. The following settings are used:
  106. * <DL><DT>Conversion table</DT>
  107. * <DD>Minimal XML conversions: <CODE>&amp; &lt; &gt;
  108. * &apos; &quot;</CODE></DD>
  109. * <DT>Skip whitespace in contents</DT>
  110. * <DD><CODE>false</CODE></DD>
  111. * <DT>Ignore Case</DT>
  112. * <DD><CODE>true</CODE></DD>
  113. * </DL>
  114. *
  115. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties)
  116. * @see nanoxml.kXMLElement#kXMLElement(boolean)
  117. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties,boolean)
  118. */
  119. public kXMLElement()
  120. {
  121. this(new FakeProperties(), false, true, true);
  122. }
  123. /**
  124. * Creates a new XML element. The following settings are used:
  125. * <DL><DT>Conversion table</DT>
  126. * <DD><I>conversionTable</I> combined with the minimal XML
  127. * conversions: <CODE>&amp; &lt; &gt;
  128. * &apos; &quot;</CODE></DD>
  129. * <DT>Skip whitespace in contents</DT>
  130. * <DD><CODE>false</CODE></DD>
  131. * <DT>Ignore Case</DT>
  132. * <DD><CODE>true</CODE></DD>
  133. * </DL>
  134. *
  135. * @see nanoxml.kXMLElement#kXMLElement()
  136. * @see nanoxml.kXMLElement#kXMLElement(boolean)
  137. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties,boolean)
  138. */
  139. public kXMLElement(FakeProperties conversionTable)
  140. {
  141. this(conversionTable, false, true, true);
  142. }
  143. /**
  144. * Creates a new XML element. The following settings are used:
  145. * <DL><DT>Conversion table</DT>
  146. * <DD>Minimal XML conversions: <CODE>&amp; &lt; &gt;
  147. * &apos; &quot;</CODE></DD>
  148. * <DT>Skip whitespace in contents</DT>
  149. * <DD><I>skipLeadingWhitespace</I></DD>
  150. * <DT>Ignore Case</DT>
  151. * <DD><CODE>true</CODE></DD>
  152. * </DL>
  153. *
  154. * @see nanoxml.kXMLElement#kXMLElement()
  155. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties)
  156. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties,boolean)
  157. */
  158. public kXMLElement(boolean skipLeadingWhitespace)
  159. {
  160. this(new FakeProperties(), skipLeadingWhitespace, true, true);
  161. }
  162. /**
  163. * Creates a new XML element. The following settings are used:
  164. * <DL><DT>Conversion table</DT>
  165. * <DD><I>conversionTable</I> combined with the minimal XML
  166. * conversions: <CODE>&amp; &lt; &gt;
  167. * &apos; &quot;</CODE></DD>
  168. * <DT>Skip whitespace in contents</DT>
  169. * <DD><I>skipLeadingWhitespace</I></DD>
  170. * <DT>Ignore Case</DT>
  171. * <DD><CODE>true</CODE></DD>
  172. * </DL>
  173. *
  174. * @see nanoxml.kXMLElement#kXMLElement()
  175. * @see nanoxml.kXMLElement#kXMLElement(boolean)
  176. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties)
  177. */
  178. public kXMLElement(FakeProperties conversionTable,
  179. boolean skipLeadingWhitespace)
  180. {
  181. this(conversionTable, skipLeadingWhitespace, true, true);
  182. }
  183. /**
  184. * Creates a new XML element. The following settings are used:
  185. * <DL><DT>Conversion table</DT>
  186. * <DD><I>conversionTable</I>, eventually combined with the minimal XML
  187. * conversions: <CODE>&amp; &lt; &gt;
  188. * &apos; &quot;</CODE>
  189. * (depending on <I>fillBasicConversionTable</I>)</DD>
  190. * <DT>Skip whitespace in contents</DT>
  191. * <DD><I>skipLeadingWhitespace</I></DD>
  192. * <DT>Ignore Case</DT>
  193. * <DD><I>ignoreCase</I></DD>
  194. * </DL>
  195. * <P>
  196. * This constructor should <I>only</I> be called from kXMLElement itself
  197. * to create child elements.
  198. *
  199. * @see nanoxml.kXMLElement#kXMLElement()
  200. * @see nanoxml.kXMLElement#kXMLElement(boolean)
  201. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties)
  202. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties,boolean)
  203. */
  204. public kXMLElement(FakeProperties conversionTable,
  205. boolean skipLeadingWhitespace,
  206. boolean ignoreCase)
  207. {
  208. this(conversionTable, skipLeadingWhitespace, true, ignoreCase);
  209. }
  210. /**
  211. * Creates a new XML element. The following settings are used:
  212. * <DL><DT>Conversion table</DT>
  213. * <DD><I>conversionTable</I>, eventually combined with the minimal XML
  214. * conversions: <CODE>&amp; &lt; &gt;
  215. * &apos; &quot;</CODE>
  216. * (depending on <I>fillBasicConversionTable</I>)</DD>
  217. * <DT>Skip whitespace in contents</DT>
  218. * <DD><I>skipLeadingWhitespace</I></DD>
  219. * <DT>Ignore Case</DT>
  220. * <DD><I>ignoreCase</I></DD>
  221. * </DL>
  222. * <P>
  223. * This constructor should <I>only</I> be called from kXMLElement itself
  224. * to create child elements.
  225. *
  226. * @see nanoxml.kXMLElement#kXMLElement()
  227. * @see nanoxml.kXMLElement#kXMLElement(boolean)
  228. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties)
  229. * @see nanoxml.kXMLElement#kXMLElement(FakeProperties,boolean)
  230. */
  231. protected kXMLElement(FakeProperties conversionTable,
  232. boolean skipLeadingWhitespace,
  233. boolean fillBasicConversionTable,
  234. boolean ignoreCase)
  235. {
  236. this.ignoreCase = ignoreCase;
  237. this.skipLeadingWhitespace = skipLeadingWhitespace;
  238. this.tagName = null;
  239. this.contents = "";
  240. this.attributes = new FakeProperties();
  241. this.children = new Vector();
  242. this.conversionTable = conversionTable;
  243. this.lineNr = 0;
  244. if (fillBasicConversionTable)
  245. {
  246. this.conversionTable.put("lt", "<");
  247. this.conversionTable.put("gt", ">");
  248. this.conversionTable.put("quot", "\"");
  249. this.conversionTable.put("apos", "'");
  250. this.conversionTable.put("amp", "&");
  251. }
  252. }
  253. /**
  254. * Adds a subobject.
  255. */
  256. public void addChild(kXMLElement child)
  257. {
  258. this.children.addElement(child);
  259. }
  260. /**
  261. * Adds a property.
  262. * If the element is case insensitive, the property name is capitalized.
  263. */
  264. public void addProperty(String key,
  265. Object value)
  266. {
  267. if (this.ignoreCase)
  268. {
  269. key = key.toUpperCase();
  270. }
  271. this.attributes.put(key, value.toString());
  272. }
  273. /**
  274. * Adds a property.
  275. * If the element is case insensitive, the property name is capitalized.
  276. */
  277. public void addProperty(String key,
  278. int value)
  279. {
  280. if (this.ignoreCase)
  281. {
  282. key = key.toUpperCase();
  283. }
  284. this.attributes.put(key, Integer.toString(value));
  285. }
  286. /**
  287. * Returns the number of subobjects of the object.
  288. */
  289. public int countChildren()
  290. {
  291. return this.children.size();
  292. }
  293. /**
  294. * Enumerates the attribute names.
  295. */
  296. public Enumeration enumeratePropertyNames()
  297. {
  298. return this.attributes.keys();
  299. }
  300. /**
  301. * Enumerates the subobjects of the object.
  302. */
  303. public Enumeration enumerateChildren()
  304. {
  305. return this.children.elements();
  306. }
  307. /**
  308. * Returns the subobjects of the object.
  309. */
  310. public Vector getChildren()
  311. {
  312. return this.children;
  313. }
  314. /**
  315. * Returns the #PCDATA content of the object. If there is no such content,
  316. * <CODE>null</CODE> is returned.
  317. */
  318. public String getContents()
  319. {
  320. return this.contents;
  321. }
  322. /**
  323. * Returns the line nr on which the element is found.
  324. */
  325. public int getLineNr()
  326. {
  327. return this.lineNr;
  328. }
  329. /**
  330. * Returns a property by looking up a key in a hashtable.
  331. * If the property doesn't exist, the value corresponding to defaultValue
  332. * is returned.
  333. */
  334. public int getIntProperty(String key,
  335. Hashtable valueSet,
  336. String defaultValue)
  337. {
  338. String val = this.attributes.getProperty(key);
  339. Integer result;
  340. if (this.ignoreCase)
  341. {
  342. key = key.toUpperCase();
  343. }
  344. if (val == null)
  345. {
  346. val = defaultValue;
  347. }
  348. try
  349. {
  350. result = (Integer)(valueSet.get(val));
  351. }
  352. catch (ClassCastException e)
  353. {
  354. throw this.invalidValueSet(key);
  355. }
  356. if (result == null)
  357. {
  358. throw this.invalidValue(key, val, this.lineNr);
  359. }
  360. return result.intValue();
  361. }
  362. /**
  363. * Returns a property of the object. If there is no such property, this
  364. * method returns <CODE>null</CODE>.
  365. */
  366. public String getProperty(String key)
  367. {
  368. if (this.ignoreCase)
  369. {
  370. key = key.toUpperCase();
  371. }
  372. return this.attributes.getProperty(key);
  373. }
  374. /**
  375. * Returns a property of the object.
  376. * If the property doesn't exist, <I>defaultValue</I> is returned.
  377. */
  378. public String getProperty(String key,
  379. String defaultValue)
  380. {
  381. if (this.ignoreCase)
  382. {
  383. key = key.toUpperCase();
  384. }
  385. return this.attributes.getProperty(key, defaultValue);
  386. }
  387. /**
  388. * Returns an integer property of the object.
  389. * If the property doesn't exist, <I>defaultValue</I> is returned.
  390. */
  391. public int getProperty(String key,
  392. int defaultValue)
  393. {
  394. if (this.ignoreCase)
  395. {
  396. key = key.toUpperCase();
  397. }
  398. String val = this.attributes.getProperty(key);
  399. if (val == null)
  400. {
  401. return defaultValue;
  402. }
  403. else
  404. {
  405. try
  406. {
  407. return Integer.parseInt(val);
  408. }
  409. catch (NumberFormatException e)
  410. {
  411. throw this.invalidValue(key, val, this.lineNr);
  412. }
  413. }
  414. }
  415. /**
  416. * Returns a boolean property of the object. If the property is missing,
  417. * <I>defaultValue</I> is returned.
  418. */
  419. public boolean getProperty(String key,
  420. String trueValue,
  421. String falseValue,
  422. boolean defaultValue)
  423. {
  424. if (this.ignoreCase)
  425. {
  426. key = key.toUpperCase();
  427. }
  428. String val = this.attributes.getProperty(key);
  429. if (val == null)
  430. {
  431. return defaultValue;
  432. }
  433. else if (val.equals(trueValue))
  434. {
  435. return true;
  436. }
  437. else if (val.equals(falseValue))
  438. {
  439. return false;
  440. }
  441. else
  442. {
  443. throw this.invalidValue(key, val, this.lineNr);
  444. }
  445. }
  446. /**
  447. * Returns a property by looking up a key in the hashtable <I>valueSet</I>
  448. * If the property doesn't exist, the value corresponding to
  449. * <I>defaultValue</I> is returned.
  450. */
  451. public Object getProperty(String key,
  452. Hashtable valueSet,
  453. String defaultValue)
  454. {
  455. if (this.ignoreCase)
  456. {
  457. key = key.toUpperCase();
  458. }
  459. String val = this.attributes.getProperty(key);
  460. if (val == null)
  461. {
  462. val = defaultValue;
  463. }
  464. Object result = valueSet.get(val);
  465. if (result == null)
  466. {
  467. throw this.invalidValue(key, val, this.lineNr);
  468. }
  469. return result;
  470. }
  471. /**
  472. * Returns a property by looking up a key in the hashtable <I>valueSet</I>.
  473. * If the property doesn't exist, the value corresponding to
  474. * <I>defaultValue</I> is returned.
  475. */
  476. public String getStringProperty(String key,
  477. Hashtable valueSet,
  478. String defaultValue)
  479. {
  480. if (this.ignoreCase)
  481. {
  482. key = key.toUpperCase();
  483. }
  484. String val = this.attributes.getProperty(key);
  485. String result;
  486. if (val == null)
  487. {
  488. val = defaultValue;
  489. }
  490. try
  491. {
  492. result = (String)(valueSet.get(val));
  493. }
  494. catch (ClassCastException e)
  495. {
  496. throw this.invalidValueSet(key);
  497. }
  498. if (result == null)
  499. {
  500. throw this.invalidValue(key, val, this.lineNr);
  501. }
  502. return result;
  503. }
  504. /**
  505. * Returns a property by looking up a key in the hashtable <I>valueSet</I>.
  506. * If the value is not defined in the hashtable, the value is considered to
  507. * be an integer.
  508. * If the property doesn't exist, the value corresponding to
  509. * <I>defaultValue</I> is returned.
  510. */
  511. public int getSpecialIntProperty(String key,
  512. Hashtable valueSet,
  513. String defaultValue)
  514. {
  515. if (this.ignoreCase)
  516. {
  517. key = key.toUpperCase();
  518. }
  519. String val = this.attributes.getProperty(key);
  520. Integer result;
  521. if (val == null)
  522. {
  523. val = defaultValue;
  524. }
  525. try
  526. {
  527. result = (Integer)(valueSet.get(val));
  528. }
  529. catch (ClassCastException e)
  530. {
  531. throw this.invalidValueSet(key);
  532. }
  533. if (result == null)
  534. {
  535. try
  536. {
  537. return Integer.parseInt(val);
  538. }
  539. catch (NumberFormatException e)
  540. {
  541. throw this.invalidValue(key, val, this.lineNr);
  542. }
  543. }
  544. return result.intValue();
  545. }
  546. /**
  547. * Returns the class (i.e. the name indicated in the tag) of the object.
  548. */
  549. public String getTagName()
  550. {
  551. return this.tagName;
  552. }
  553. /**
  554. * Checks whether a character may be part of an identifier.
  555. */
  556. private boolean isIdentifierChar(char ch)
  557. {
  558. return (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z'))
  559. || ((ch >= '0') && (ch <= '9')) || (".-_:".indexOf(ch) >= 0));
  560. }
  561. /**
  562. * Reads an XML definition from a java.io.Reader and parses it.
  563. *
  564. * @exception java.io.IOException
  565. * if an error occured while reading the input
  566. * @exception nanoxml.XMLParseException
  567. * if an error occured while parsing the read data
  568. */
  569. public void parseFromReader(Reader reader)
  570. throws IOException, XMLParseException
  571. {
  572. this.parseFromReader(reader, 1);
  573. }
  574. /**
  575. * Reads an XML definition from a java.io.Reader and parses it.
  576. *
  577. * @exception java.io.IOException
  578. * if an error occured while reading the input
  579. * @exception nanoxml.XMLParseException
  580. * if an error occured while parsing the read data
  581. */
  582. public void parseFromReader(Reader reader,
  583. int startingLineNr)
  584. throws IOException, XMLParseException
  585. {
  586. int blockSize = 4096;
  587. char[] input = null;
  588. int size = 0;
  589. for (;;)
  590. {
  591. if (input == null)
  592. {
  593. input = new char[blockSize];
  594. }
  595. else
  596. {
  597. char[] oldInput = input;
  598. input = new char[input.length + blockSize];
  599. System.arraycopy(oldInput, 0, input, 0, oldInput.length);
  600. }
  601. int charsRead = reader.read(input, size, blockSize);
  602. if (charsRead < 0)
  603. {
  604. break;
  605. }
  606. size += charsRead;
  607. }
  608. this.parseCharArray(input, 0, size, startingLineNr);
  609. }
  610. /**
  611. * Parses an XML definition.
  612. *
  613. * @exception nanoxml.XMLParseException
  614. * if an error occured while parsing the string
  615. */
  616. public void parseString(String string)
  617. throws XMLParseException
  618. {
  619. this.parseCharArray(string.toCharArray(), 0, string.length(), 1);
  620. }
  621. /**
  622. * Parses an XML definition starting at <I>offset</I>.
  623. *
  624. * @return the offset of the string following the XML data
  625. *
  626. * @exception nanoxml.XMLParseException
  627. * if an error occured while parsing the string
  628. */
  629. public int parseString(String string,
  630. int offset)
  631. throws XMLParseException
  632. {
  633. return this.parseCharArray(string.toCharArray(), offset,
  634. string.length(), 1);
  635. }
  636. /**
  637. * Parses an XML definition starting at <I>offset</I>.
  638. *
  639. * @return the offset of the string following the XML data (<= end)
  640. *
  641. * @exception nanoxml.XMLParseException
  642. * if an error occured while parsing the string
  643. */
  644. public int parseString(String string,
  645. int offset,
  646. int end)
  647. throws XMLParseException
  648. {
  649. return this.parseCharArray(string.toCharArray(), offset, end, 1);
  650. }
  651. /**
  652. * Parses an XML definition starting at <I>offset</I>.
  653. *
  654. * @return the offset of the string following the XML data (<= end)
  655. *
  656. * @exception nanoxml.XMLParseException
  657. * if an error occured while parsing the string
  658. */
  659. public int parseString(String string,
  660. int offset,
  661. int end,
  662. int startingLineNr)
  663. throws XMLParseException
  664. {
  665. return this.parseCharArray(string.toCharArray(), offset, end,
  666. startingLineNr);
  667. }
  668. /**
  669. * Parses an XML definition starting at <I>offset</I>.
  670. *
  671. * @return the offset of the array following the XML data (<= end)
  672. *
  673. * @exception nanoxml.XMLParseException
  674. * if an error occured while parsing the array
  675. */
  676. public int parseCharArray(char[] input,
  677. int offset,
  678. int end)
  679. throws XMLParseException
  680. {
  681. return this.parseCharArray(input, offset, end, 1);
  682. }
  683. /**
  684. * Parses an XML definition starting at <I>offset</I>.
  685. *
  686. * @return the offset of the array following the XML data (<= end)
  687. *
  688. * @exception nanoxml.XMLParseException
  689. * if an error occured while parsing the array
  690. */
  691. public int parseCharArray(char[] input,
  692. int offset,
  693. int end,
  694. int startingLineNr)
  695. throws XMLParseException
  696. {
  697. int[] lineNr = new int[1];
  698. lineNr[0] = startingLineNr;
  699. return this.parseCharArray(input, offset, end, lineNr);
  700. }
  701. /**
  702. * Parses an XML definition starting at <I>offset</I>.
  703. *
  704. * @return the offset of the array following the XML data (<= end)
  705. *
  706. * @exception nanoxml.XMLParseException
  707. * if an error occured while parsing the array
  708. */
  709. private int parseCharArray(char[] input,
  710. int offset,
  711. int end,
  712. int[] currentLineNr)
  713. throws XMLParseException
  714. {
  715. this.lineNr = currentLineNr[0];
  716. this.tagName = null;
  717. this.contents = "";
  718. this.attributes = new FakeProperties();
  719. this.children = new Vector();
  720. try
  721. {
  722. offset = this.skipWhitespace(input, offset, end, currentLineNr);
  723. }
  724. catch (XMLParseException e)
  725. {
  726. return offset;
  727. }
  728. offset = this.skipPreamble(input, offset, end, currentLineNr);
  729. offset = this.scanTagName(input, offset, end, currentLineNr);
  730. this.lineNr = currentLineNr[0];
  731. offset = this.scanAttributes(input, offset, end, currentLineNr);
  732. int[] contentOffset = new int[1];
  733. int[] contentSize = new int[1];
  734. int contentLineNr = currentLineNr[0];
  735. offset = this.scanContent(input, offset, end,
  736. contentOffset, contentSize, currentLineNr);
  737. if (contentSize[0] > 0)
  738. {
  739. this.scanChildren(input, contentOffset[0], contentSize[0],
  740. contentLineNr);
  741. if (this.children.size() > 0)
  742. {
  743. this.contents = null;
  744. }
  745. else
  746. {
  747. this.processContents(input, contentOffset[0],
  748. contentSize[0], contentLineNr);
  749. }
  750. }
  751. return offset;
  752. }
  753. /**
  754. * Decodes the entities in the contents and, if skipLeadingWhitespace is
  755. * <CODE>true</CODE>, removes extraneous whitespaces after newlines and
  756. * convert those newlines into spaces.
  757. *
  758. * @see nanoxml.kXMLElement#decodeString
  759. *
  760. * @exception nanoxml.XMLParseException
  761. * if an error occured while parsing the array
  762. */
  763. private void processContents(char[] input,
  764. int contentOffset,
  765. int contentSize,
  766. int contentLineNr)
  767. throws XMLParseException
  768. {
  769. int[] lineNr = new int[1];
  770. lineNr[0] = contentLineNr;
  771. if (! this.skipLeadingWhitespace)
  772. {
  773. String str = new String(input, contentOffset, contentSize);
  774. this.contents = this.decodeString(str, lineNr[0]);
  775. return;
  776. }
  777. StringBuffer result = new StringBuffer(contentSize);
  778. int end = contentSize + contentOffset;
  779. for (int i = contentOffset; i < end; i++)
  780. {
  781. char ch = input[i];
  782. // The end of the contents is always a < character, so there's
  783. // no danger for bounds violation
  784. while ((ch == '\r') || (ch == '\n'))
  785. {
  786. lineNr[0]++;
  787. result.append(ch);
  788. i++;
  789. ch = input[i];
  790. if (ch != '\n')
  791. {
  792. result.append(ch);
  793. }
  794. do
  795. {
  796. i++;
  797. ch = input[i];
  798. } while ((ch == ' ') || (ch == '\t'));
  799. }
  800. if (i < end)
  801. {
  802. result.append(ch);
  803. }
  804. }
  805. this.contents = this.decodeString(result.toString(), lineNr[0]);
  806. }
  807. /**
  808. * Removes a child object. If the object is not a child, nothing happens.
  809. */
  810. public void removeChild(kXMLElement child)
  811. {
  812. this.children.removeElement(child);
  813. }
  814. /**
  815. * Removes an attribute.
  816. */
  817. public void removeChild(String key)
  818. {
  819. if (this.ignoreCase)
  820. {
  821. key = key.toUpperCase();
  822. }
  823. this.attributes.remove(key);
  824. }
  825. /**
  826. * Scans the attributes of the object.
  827. *
  828. * @return the offset in the string following the attributes, so that
  829. * input[offset] in { '/', '>' }
  830. *
  831. * @see nanoxml.kXMLElement#scanOneAttribute
  832. *
  833. * @exception nanoxml.XMLParseException
  834. * if an error occured while parsing the array
  835. */
  836. private int scanAttributes(char[] input,
  837. int offset,
  838. int end,
  839. int[] lineNr)
  840. throws XMLParseException
  841. {
  842. String key, value;
  843. for (;;)
  844. {
  845. offset = this.skipWhitespace(input, offset, end, lineNr);
  846. char ch = input[offset];
  847. if ((ch == '/') || (ch == '>'))
  848. {
  849. break;
  850. }
  851. offset = this.scanOneAttribute(input, offset, end, lineNr);
  852. }
  853. return offset;
  854. }
  855. /**!!!
  856. * Searches the content for child objects. If such objects exist, the
  857. * content is reduced to <CODE>null</CODE>.
  858. *
  859. * @see nanoxml.kXMLElement#parseCharArray
  860. *
  861. * @exception nanoxml.XMLParseException
  862. * if an error occured while parsing the array
  863. */
  864. protected void scanChildren(char[] input,
  865. int contentOffset,
  866. int contentSize,
  867. int contentLineNr)
  868. throws XMLParseException
  869. {
  870. int end = contentOffset + contentSize;
  871. int offset = contentOffset;
  872. int lineNr[] = new int[1];
  873. lineNr[0] = contentLineNr;
  874. while (offset < end)
  875. {
  876. try
  877. {
  878. offset = this.skipWhitespace(input, offset, end, lineNr);
  879. }
  880. catch (XMLParseException e)
  881. {
  882. return;
  883. }
  884. if ((input[offset] != '<')
  885. || ((input[offset + 1] == '!') && (input[offset + 2] == '[')))
  886. {
  887. return;
  888. }
  889. kXMLElement child = this.createAnotherElement();
  890. offset = child.parseCharArray(input, offset, end, lineNr);
  891. this.children.addElement(child);
  892. }
  893. }
  894. /**
  895. * Creates a new XML element.
  896. */
  897. protected kXMLElement createAnotherElement()
  898. {
  899. return new kXMLElement(this.conversionTable,
  900. this.skipLeadingWhitespace,
  901. false,
  902. this.ignoreCase);
  903. }
  904. /**
  905. * Scans the content of the object.
  906. *
  907. * @return the offset after the XML element; contentOffset points to the
  908. * start of the content section; contentSize is the size of the
  909. * content section
  910. *
  911. * @exception nanoxml.XMLParseException
  912. * if an error occured while parsing the array
  913. */
  914. private int scanContent(char[] input,
  915. int offset,
  916. int end,
  917. int[] contentOffset,
  918. int[] contentSize,
  919. int[] lineNr)
  920. throws XMLParseException
  921. {
  922. if (input[offset] == '/')
  923. {
  924. contentSize[0] = 0;
  925. if (input[offset + 1] != '>')
  926. {
  927. throw this.expectedInput("'>'", lineNr[0]);
  928. }
  929. return offset + 2;
  930. }
  931. if (input[offset] != '>')
  932. {
  933. throw this.expectedInput("'>'", lineNr[0]);
  934. }
  935. if (this.skipLeadingWhitespace)
  936. {
  937. offset = this.skipWhitespace(input, offset + 1, end, lineNr);
  938. }
  939. else
  940. {
  941. offset++;
  942. }
  943. int begin = offset;
  944. contentOffset[0] = offset;
  945. int level = 0;
  946. char[] tag = this.tagName.toCharArray();
  947. end -= (tag.length + 2);
  948. while ((offset < end) && (level >= 0))
  949. {
  950. if (input[offset] == '<')
  951. {
  952. boolean ok = true;
  953. if ((offset < (end - 1)) && (input[offset + 1] == '!'))
  954. {
  955. offset++;
  956. continue;
  957. }
  958. for (int i = 0; ok && (i < tag.length); i++)
  959. {
  960. ok &= (input[offset + (i + 1)] == tag[i]);
  961. }
  962. ok &= ! this.isIdentifierChar(input[offset + tag.length + 1]);
  963. if (ok)
  964. {
  965. while ((offset < end) && (input[offset] != '>'))
  966. {
  967. offset++;
  968. }
  969. if (input[offset - 1] != '/')
  970. {
  971. level++;
  972. }
  973. continue;
  974. }
  975. else if (input[offset + 1] == '/')
  976. {
  977. ok = true;
  978. for (int i = 0; ok && (i < tag.length); i++)
  979. {
  980. ok &= (input[offset + (i + 2)] == tag[i]);
  981. }
  982. if (ok)
  983. {
  984. contentSize[0] = offset - contentOffset[0];
  985. offset += tag.length + 2;
  986. offset = this.skipWhitespace(input, offset, end,
  987. lineNr);
  988. if (input[offset] == '>')
  989. {
  990. level--;
  991. offset++;
  992. }
  993. continue;
  994. }
  995. }
  996. }
  997. if (input[offset] == '\r')
  998. {
  999. lineNr[0]++;
  1000. if ((offset != end) && (input[offset + 1] == '\n'))
  1001. {
  1002. offset++;
  1003. }
  1004. }
  1005. else if (input[offset] == '\n')
  1006. {
  1007. lineNr[0]++;
  1008. }
  1009. offset++;
  1010. }
  1011. if (level >= 0)
  1012. {
  1013. throw this.unexpectedEndOfData(lineNr[0]);
  1014. }
  1015. if (this.skipLeadingWhitespace)
  1016. {
  1017. int i = contentOffset[0] + contentSize[0] - 1;
  1018. while ((contentSize[0] >= 0) && (input[i] <= ' '))
  1019. {
  1020. i--;
  1021. contentSize[0]--;
  1022. }
  1023. }
  1024. return offset;
  1025. }
  1026. /**
  1027. * Scans an identifier.
  1028. *
  1029. * @return the identifier, or <CODE>null</CODE> if offset doesn't point
  1030. * to an identifier
  1031. */
  1032. private String scanIdentifier(char[] input,
  1033. int offset,
  1034. int end)
  1035. {
  1036. int begin = offset;
  1037. while ((offset < end) && (this.isIdentifierChar(input[offset])))
  1038. {
  1039. offset++;
  1040. }
  1041. if ((offset == end) || (offset == begin))
  1042. {
  1043. return null;
  1044. }
  1045. else
  1046. {
  1047. return new String(input, begin, offset - begin);
  1048. }
  1049. }
  1050. /**
  1051. * Scans one attribute of an object.
  1052. *
  1053. * @return the offset after the attribute
  1054. *
  1055. * @exception nanoxml.XMLParseException
  1056. * if an error occured while parsing the array
  1057. */
  1058. private int scanOneAttribute(char[] input,
  1059. int offset,
  1060. int end,
  1061. int[] lineNr)
  1062. throws XMLParseException
  1063. {
  1064. String key, value;
  1065. key = this.scanIdentifier(input, offset, end);
  1066. if (key == null)
  1067. {
  1068. throw this.syntaxError("an attribute key", lineNr[0]);
  1069. }
  1070. offset = this.skipWhitespace(input, offset + key.length(), end, lineNr);
  1071. if (this.ignoreCase)
  1072. {
  1073. key = key.toUpperCase();
  1074. }
  1075. if (input[offset] != '=')
  1076. {
  1077. throw this.valueMissingForAttribute(key, lineNr[0]);
  1078. }
  1079. offset = this.skipWhitespace(input, offset + 1, end, lineNr);
  1080. value = this.scanString(input, offset, end, lineNr);
  1081. if (value == null)
  1082. {
  1083. throw this.syntaxError("an attribute value", lineNr[0]);
  1084. }
  1085. if ((value.charAt(0) == '"') || (value.charAt(0) == '\''))
  1086. {
  1087. value = value.substring(1, (value.length() - 1));
  1088. offset += 2;
  1089. }
  1090. this.attributes.put(key, this.decodeString(value, lineNr[0]));
  1091. return offset + value.length();
  1092. }
  1093. /**
  1094. * Scans a string. Strings are either identifiers, or text delimited by
  1095. * double quotes.
  1096. *
  1097. * @return the string found, without delimiting double quotes; or null
  1098. * if offset didn't point to a valid string
  1099. *
  1100. * @see nanoxml.kXMLElement#scanIdentifier
  1101. *
  1102. * @exception nanoxml.XMLParseException
  1103. * if an error occured while parsing the array
  1104. */
  1105. private String scanString(char[] input,
  1106. int offset,
  1107. int end,
  1108. int[] lineNr)
  1109. throws XMLParseException
  1110. {
  1111. char delim = input[offset];
  1112. if ((delim == '"') || (delim == '\''))
  1113. {
  1114. int begin = offset;
  1115. offset++;
  1116. while ((offset < end) && (input[offset] != delim))
  1117. {
  1118. if (input[offset] == '\r')
  1119. {
  1120. lineNr[0]++;
  1121. if ((offset != end) && (input[offset + 1] == '\n'))
  1122. {
  1123. offset++;
  1124. }
  1125. }
  1126. else if (input[offset] == '\n')
  1127. {
  1128. lineNr[0]++;
  1129. }
  1130. offset++;
  1131. }
  1132. if (offset == end)
  1133. {
  1134. return null;
  1135. }
  1136. else
  1137. {
  1138. return new String(input, begin, offset - begin + 1);
  1139. }
  1140. }
  1141. else
  1142. {
  1143. return this.scanIdentifier(input, offset, end);
  1144. }
  1145. }
  1146. /**
  1147. * Scans the class (tag) name of the object.
  1148. *
  1149. * @return the position after the tag name
  1150. *
  1151. * @exception nanoxml.XMLParseException
  1152. * if an error occured while parsing the array
  1153. */
  1154. private int scanTagName(char[] input,
  1155. int offset,
  1156. int end,
  1157. int[] lineNr)
  1158. throws XMLParseException
  1159. {
  1160. this.tagName = this.scanIdentifier(input, offset, end);
  1161. if (this.tagName == null)
  1162. {
  1163. throw this.syntaxError("a tag name", lineNr[0]);
  1164. }
  1165. return offset + this.tagName.length();
  1166. }
  1167. /**
  1168. * Changes the content string.
  1169. *
  1170. * @param content The new content string.
  1171. */
  1172. public void setContent(String content)
  1173. {
  1174. this.contents = content;
  1175. }
  1176. /**
  1177. * Changes the tag name.
  1178. *
  1179. * @param tagName The new tag name.
  1180. */
  1181. public void setTagName(String tagName)
  1182. {
  1183. this.tagName = tagName;
  1184. }
  1185. /**
  1186. * Skips a tag that don't contain any useful data: <?...?>,
  1187. * <!...> and comments.
  1188. *
  1189. * @return the position after the tag
  1190. *
  1191. * @exception nanoxml.XMLParseException
  1192. * if an error occured while parsing the array
  1193. */
  1194. protected int skipBogusTag(char[] input,
  1195. int offset,
  1196. int end,
  1197. int[] lineNr)
  1198. {
  1199. if ((input[offset + 1] == '-') && (input[offset + 2] == '-'))
  1200. {
  1201. while ((offset < end)
  1202. && ((input[offset] != '-')
  1203. || (input[offset + 1] != '-')
  1204. || (input[offset + 2] != '>')))
  1205. {
  1206. if (input[offset] == '\r')
  1207. {
  1208. lineNr[0]++;
  1209. if ((offset != end) && (input[offset + 1] == '\n'))
  1210. {
  1211. offset++;
  1212. }
  1213. }
  1214. else if (input[offset] == '\n')
  1215. {
  1216. lineNr[0]++;
  1217. }
  1218. offset++;
  1219. }
  1220. if (offset == end)
  1221. {
  1222. throw unexpectedEndOfData(lineNr[0]);
  1223. }
  1224. else
  1225. {
  1226. return offset + 3;
  1227. }
  1228. }
  1229. int level = 1;
  1230. while (offset < end)
  1231. {
  1232. char ch = input[offset++];
  1233. switch (ch)
  1234. {
  1235. case '\r':
  1236. if ((offset < end) && (input[offset] == '\n'))
  1237. {
  1238. offset++;
  1239. }
  1240. lineNr[0]++;
  1241. break;
  1242. case '\n':
  1243. lineNr[0]++;
  1244. break;
  1245. case '<':
  1246. level++;
  1247. break;
  1248. case '>':
  1249. level--;
  1250. if (level == 0) {
  1251. return offset;
  1252. }
  1253. break;
  1254. default:
  1255. }
  1256. }
  1257. throw this.unexpectedEndOfData(lineNr[0]);
  1258. }
  1259. /**
  1260. * Skips a tag that don't contain any useful data: <?...?>,
  1261. * <!...> and comments.
  1262. *
  1263. * @return the position after the tag
  1264. *
  1265. * @exception nanoxml.XMLParseException
  1266. * if an error occured while parsing the array
  1267. */
  1268. private int skipPreamble(char[] input,
  1269. int offset,
  1270. int end,
  1271. int[] lineNr)
  1272. throws XMLParseException
  1273. {
  1274. char ch;
  1275. do
  1276. {
  1277. offset = this.skipWhitespace(input, offset, end, lineNr);
  1278. if (input[offset] != '<')
  1279. {
  1280. this.expectedInput("'<'", lineNr[0]);
  1281. }
  1282. offset++;
  1283. if (offset >= end)
  1284. {
  1285. throw this.unexpectedEndOfData(lineNr[0]);
  1286. }
  1287. ch = input[offset];
  1288. if ((ch == '!') || (ch == '?'))
  1289. {
  1290. offset = this.skipBogusTag(input, offset, end, lineNr);
  1291. }
  1292. } while (! isIdentifierChar(ch));
  1293. return offset;
  1294. }
  1295. /**
  1296. * Skips whitespace characters.
  1297. *
  1298. * @return the position after the whitespace
  1299. *
  1300. * @exception nanoxml.XMLParseException
  1301. * if an error occured while parsing the array
  1302. */
  1303. private int skipWhitespace(char[] input,
  1304. int offset,
  1305. int end,
  1306. int[] lineNr)
  1307. {
  1308. while ((offset < end) && (input[offset] <= ' '))
  1309. {
  1310. if (input[offset] == '\r')
  1311. {
  1312. lineNr[0]++;
  1313. if ((offset != end) && (input[offset + 1] == '\n'))
  1314. {
  1315. offset++;
  1316. }
  1317. }
  1318. else if (input[offset] == '\n')
  1319. {
  1320. lineNr[0]++;
  1321. }
  1322. offset++;
  1323. }
  1324. if (offset == end)
  1325. {
  1326. throw this.unexpectedEndOfData(lineNr[0]);
  1327. }
  1328. return offset;
  1329. }
  1330. /**
  1331. * Converts &...; sequences to "normal" chars.
  1332. */
  1333. protected String decodeString(String s,
  1334. int lineNr)
  1335. {
  1336. StringBuffer result = new StringBuffer(s.length());
  1337. int index = 0;
  1338. while (index < s.length())
  1339. {
  1340. int index2 = (s + '&').indexOf('&', index);
  1341. int index3 = (s + "<![CDATA[").indexOf("<![CDATA[", index);
  1342. if (index2 <= index3)
  1343. {
  1344. result.append(s.substring(index, index2));
  1345. if (index2 == s.length())
  1346. {
  1347. break;
  1348. }
  1349. index = s.indexOf(';', index2);
  1350. if (index < 0)
  1351. {
  1352. result.append(s.substring(index2));
  1353. break;
  1354. }
  1355. String key = s.substring(index2 + 1, index);
  1356. if (key.charAt(0) == '#')
  1357. {
  1358. if (key.charAt(1) == 'x')
  1359. {
  1360. result.append((char)(Integer.
  1361. parseInt(key.substring(2),
  1362. 16)));
  1363. }
  1364. else
  1365. {
  1366. result.append((char)(Integer.
  1367. parseInt(key.substring(1),
  1368. 10)));
  1369. }
  1370. }
  1371. else
  1372. {
  1373. result.append(this.conversionTable
  1374. .getProperty(key, "&" + key + ';'));
  1375. }
  1376. }
  1377. else
  1378. {
  1379. int index4 = (s + "]]>").indexOf("]]>", index3 + 9);
  1380. result.append(s.substring(index, index3));
  1381. result.append(s.substring(index3 + 9, index4));
  1382. index = index4 + 2;
  1383. }
  1384. index++;
  1385. }
  1386. return result.toString();
  1387. }
  1388. /**
  1389. * Writes the XML element to a string.
  1390. */
  1391. public String toString()
  1392. {
  1393. FakeStringWriter writer = new FakeStringWriter();
  1394. this.write(writer);
  1395. return writer.toString();
  1396. }
  1397. /**
  1398. * Writes the XML element to a writer.
  1399. */
  1400. public void write(Writer writer)
  1401. {
  1402. this.write(writer, 0);
  1403. }
  1404. /**
  1405. * Writes the XML element to a writer.
  1406. */
  1407. public void write(Writer writer,
  1408. int indent)
  1409. {
  1410. FakePrintWriter out = new FakePrintWriter(writer);
  1411. for (int i = 0; i < indent; i++)
  1412. {
  1413. out.print(' ');
  1414. }
  1415. if (this.tagName == null)
  1416. {
  1417. this.writeEncoded(out, this.contents);
  1418. return;
  1419. }
  1420. out.print('<');
  1421. out.print(this.tagName);
  1422. if (! this.attributes.isEmpty())
  1423. {
  1424. Enumeration lenum = this.attributes.keys();
  1425. while (lenum.hasMoreElements())
  1426. {
  1427. out.print(' ');
  1428. String key = (String)(lenum.nextElement());
  1429. String value = (String)(this.attributes.get(key));
  1430. out.print(key);
  1431. out.print("=\"");
  1432. this.writeEncoded(out, value);
  1433. out.print('"');
  1434. }
  1435. }
  1436. if ((this.contents != null) && (this.contents.length() > 0))
  1437. {
  1438. if (this.skipLeadingWhitespace)
  1439. {
  1440. out.println('>');
  1441. for (int i = 0; i < indent + 4; i++)
  1442. {
  1443. out.print(' ');
  1444. }
  1445. out.println(this.contents);
  1446. for (int i = 0; i < indent; i++)
  1447. {
  1448. out.print(' ');
  1449. }
  1450. }
  1451. else
  1452. {
  1453. out.print('>');
  1454. this.writeEncoded(out, this.contents);
  1455. }
  1456. out.print("</");
  1457. out.print(this.tagName);
  1458. out.println('>');
  1459. }
  1460. else if (this.children.isEmpty())
  1461. {
  1462. out.println("/>");
  1463. }
  1464. else {
  1465. out.println('>');
  1466. Enumeration lenum = this.enumerateChildren();
  1467. while (lenum.hasMoreElements())
  1468. {
  1469. kXMLElement child = (kXMLElement)(lenum.nextElement());
  1470. child.write(writer, indent + 4);
  1471. }
  1472. for (int i = 0; i < indent; i++)
  1473. {
  1474. out.print(' ');
  1475. }
  1476. out.print("</");
  1477. out.print(this.tagName);
  1478. out.println('>');
  1479. }
  1480. }
  1481. /**
  1482. * Writes a string encoded to a writer.
  1483. */
  1484. protected void writeEncoded(FakePrintWriter out,
  1485. String str)
  1486. {
  1487. for (int i = 0; i < str.length(); i++)
  1488. {
  1489. char ch = str.charAt(i);
  1490. switch (ch)
  1491. {
  1492. case '<':
  1493. out.write("<");
  1494. break;
  1495. case '>':
  1496. out.write(">");
  1497. break;
  1498. case '&':
  1499. out.write("&");
  1500. break;
  1501. case '"':
  1502. out.write(""");
  1503. break;
  1504. case '\'':
  1505. out.write("&");
  1506. break;
  1507. case '\r':
  1508. case '\n':
  1509. out.write(ch);
  1510. break;
  1511. default:
  1512. if ((int)ch < 16)
  1513. {
  1514. out.write("�");
  1515. out.write(Integer.toString((int)ch, 16));
  1516. out.write(';');
  1517. }
  1518. else if (((int)ch < 32)
  1519. || (((int)ch > 126) && ((int)ch < 256)))
  1520. {
  1521. out.write("&#x");
  1522. out.write(Integer.toString((int)ch, 16));
  1523. out.write(';');
  1524. }
  1525. else
  1526. {
  1527. out.write(ch);
  1528. }
  1529. }
  1530. }
  1531. }
  1532. /**
  1533. * Creates a parse exception for when an invalid valueset is given to
  1534. * a method.
  1535. */
  1536. private XMLParseException invalidValueSet(String key)
  1537. {
  1538. String msg = "Invalid value set (key = \"" + key + "\")";
  1539. return new XMLParseException(this.getTagName(), msg);
  1540. }
  1541. /**
  1542. * Creates a parse exception for when an invalid value is given to a
  1543. * method.
  1544. */
  1545. private XMLParseException invalidValue(String key,
  1546. String value,
  1547. int lineNr)
  1548. {
  1549. String msg = "Attribute \"" + key + "\" does not contain a valid "
  1550. + "value (\"" + value + "\")";
  1551. return new XMLParseException(this.getTagName(), lineNr, msg);
  1552. }
  1553. /**
  1554. * The end of the data input has been reached.
  1555. */
  1556. private XMLParseException unexpectedEndOfData(int lineNr)
  1557. {
  1558. String msg = "Unexpected end of data reached";
  1559. return new XMLParseException(this.getTagName(), lineNr, msg);
  1560. }
  1561. /**
  1562. * A syntax error occured.
  1563. */
  1564. private XMLParseException syntaxError(String context,
  1565. int lineNr)
  1566. {
  1567. String msg = "Syntax error while parsing " + context;
  1568. return new XMLParseException(this.getTagName(), lineNr, msg);
  1569. }
  1570. /**
  1571. * A character has been expected.
  1572. */
  1573. private XMLParseException expectedInput(String charSet,
  1574. int lineNr)
  1575. {
  1576. String msg = "Expected: " + charSet;
  1577. return new XMLParseException(this.getTagName(), lineNr, msg);
  1578. }
  1579. /**
  1580. * A value is missing for an attribute.
  1581. */
  1582. private XMLParseException valueMissingForAttribute(String key,
  1583. int lineNr)
  1584. {
  1585. String msg = "Value missing for attribute with key \"" + key + "\"";
  1586. return new XMLParseException(this.getTagName(), lineNr, msg);
  1587. }
  1588. //------------------------------------------------------------------
  1589. public static class FakeProperties extends Hashtable
  1590. {
  1591. /**
  1592. */
  1593. public FakeProperties()
  1594. {
  1595. }
  1596. /**
  1597. */
  1598. public String getProperty( String key )
  1599. {
  1600. return (String) super.get( key );
  1601. }
  1602. /**
  1603. */
  1604. public String getProperty( String key, String defstr )
  1605. {
  1606. String val = getProperty( key );
  1607. return( ( val != null ) ? val : defstr );
  1608. }
  1609. }
  1610. //------------------------------------------------------------------
  1611. /**
  1612. * A simple replacement for StringWriter.
  1613. */
  1614. private static class FakeStringWriter extends Writer
  1615. {
  1616. private StringBuffer buf;
  1617. /**
  1618. */
  1619. public FakeStringWriter()
  1620. {
  1621. buf = new StringBuffer();
  1622. }
  1623. /**
  1624. */
  1625. public void close()
  1626. {
  1627. }
  1628. /**
  1629. */
  1630. public void flush()
  1631. {
  1632. }
  1633. /**
  1634. */
  1635. public void write( int c )
  1636. {
  1637. buf.append( (char) c );
  1638. }
  1639. /**
  1640. */
  1641. public void write( char b[], int off, int len )
  1642. {
  1643. buf.append( b, off, len );
  1644. }
  1645. /**
  1646. */
  1647. public void write( String str )
  1648. {
  1649. buf.append( str );
  1650. }
  1651. /**
  1652. */
  1653. public String toString()
  1654. {
  1655. return buf.toString();
  1656. }
  1657. }
  1658. //------------------------------------------------------------------
  1659. /**
  1660. * A simple replacement for PrintWriter.
  1661. */
  1662. private static class FakePrintWriter extends Writer
  1663. {
  1664. private Writer out;
  1665. /**
  1666. */
  1667. public FakePrintWriter( Writer out )
  1668. {
  1669. super( out );
  1670. this.out = out;
  1671. }
  1672. public void close()
  1673. {
  1674. try {
  1675. out.close();
  1676. }
  1677. catch( IOException e ){
  1678. }
  1679. }
  1680. public void flush()
  1681. {
  1682. try {
  1683. out.flush();
  1684. }
  1685. catch( IOException e ){
  1686. }
  1687. }
  1688. public void write( int c )
  1689. {
  1690. try {
  1691. out.write( c );
  1692. }
  1693. catch( IOException e ){
  1694. }
  1695. }
  1696. public void write( char buf[], int off, int len )
  1697. {
  1698. try {
  1699. out.write( buf, off, len );
  1700. }
  1701. catch( IOException e ){
  1702. }
  1703. }
  1704. public void write( String s )
  1705. {
  1706. try {
  1707. out.write( s, 0, s.length() );
  1708. }
  1709. catch( IOException e ){
  1710. }
  1711. }
  1712. /**
  1713. */
  1714. public void print( char ch )
  1715. {
  1716. write( String.valueOf( ch ) );
  1717. }
  1718. /**
  1719. */
  1720. private void newLine()
  1721. {
  1722. write( '\n' );
  1723. }
  1724. /**
  1725. */
  1726. public void print( String str )
  1727. {
  1728. if( str == null ){
  1729. str = "null";
  1730. }
  1731. write( str );
  1732. }
  1733. /**
  1734. */
  1735. public void println( char ch )
  1736. {
  1737. print( ch );
  1738. newLine();
  1739. }
  1740. /**
  1741. */
  1742. public void println( String str )
  1743. {
  1744. print( str );
  1745. newLine();
  1746. }
  1747. }
  1748. }