--- a/xml.text/src/org/netbeans/modules/xml/text/indent/XMLLexerFormatter.java Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/src/org/netbeans/modules/xml/text/indent/XMLLexerFormatter.java Wed Feb 03 22:20:16 2010 +0000 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. + * Copyright 1997-2010 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common @@ -42,9 +42,9 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.Stack; -import javax.swing.text.AbstractDocument; import javax.swing.text.BadLocationException; import javax.swing.text.Element; import org.netbeans.api.lexer.LanguagePath; @@ -53,7 +53,6 @@ import org.netbeans.api.lexer.TokenSequence; import org.netbeans.api.xml.lexer.XMLTokenId; import org.netbeans.editor.BaseDocument; -import org.netbeans.editor.Formatter; import org.netbeans.editor.Utilities; import org.netbeans.modules.editor.indent.api.IndentUtils; import org.netbeans.modules.editor.indent.spi.Context; @@ -61,7 +60,7 @@ import org.netbeans.modules.editor.structure.formatting.TagBasedLexerFormatter; import org.netbeans.modules.xml.text.folding.TokenElement; import org.netbeans.modules.xml.text.folding.TokenElement.TokenType; -import org.netbeans.modules.xml.text.syntax.XMLKit; +import org.netbeans.modules.xml.text.folding.XmlFoldManager; /** * New XML formatter based on Lexer APIs. @@ -218,23 +217,28 @@ } // # 170343 -// @Override -// public void reformat(Context context, final int startOffset, final int endOffset) -// throws BadLocationException { -// final BaseDocument doc = (BaseDocument) context.document(); -// doc.render(new Runnable() { -// public void run() { -// doReformat(doc, startOffset, endOffset); -// } -// }); -// } + @Override + public void reformat(Context context, final int startOffset, final int endOffset) + throws BadLocationException { + final BaseDocument doc = (BaseDocument) context.document(); + doc.render(new Runnable() { + + public void run() { + doReformat(doc, startOffset, endOffset); + } + }); + } public BaseDocument doReformat(BaseDocument doc, int startOffset, int endOffset) { spacesPerTab = IndentUtils.indentLevelSize(doc); try { - List tags = getTags(doc, startOffset, endOffset); + List tags = getTags(doc, startOffset, endOffset); for (int i = tags.size() - 1; i >= 0; i--) { - TokenElement tag = tags.get(i); + if (tags.get(i).isPreserveIndent()) { + continue; + } + TokenElement tag = tags.get(i).getToken(); + int so = tag.getStartOffset(); int lineOffset = Utilities.getLineOffset(doc, so); String tagName = tag.getName(); @@ -252,7 +256,7 @@ int ndx = lineStr.lastIndexOf(tagName); if (ndx != -1) { lineStr = lineStr.substring(0, ndx); - int ndx2 = lineStr.lastIndexOf("<" + tagName.substring(2) ); + int ndx2 = lineStr.lastIndexOf("<" + tagName.substring(2)); if (ndx2 == -1) {//no start found in this line, so indent this tag changePrettyText(doc, tag, so); } else { @@ -266,20 +270,18 @@ } else { changePrettyText(doc, tag, so); } - } + } } catch (BadLocationException ble) { //ignore exception } catch (IOException iox) { - //ignore exception + //ignore exception } finally { - //((AbstractDocument)doc).readUnlock(); + //((AbstractDocument)doc).readUnlock(); } return doc; } private void changePrettyText(BaseDocument doc, TokenElement tag, int so) throws BadLocationException { - Formatter formatter = Formatter.getFormatter(XMLKit.class); - formatter.setExpandTabs(false); //i expected the call IndentUtils.createIndentString() to return //the correct string for the indent level, but it doesn't. //so this is just a workaround. @@ -292,54 +294,47 @@ int i = Utilities.getRowFirstNonWhite(doc, so); int rowStart = Utilities.getRowStart(doc, so); doc.insertString(so, newIndentText, null); - doc.remove(rowStart, i - rowStart); + doc.remove(rowStart, i - rowStart); } else { - doc.insertString(so, "\n" + newIndentText, null); + doc.insertString(so, "\n" + newIndentText, null); } } /** - * This is the core of the fold creation algorithm. - * This method parses the document using lexer and creates folds and adds - * them to the fold hierarchy. + * This is the core of the formatting algorithm. It was originally derived + * from {@link XmlFoldManager#createFolds(org.netbeans.spi.editor.fold.FoldHierarchyTransaction)}. + * Like that method, this method parses the document using lexer. Rather + * than creating folds though, this method reformats by manipulating the + * whitespace tokens. To do this it keeps track of the nesting level of the + * XML and the use of the xml:space attribute. Together they are used to + * calculate how much each token should be indented by. */ - private List getTags(BaseDocument basedoc, int startOffset, int endOffset) + private List getTags(BaseDocument basedoc, int startOffset, int endOffset) throws BadLocationException, IOException { - List tags = new ArrayList(); + List tags = new ArrayList(); + + // List to keep track of whether whitespace is to be preserved at each + // level of nesting. By default whitesapce is not preserved. + LinkedList preserveNesting_outdent = new LinkedList(); + preserveNesting_outdent.add(Boolean.FALSE); + + // flag that is true if whitespace is currently + // to not be changed. That is, xml:space + // was last set to "preserve". + boolean preserveWhitespace = false; + + // flag to indicate if the current + // argument is xml:space + boolean settingSpaceValue = false; + + int indentLevel = -1; basedoc.readLock(); - int incrIndentLevelBy = 0; try { - //are we formatting from the beginning of doc or a subsection - int line = Utilities.getLineOffset(basedoc, startOffset); - if(line > 0) { - boolean nested = isSubSectionToFormatNested(basedoc, startOffset); - //we are formatting a subsection - int precedingWordLoc = Utilities.getFirstNonWhiteBwd(basedoc, startOffset) ; - int previousLine = Utilities.getLineOffset(basedoc, precedingWordLoc); - int previousLineIndentation = 0; - //we need to get the previous line and find its indentation - //the previous line will be ( current line - 1), if the user has - //has selected the entire line to format - //the previous line will be the same as current line if user - //selected a part of line to format - if(previousLine != line){ - previousLineIndentation = Utilities.getRowIndent(basedoc, precedingWordLoc); - } else - previousLineIndentation = Utilities.getRowIndent(basedoc, startOffset); - //the section being formatted should be idented wrt to previous line's indentation - - int div = previousLineIndentation / spacesPerTab; - if(nested) - incrIndentLevelBy = div +1; - else - incrIndentLevelBy = div; - - } TokenHierarchy tokenHierarchy = TokenHierarchy.get(basedoc); TokenSequence tokenSequence = tokenHierarchy.tokenSequence(); org.netbeans.api.lexer.Token token = tokenSequence.token(); - // Add the text token, if any, before xml decalration to document node + // Add the text token, if any, before xml declaration to document node if (token != null && token.id() == XMLTokenId.TEXT) { if (tokenSequence.moveNext()) { token = tokenSequence.token(); @@ -347,21 +342,25 @@ } int currentTokensSize = 0; Stack stack = new Stack(); - String currentNode = null; while (tokenSequence.moveNext()) { token = tokenSequence.token(); XMLTokenId tokenId = token.id(); String image = token.text().toString(); - if( ! (tokenSequence.offset() >= startOffset && tokenSequence.offset() endOffset) { + break; } + boolean tokenInSelectionRange = tokenSequence.offset() >= startOffset; TokenType tokenType = TokenType.TOKEN_WHITESPACE; switch (tokenId) { - case TAG: { + case TAG: { // Tag is encountered and the required level of indenting determined. + // The tokens are only assessed if they are in the selection + // range, which is the whole document if no text is selected. int len = image.length(); if (image.charAt(len - 1) == '>') {// '/>' if (len == 2) { + if (!preserveWhitespace) { + --indentLevel; + } if (!stack.empty()) { stack.pop(); } @@ -369,27 +368,35 @@ } else { tokenType = TokenType.TOKEN_ELEMENT_START_TAG; if (image.startsWith("")) - return false; - if(tagText.startsWith("<")) - return true; - } - } catch (Exception ex) { - //return false anyway - } finally { - doc.readUnlock(); - } - return false; + + /** + * The formatter needs to keep track of when it can remove whitespace and + * when it must preserve whitespace as defined by the xml:space attribute. + * This class associates a flag that defines whether whitespace is to be + * preserved with the other token data that is used in the code folding + * algorithm. + */ + private class TokenIndent { + + private TokenElement token; + private boolean preserveIndent; + + public TokenIndent(TokenElement token, boolean preserveIndent) { + this.token = token; + this.preserveIndent = preserveIndent; + } + + public TokenElement getToken() { + return token; + } + + public boolean isPreserveIndent() { + return preserveIndent; + } + + public void setPreserveIndent(boolean preserveIndent) { + this.preserveIndent = preserveIndent; + } + + @Override + public String toString() { + return "TokenIndent: name=" + token.getName() + " preserveIndent=" + preserveIndent; + } } } --- a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/XMLLexerFormatterTest.java Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/XMLLexerFormatterTest.java Wed Feb 03 22:20:16 2010 +0000 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. + * Copyright 1997-2010 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common @@ -48,7 +48,7 @@ * @author Samaresh (samaresh.panda@sun.com) */ public class XMLLexerFormatterTest extends AbstractTestCase { - + public XMLLexerFormatterTest(String testName) { super(testName); } @@ -61,9 +61,12 @@ suite.addTest(new XMLLexerFormatterTest("testFormatPerformance")); suite.addTest(new XMLLexerFormatterTest("testFormatSubsection1")); suite.addTest(new XMLLexerFormatterTest("testFormatSubsection2")); + suite.addTest(new XMLLexerFormatterTest("testFormat_PreserveWhitespace")); + suite.addTest(new XMLLexerFormatterTest("testFormat_WithNestedPreserveWhitespace")); + suite.addTest(new XMLLexerFormatterTest("testFormatSubsection_PreserveWhitespace")); return suite; } - + /** * Formats an input document and then compares the formatted doc * with a document that represents expected outcome. @@ -74,20 +77,20 @@ XMLLexerFormatter formatter = new XMLLexerFormatter(null); BaseDocument formattedDoc = formatter.doReformat(inputDoc, 0, inputDoc.getLength()); System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); - BaseDocument outputDoc = getDocument("indent/output.xml"); - assert(compare(formattedDoc, outputDoc)); + BaseDocument outputDoc = getDocument("indent/output.xml"); + assert (compare(formattedDoc, outputDoc)); } - + public void testFormatSubsection() throws Exception { BaseDocument inputDoc = getDocument("indent/input_sub.xml"); //format a subsection of the inputDoc XMLLexerFormatter formatter = new XMLLexerFormatter(null); BaseDocument formattedDoc = formatter.doReformat(inputDoc, 72, 97); System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); - BaseDocument outputDoc = getDocument("indent/output_sub.xml"); - assert(compare(formattedDoc, outputDoc)); + BaseDocument outputDoc = getDocument("indent/output_sub.xml"); + assert (compare(formattedDoc, outputDoc)); } - + //for bug 139160 public void testFormatForTab() throws Exception { BaseDocument inputDoc = getDocument("indent/input2.xsd"); @@ -95,11 +98,10 @@ XMLLexerFormatter formatter = new XMLLexerFormatter(null); BaseDocument formattedDoc = formatter.doReformat(inputDoc, 0, inputDoc.getLength()); System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); - BaseDocument outputDoc = getDocument("indent/output2.xsd"); - assert(compare(formattedDoc, outputDoc)); + BaseDocument outputDoc = getDocument("indent/output2.xsd"); + assert (compare(formattedDoc, outputDoc)); } - - + public void testFormatPerformance() throws Exception { BaseDocument inputDoc = getDocument("indent/1998stats.xml"); //format the inputDoc @@ -107,8 +109,8 @@ long t1 = System.currentTimeMillis(); formatter.doReformat(inputDoc, 0, inputDoc.getLength()); long t2 = System.currentTimeMillis(); - System.out.println("Time taken to format NFL XML in ms:: " + (t2-t1) ); - + System.out.println("Time taken to format NFL XML in ms:: " + (t2 - t1)); + //try OTA Schema inputDoc = getDocument("indent/1998stats.xml"); //format the inputDoc @@ -116,25 +118,60 @@ t1 = System.currentTimeMillis(); formatter.doReformat(inputDoc, 0, inputDoc.getLength()); t2 = System.currentTimeMillis(); - System.out.println("Time taken to format OTA Schema in ms:: " + (t2-t1) ); + System.out.println("Time taken to format OTA Schema in ms:: " + (t2 - t1)); } + public void testFormatSubsection1() throws Exception { BaseDocument inputDoc = getDocument("indent/input_sub1.xml"); //format a subsection of the inputDoc XMLLexerFormatter formatter = new XMLLexerFormatter(null); - BaseDocument formattedDoc = formatter.doReformat(inputDoc, 39, 68); + BaseDocument formattedDoc = formatter.doReformat(inputDoc, 46, 74); System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); - BaseDocument outputDoc = getDocument("indent/output_sub1.xml"); - assert(compare(formattedDoc, outputDoc)); + BaseDocument outputDoc = getDocument("indent/output_sub1.xml"); + assert (compare(formattedDoc, outputDoc)); } - + public void testFormatSubsection2() throws Exception { BaseDocument inputDoc = getDocument("indent/input_sub2.xml"); //format a subsection of the inputDoc XMLLexerFormatter formatter = new XMLLexerFormatter(null); - BaseDocument formattedDoc = formatter.doReformat(inputDoc, 68, 83); + BaseDocument formattedDoc = formatter.doReformat(inputDoc, 51, 80); System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); - BaseDocument outputDoc = getDocument("indent/output_sub2.xml"); - assert(compare(formattedDoc, outputDoc)); + BaseDocument outputDoc = getDocument("indent/output_sub2.xml"); + assert (compare(formattedDoc, outputDoc)); + } + + //for bug 170343 + public void testFormat_PreserveWhitespace() throws Exception { + BaseDocument inputDoc = getDocument("indent/input_preserve.xml"); + //format the inputDoc + XMLLexerFormatter formatter = new XMLLexerFormatter(null); + BaseDocument formattedDoc = formatter.doReformat(inputDoc, 0, inputDoc.getLength()); + System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); + BaseDocument outputDoc = getDocument("indent/output_preserve.xml"); + assert (compare(formattedDoc, outputDoc)); + } + + //for bug 170343 + public void testFormat_WithNestedPreserveWhitespace() throws Exception { + BaseDocument inputDoc = getDocument("indent/input_withpreserve.xml"); + //format the inputDoc + XMLLexerFormatter formatter = new XMLLexerFormatter(null); + BaseDocument formattedDoc = formatter.doReformat(inputDoc, 0, inputDoc.getLength()); + System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); + BaseDocument outputDoc = getDocument("indent/output_withpreserve.xml"); + assert (compare(formattedDoc, outputDoc)); + } + + //for bug 170343 + public void testFormatSubsection_PreserveWhitespace() throws Exception { + BaseDocument inputDoc = getDocument("indent/input_preserve.xml"); + //format the inputDoc + XMLLexerFormatter formatter = new XMLLexerFormatter(null); + System.out.println("SECTION:" + inputDoc.getText(91, 87)); + BaseDocument formattedDoc = formatter.doReformat(inputDoc, 91, 91 + 87); + System.out.println(formattedDoc.getText(0, formattedDoc.getLength())); + BaseDocument outputDoc = getDocument("indent/output_preserve.xml"); + assert (compare(formattedDoc, outputDoc)); } } --- a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/input.xml Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/input.xml Wed Feb 03 22:20:16 2010 +0000 @@ -1,31 +1,32 @@ + + + hello world + - - hello world - + + - - - - - - test - - - - + - - - - test - - + test - - - 1 - + + + + + + + + test + + + + + + 1 + + --- a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/input_sub1.xml Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/input_sub1.xml Wed Feb 03 22:20:16 2010 +0000 @@ -1,5 +1,5 @@ + - + - --- a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/input_sub2.xml Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/input_sub2.xml Wed Feb 03 22:20:16 2010 +0000 @@ -1,4 +1,5 @@ + + - --- a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/output.xml Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/output.xml Wed Feb 03 22:20:16 2010 +0000 @@ -1,33 +1,34 @@ + + + + hello world + + - - - hello world - - + + - - - - - - test - - - - + - - - - test - - + test - - - 1 - + + + + + + + + test + + + + + + 1 + + --- a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/output_sub1.xml Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/output_sub1.xml Wed Feb 03 22:20:16 2010 +0000 @@ -1,5 +1,5 @@ - + + - --- a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/output_sub2.xml Wed Feb 03 19:20:09 2010 +0100 +++ a/xml.text/test/unit/src/org/netbeans/modules/xml/text/indent/output_sub2.xml Wed Feb 03 22:20:16 2010 +0000 @@ -1,4 +1,5 @@ + + -