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

/bundles/plugins-trunk/XML/xml/indent/IndentingTransformer.java

#
Java | 531 lines | 478 code | 22 blank | 31 comment | 8 complexity | 0a43f238bed585209540815ed0cd0aa4 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, 2003 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 xml.indent;
  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. import java.util.regex.*;
  31. import org.gjt.sp.jedit.jEdit;
  32. /**
  33. * Indents elements, by adding whitespace where appropriate.
  34. * Does not remove blank lines between nodes.
  35. * Does not remove new lines within text nodes.
  36. * Puts element tags immediately following mixed content text on the same line as the text.
  37. *
  38. * @author Robert McKinnon - robmckinnon@users.sourceforge.net
  39. */
  40. public abstract class IndentingTransformer implements TransformerHandler, DeclHandler {
  41. private static final String ON_NEW_LINE = "onNewLine";
  42. /** buffer to hold character data */
  43. private Writer writer;
  44. private String xml;
  45. private char[] chars;
  46. private boolean okToContinue = true;
  47. private boolean isClosingTag = false;
  48. private boolean isEmptyElement = false;
  49. private boolean isDocType = false;
  50. public void characters( char ch[], int start, int length ) throws SAXException {
  51. try {
  52. if ( isDocType ) {
  53. isDocType = false;
  54. }
  55. else {
  56. writer.write( ch, start, length );
  57. }
  58. }
  59. catch ( IOException e ) {
  60. throw new SAXException( e );
  61. }
  62. }
  63. public void comment( char ch[], int start, int length ) throws SAXException {
  64. try {
  65. writer.write( "<!--" );
  66. writer.write( ch, start, length );
  67. writer.write( "-->" );
  68. }
  69. catch ( IOException e ) {
  70. throw new SAXException( e );
  71. }
  72. }
  73. public void processingInstruction( String target, String data ) throws SAXException {
  74. try {
  75. writer.write( "<?" );
  76. writer.write( target );
  77. writer.write( " " );
  78. writer.write( data );
  79. writer.write( "?>" );
  80. }
  81. catch ( IOException e ) {
  82. throw new SAXException( e );
  83. }
  84. }
  85. public void endElement( String namespaceURI, String localName, String qName ) throws SAXException {
  86. try {
  87. if ( isClosingTag ) {
  88. writer.write( "</" );
  89. writer.write( qName );
  90. writer.write( ">" );
  91. isClosingTag = false;
  92. }
  93. }
  94. catch ( IOException e ) {
  95. throw new SAXException( e );
  96. }
  97. }
  98. public void startElement( String namespaceURI, String localName, String qName, Attributes atts )
  99. throws SAXException {
  100. try {
  101. writer.write( "<" );
  102. boolean spaceAtEnd = qName.charAt( qName.length() - 1 ) == ' ';
  103. if ( spaceAtEnd ) {
  104. writer.write( qName.substring( 0, qName.length() - 1 ) );
  105. }
  106. else {
  107. writer.write( qName );
  108. }
  109. boolean onNewLine = jEdit.getBooleanProperty("xmlindenter.splitAttributes", false);
  110. for ( int i = 0; i < atts.getLength(); i++ ) {
  111. String attributeQName = atts.getQName( i );
  112. String attributeValue = atts.getValue( i );
  113. //boolean onNewLine = ( atts.getType( i ) == ON_NEW_LINE );
  114. boolean containsDoubleQuote = ( attributeValue.indexOf( '"' ) != -1 );
  115. char quote = containsDoubleQuote ? '\'' : '\"';
  116. if ( onNewLine ) {
  117. indent( 1 );
  118. }
  119. else {
  120. writer.write( ' ' );
  121. }
  122. writer.write( attributeQName );
  123. writer.write( '=' );
  124. writer.write( quote );
  125. writer.write( attributeValue );
  126. writer.write( quote );
  127. }
  128. if ( isEmptyElement ) {
  129. if ( spaceAtEnd ) {
  130. writer.write( " />" ); // to cater for <br /> elements
  131. }
  132. else {
  133. writer.write( "/>" );
  134. }
  135. }
  136. else {
  137. writer.write( ">" );
  138. }
  139. }
  140. catch ( IOException e ) {
  141. throw new SAXException( e );
  142. }
  143. }
  144. protected abstract void indent( int levelAdjustment ) throws SAXException;
  145. protected String indentXml( final String xmlString, final Writer outputWriter ) throws IOException, SAXException {
  146. this.okToContinue = true;
  147. this.isClosingTag = false;
  148. this.isEmptyElement = false;
  149. this.isDocType = false;
  150. this.writer = outputWriter;
  151. this.xml = xmlString;
  152. this.chars = xml.toCharArray();
  153. int start = 0;
  154. int end = 0;
  155. while ( okToContinue ) {
  156. end = xml.indexOf( '<', start );
  157. writeTextPrecedingLessThan( start, end );
  158. if ( okToContinue ) {
  159. start = end;
  160. if ( xml.startsWith( "<!--", start ) ) {
  161. end = writeComment( start );
  162. }
  163. else if ( xml.startsWith( "<?", start ) ) {
  164. end = writeXmlDeclarationOrProcessingInstruction( start );
  165. }
  166. else if ( xml.startsWith( "<!", start ) ) {
  167. if ( xml.startsWith( "<![CDATA[", start ) ) {
  168. end = writeCData( start );
  169. }
  170. else {
  171. end = writeDocType( start );
  172. }
  173. }
  174. else if ( xml.startsWith( "</", start ) ) {
  175. end = writeClosingTag( start );
  176. }
  177. else if ( Character.isWhitespace( chars[ start + 1 ] ) ) {
  178. throw new SAXException( "The content of elements must consist of well-formed character data or markup." );
  179. }
  180. else {
  181. end = writeElement( start );
  182. }
  183. start = end;
  184. }
  185. }
  186. return outputWriter.toString();
  187. }
  188. private int writeCData( int start ) throws IOException, SAXException {
  189. int end = xml.indexOf( "]]>", start );
  190. writeRemaining( start, end );
  191. if ( okToContinue ) {
  192. startCDATA();
  193. end = end + 3;
  194. writer.write( xml, start, end - start );
  195. endCDATA();
  196. }
  197. return end;
  198. }
  199. private int writeElement( int start ) throws IOException, SAXException {
  200. int end = getStartTagEnd( start );
  201. writeRemaining( start, end );
  202. if ( okToContinue ) {
  203. int offset = 1;
  204. while ( Character.isWhitespace( chars[ end - offset ] ) ) {
  205. offset++;
  206. }
  207. isEmptyElement = ( chars[ end - offset ] == '/' );
  208. if ( isEmptyElement ) {
  209. end = end - offset;
  210. }
  211. AttributesImpl attributes = new AttributesImpl();
  212. String elementName = getElementNameAndPopulateAttributes( start, end, attributes );
  213. if ( isEmptyElement && chars[ end - 1 ] == ' ' ) {
  214. elementName += ' '; // to cater for <br /> elements
  215. }
  216. startElement( "", "", elementName, attributes );
  217. if ( isEmptyElement ) {
  218. endElement( "", "", elementName );
  219. end = end + offset + 1;
  220. isEmptyElement = false;
  221. }
  222. else {
  223. end = end + 1;
  224. }
  225. }
  226. return end;
  227. }
  228. /**
  229. * Ignores '>' characters that are inside of attribute values.
  230. */
  231. private int getStartTagEnd( int start ) {
  232. int end = -1;
  233. int index = start;
  234. while ( index < chars.length && end == -1 ) {
  235. char aChar = chars[ index ];
  236. index++;
  237. if ( aChar == '\"' ) {
  238. while ( chars[ index ] != '\"' ) {
  239. index++;
  240. }
  241. index++;
  242. }
  243. else if ( aChar == '\'' ) {
  244. while ( chars[ index ] != '\'' ) {
  245. index++;
  246. }
  247. index++;
  248. // } else if(aChar == '/') {
  249. // while(chars[index] != '>') {
  250. // index++;
  251. // }
  252. }
  253. else if ( aChar == '>' ) {
  254. end = index - 1;
  255. // end = index;
  256. }
  257. }
  258. return end;
  259. }
  260. Pattern p = Pattern.compile( "([<].*?)\\s", Pattern.DOTALL );
  261. private String getElementNameAndPopulateAttributes( int start, int end, AttributesImpl attributes ) throws SAXException {
  262. // element name ends at _first_ white space char
  263. int nameEnd = -1;
  264. Matcher m = p.matcher(xml.substring(start, end));
  265. boolean found = m.find();
  266. if (found) {
  267. nameEnd = start + m.end();
  268. }
  269. else {
  270. nameEnd = xml.indexOf( ' ', start );
  271. if ( nameEnd == -1 || nameEnd > end ) {
  272. nameEnd = xml.indexOf( '\n', start );
  273. }
  274. if ( nameEnd == -1 || nameEnd > end ) {
  275. nameEnd = xml.indexOf( '\r', start );
  276. }
  277. }
  278. if ( nameEnd == -1 || nameEnd > end ) {
  279. nameEnd = end;
  280. }
  281. else if ( nameEnd + 1 != end ) {
  282. while ( Character.isWhitespace( chars[ nameEnd - 1 ] ) ) {
  283. nameEnd--; // want to check if char at nameEnd is a new line char
  284. }
  285. char[] elementChars = xml.substring( nameEnd, end ).toCharArray();
  286. populateAttributes( elementChars, attributes );
  287. }
  288. StringBuffer elementName = new StringBuffer();
  289. int index = start + 1;
  290. while ( !Character.isWhitespace( chars[ index ] ) && chars[ index ] != '>' && chars[ index ] != '/' ) {
  291. elementName.append( chars[ index++ ] );
  292. }
  293. return elementName.toString();
  294. }
  295. private void populateAttributes( char[] chars, AttributesImpl attributes ) throws SAXException {
  296. StringBuffer qName = new StringBuffer();
  297. StringBuffer value = new StringBuffer();
  298. char quote = '\"';
  299. int i = 0;
  300. boolean attributeOnNewLine;
  301. while ( i < chars.length ) {
  302. attributeOnNewLine = false;
  303. qName.setLength( 0 );
  304. value.setLength( 0 );
  305. // consume leading whitespace
  306. while ( i < chars.length && Character.isWhitespace( chars[ i ] ) ) {
  307. if ( chars[ i ] == '\r' || chars[ i ] == '\n' ) {
  308. attributeOnNewLine = true;
  309. }
  310. i++;
  311. }
  312. if ( i < chars.length ) {
  313. // consume attribute name
  314. while ( i < chars.length && chars[ i ] != '=' ) {
  315. qName.append( chars[ i ] );
  316. i++;
  317. }
  318. i++; // get past equals
  319. // consume whitespace between equals sign and start of attribute value
  320. while ( i < chars.length && Character.isWhitespace( chars[ i ] ) ) {
  321. i++; // get past whitespace before first quote
  322. }
  323. // consume attribute value start quote
  324. if ( i < chars.length ) {
  325. if ( chars[ i ] != '\"' && chars[ i ] != '\'' ) {
  326. throw new SAXException( "value for attribute " + qName.toString().trim() + " must be in quotes" );
  327. }
  328. else {
  329. quote = chars[ i ];
  330. i++; // get past first quote
  331. }
  332. }
  333. // consume attribute value
  334. while ( i < chars.length && chars[ i ] != quote ) {
  335. value.append( chars[ i ] );
  336. i++;
  337. }
  338. i++; // get past quote
  339. String type = attributeOnNewLine ? ON_NEW_LINE : "";
  340. attributes.addAttribute( "", "", qName.toString().trim(), type, value.toString() );
  341. }
  342. }
  343. }
  344. private int writeClosingTag( int start ) throws IOException, SAXException {
  345. int end = xml.indexOf( '>', start );
  346. writeRemaining( start, end );
  347. if ( okToContinue ) {
  348. isClosingTag = true;
  349. endElement( "", "", xml.substring( start + 2, end ).trim() );
  350. end = end + 1;
  351. }
  352. return end;
  353. }
  354. private int writeDocType( int start ) throws IOException {
  355. int end = xml.indexOf( '>', start );
  356. int bracketStart = xml.indexOf( '[', start );
  357. if ( bracketStart != -1 && bracketStart < end ) {
  358. int bracketEnd = xml.indexOf( ']', bracketStart );
  359. end = xml.indexOf( '>', bracketEnd );
  360. }
  361. writeRemaining( start, end );
  362. if ( okToContinue ) {
  363. end = end + 1;
  364. writer.write( "\n" );
  365. int length = end - start;
  366. writer.write( xml, start, length );
  367. isDocType = true;
  368. }
  369. return end;
  370. }
  371. private int writeXmlDeclarationOrProcessingInstruction( int start ) throws IOException, SAXException {
  372. int end;
  373. if ( xml.startsWith( "<?xml ", start ) ) {
  374. end = writeXmlDeclaration( start );
  375. }
  376. else {
  377. end = writeProcessingInstruction( start );
  378. }
  379. return end;
  380. }
  381. private int writeProcessingInstruction( int start ) throws IOException, SAXException {
  382. int end = xml.indexOf( "?>", start );
  383. writeRemaining( start, end );
  384. if ( okToContinue ) {
  385. int targetEnd = xml.indexOf( ' ', start );
  386. String target = xml.substring( start + "<?".length(), targetEnd );
  387. String data = xml.substring( targetEnd + 1, end );
  388. processingInstruction( target, data );
  389. end = end + "?>".length();
  390. }
  391. return end;
  392. }
  393. private int writeXmlDeclaration( int start ) throws IOException {
  394. int end = xml.indexOf( "?>", start );
  395. writeRemaining( start, end );
  396. if ( okToContinue ) {
  397. end = end + "?>".length();
  398. int length = end - start;
  399. writer.write( xml, start, length );
  400. }
  401. return end;
  402. }
  403. private int writeComment( int start ) throws IOException, SAXException {
  404. int end = xml.indexOf( "-->", start );
  405. writeRemaining( start, end );
  406. if ( okToContinue ) {
  407. int commentTextStart = start + "<!--".length();
  408. int commentTextLength = end - commentTextStart;
  409. comment( chars, commentTextStart, commentTextLength );
  410. end = end + "-->".length();
  411. }
  412. return end;
  413. }
  414. private void writeTextPrecedingLessThan( int start, int end ) throws IOException, SAXException {
  415. writeRemaining( start, end );
  416. if ( okToContinue && end > start ) {
  417. int length = end - start;
  418. characters( chars, start, length );
  419. }
  420. }
  421. private void writeRemaining( int start, int end ) throws IOException {
  422. if ( end == -1 ) {
  423. int length = xml.length() - start;
  424. writer.write( xml, start, length );
  425. okToContinue = false;
  426. }
  427. }
  428. }