PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/XSLT/tags/xslt-0_5/xslt/IndentingTransformer.java

#
Java | 458 lines | 324 code | 100 blank | 34 comment | 83 complexity | 6d0861f3fa363ca97be566f1468ff121 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * AbstractIndentingTransformer.java - Indents XML elements, by adding whitespace where appropriate.
  3. *
  4. * Copyright (c) 2002 Robert McKinnon
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19. *
  20. * email: robmckinnon@users.sourceforge.net
  21. */
  22. package xslt;
  23. import org.xml.sax.Attributes;
  24. import org.xml.sax.SAXException;
  25. import org.xml.sax.ext.DeclHandler;
  26. import org.xml.sax.helpers.AttributesImpl;
  27. import javax.xml.transform.sax.TransformerHandler;
  28. import java.io.IOException;
  29. import java.io.Writer;
  30. /**
  31. * Indents elements, by adding whitespace where appropriate.
  32. * Does not remove blank lines between nodes.
  33. * Does not remove new lines within text nodes.
  34. * Puts element tags immediately following mixed content text on the same line as the text.
  35. *
  36. * @author Robert McKinnon - robmckinnon@users.sourceforge.net
  37. */
  38. public abstract class IndentingTransformer implements TransformerHandler, DeclHandler {
  39. /** buffer to hold character data */
  40. private Writer writer;
  41. private String xml;
  42. private char[] chars;
  43. private boolean isContinue = true;
  44. private boolean isClosingTag = false;
  45. private boolean isEmptyElement = false;
  46. private boolean isDocType = false;
  47. public void characters(char ch[], int start, int length) throws SAXException {
  48. try {
  49. if(isDocType) {
  50. isDocType = false;
  51. } else {
  52. writer.write(ch, start, length);
  53. }
  54. } catch(IOException e) {
  55. throw new SAXException(e);
  56. }
  57. }
  58. public void comment(char ch[], int start, int length) throws SAXException {
  59. try {
  60. int priorSpaceIndex = start - 5;
  61. while(priorSpaceIndex >= 0 && (Character.isSpaceChar(ch[priorSpaceIndex]) || ch[priorSpaceIndex] == '\t')) {
  62. if(ch[priorSpaceIndex] == '\t') {
  63. writer.write('\t');
  64. } else {
  65. writer.write(' ');
  66. }
  67. priorSpaceIndex--;
  68. }
  69. writer.write("<!--");
  70. writer.write(ch, start, length);
  71. writer.write("-->");
  72. } catch(IOException e) {
  73. throw new SAXException(e);
  74. }
  75. }
  76. public void processingInstruction(String target, String data) throws SAXException {
  77. try {
  78. writer.write("<?");
  79. writer.write(target);
  80. writer.write(" ");
  81. writer.write(data);
  82. writer.write("?>");
  83. } catch(IOException e) {
  84. throw new SAXException(e);
  85. }
  86. }
  87. public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
  88. try {
  89. if(isClosingTag) {
  90. writer.write("</");
  91. writer.write(qName);
  92. writer.write(">");
  93. isClosingTag = false;
  94. }
  95. } catch(IOException e) {
  96. throw new SAXException(e);
  97. }
  98. }
  99. public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
  100. throws SAXException {
  101. try {
  102. writer.write("<");
  103. writer.write(qName);
  104. for(int i = 0; i < atts.getLength(); i++) {
  105. writer.write(' ');
  106. writer.write(atts.getQName(i));
  107. writer.write("=\"");
  108. writer.write(atts.getValue(i));
  109. writer.write('\"');
  110. }
  111. if(isEmptyElement) {
  112. writer.write("/>");
  113. } else {
  114. writer.write(">");
  115. }
  116. } catch(IOException e) {
  117. throw new SAXException(e);
  118. }
  119. }
  120. protected String indentXml(final String xmlString, final Writer outputWriter) throws IOException, SAXException {
  121. this.writer = outputWriter;
  122. this.xml = xmlString;
  123. this.chars = xml.toCharArray();
  124. int start = 0;
  125. int end = 0;
  126. while(isContinue) {
  127. end = xml.indexOf('<', start);
  128. writeTextPrecedingLessThan(start, end);
  129. if(isContinue) {
  130. start = end;
  131. if(xml.startsWith("<!--", start)) {
  132. end = writeComment(start);
  133. } else if(xml.startsWith("<?", start)) {
  134. end = writeXmlDeclarationOrProcessingInstruction(start);
  135. } else if(xml.startsWith("<!", start)) {
  136. if(xml.startsWith("<![CDATA[", start)) {
  137. end = writeCData(start);
  138. } else {
  139. end = writeDocType(start);
  140. }
  141. } else if(xml.startsWith("</", start)) {
  142. end = writeClosingTag(start);
  143. } else if(Character.isWhitespace(chars[start+1])) {
  144. throw new SAXException("The content of elements must consist of well-formed character data or markup.");
  145. } else {
  146. end = writeElement(start);
  147. }
  148. start = end;
  149. }
  150. }
  151. return outputWriter.toString();
  152. }
  153. private int writeCData(int start) throws IOException, SAXException {
  154. int end = xml.indexOf("]]>", start);
  155. writeRemaining(start, end);
  156. if(isContinue) {
  157. startCDATA();
  158. end = end + 3;
  159. writer.write(xml, start, end - start);
  160. endCDATA();
  161. }
  162. return end;
  163. }
  164. private int writeElement(int start) throws IOException, SAXException {
  165. int end = getStartTagEnd(start);
  166. writeRemaining(start, end);
  167. if(isContinue) {
  168. isEmptyElement = (chars[end - 1] == '/');
  169. if(isEmptyElement) {
  170. end--;
  171. }
  172. AttributesImpl attributes = new AttributesImpl();
  173. String elementName = getElementNameAndPopulateAttributes(start, end, attributes);
  174. startElement("", "", elementName, attributes);
  175. if(isEmptyElement) {
  176. endElement("", "", elementName);
  177. end = end + 2;
  178. isEmptyElement = false;
  179. } else {
  180. end = end + 1;
  181. }
  182. }
  183. return end;
  184. }
  185. /**
  186. * Ignores '>' characters that are inside of attribute values.
  187. */
  188. private int getStartTagEnd(int start) {
  189. int end = -1;
  190. int index = start;
  191. while(index < chars.length && end == -1) {
  192. char aChar = chars[index];
  193. index++;
  194. if(aChar == '\"') {
  195. while(chars[index] != '\"') {
  196. index++;
  197. }
  198. index++;
  199. } else if(aChar == '\'') {
  200. while(chars[index] != '\'') {
  201. index++;
  202. }
  203. index++;
  204. } else if(aChar == '>') {
  205. end = index -1;
  206. }
  207. }
  208. return end;
  209. }
  210. private String getElementNameAndPopulateAttributes(int start, int end, AttributesImpl attributes) throws SAXException {
  211. int nameEnd = xml.indexOf(' ', start);
  212. if(nameEnd == -1 || end < nameEnd) {
  213. nameEnd = end;
  214. } else if(nameEnd + 1 != end) {
  215. char[] chars = xml.substring(nameEnd + 1, end).toCharArray();
  216. populateAttributes(chars, attributes);
  217. }
  218. StringBuffer elementName = new StringBuffer();
  219. int index = start + 1;
  220. while(!Character.isWhitespace(chars[index]) && chars[index] != '>') {
  221. elementName.append(chars[index++]);
  222. }
  223. return elementName.toString();
  224. }
  225. private void populateAttributes(char[] chars, AttributesImpl attributes) throws SAXException {
  226. StringBuffer qName = new StringBuffer();
  227. StringBuffer value = new StringBuffer();
  228. boolean isLastSpace = false;
  229. char quote = '\"';
  230. int i = 0;
  231. while(i < chars.length) {
  232. qName.setLength(0);
  233. value.setLength(0);
  234. while(i < chars.length && Character.isWhitespace(chars[i])) {
  235. i++;
  236. }
  237. if(i < chars.length) {
  238. while(i < chars.length && chars[i] != '=') {
  239. qName.append(chars[i]);
  240. i++;
  241. }
  242. i++; // get past equals
  243. while(i < chars.length && Character.isWhitespace(chars[i])) {
  244. i++;
  245. }
  246. if(i < chars.length) {
  247. if(chars[i] != '\"' && chars[i] != '\'') {
  248. throw new SAXException("value for attribute " + qName.toString().trim() + " must be in quotes");
  249. } else {
  250. quote = chars[i];
  251. i++;
  252. }
  253. }
  254. while(i < chars.length && chars[i] != quote) {
  255. if(Character.isWhitespace(chars[i])) {
  256. if(!isLastSpace) {
  257. if(Character.isSpaceChar(chars[i])) {
  258. value.append(' ');
  259. }
  260. isLastSpace = true;
  261. } else {
  262. // don't add consequtive space
  263. }
  264. } else {
  265. value.append(chars[i]);
  266. isLastSpace = false;
  267. }
  268. i++;
  269. }
  270. i++; // get past quote
  271. attributes.addAttribute("", "", qName.toString().trim(), "", value.toString().trim());
  272. }
  273. }
  274. }
  275. private int writeClosingTag(int start) throws IOException, SAXException {
  276. int end = xml.indexOf('>', start);
  277. writeRemaining(start, end);
  278. if(isContinue) {
  279. isClosingTag = true;
  280. endElement("", "", xml.substring(start + 2, end).trim());
  281. end = end + 1;
  282. }
  283. return end;
  284. }
  285. private int writeDocType(int start) throws IOException {
  286. int end = xml.indexOf('>', start);
  287. int bracketStart = xml.indexOf('[', start);
  288. if(bracketStart != -1 && bracketStart < end) {
  289. int bracketEnd = xml.indexOf(']', bracketStart);
  290. end = xml.indexOf('>', bracketEnd);
  291. }
  292. writeRemaining(start, end);
  293. if(isContinue) {
  294. end = end + 1;
  295. writer.write("\n");
  296. int length = end - start;
  297. writer.write(xml, start, length);
  298. isDocType = true;
  299. }
  300. return end;
  301. }
  302. private int writeXmlDeclarationOrProcessingInstruction(int start) throws IOException, SAXException {
  303. int end;
  304. if(xml.startsWith("<?xml ", start)) {
  305. end = writeXmlDeclaration(start);
  306. } else {
  307. end = writeProcessingInstruction(start);
  308. }
  309. return end;
  310. }
  311. private int writeProcessingInstruction(int start) throws IOException, SAXException {
  312. int end = xml.indexOf("?>", start);
  313. writeRemaining(start, end);
  314. if(isContinue) {
  315. int targetEnd = xml.indexOf(" ", start);
  316. String target = xml.substring(start + "<?".length(), targetEnd);
  317. String data = xml.substring(targetEnd + 1, end);
  318. processingInstruction(target, data);
  319. end = end + "?>".length();
  320. }
  321. return end;
  322. }
  323. private int writeXmlDeclaration(int start) throws IOException {
  324. int end = xml.indexOf("?>", start);
  325. writeRemaining(start, end);
  326. if(isContinue) {
  327. end = end + "?>".length();
  328. int length = end - start;
  329. writer.write(xml, start, length);
  330. }
  331. return end;
  332. }
  333. private int writeComment(int start) throws IOException, SAXException {
  334. int end = xml.indexOf("-->", start);
  335. writeRemaining(start, end);
  336. if(isContinue) {
  337. int commentTextStart = start + "<!--".length();
  338. int commentTextLength = end - commentTextStart;
  339. comment(chars, commentTextStart, commentTextLength);
  340. end = end + "-->".length();
  341. }
  342. return end;
  343. }
  344. private void writeTextPrecedingLessThan(int start, int end) throws IOException, SAXException {
  345. writeRemaining(start, end);
  346. if(isContinue && end > start) {
  347. int length = end - start;
  348. characters(chars, start, length);
  349. }
  350. }
  351. private void writeRemaining(int start, int end) throws IOException {
  352. if(end == -1) {
  353. int length = xml.length() - start;
  354. writer.write(xml, start, length);
  355. isContinue = false;
  356. }
  357. }
  358. }