in formatter/src/main/java/org/apache/royale/formatter/MXMLTokenFormatter.java [100:331]
private String parseTokens(String filePath, String text, List<IMXMLToken> tokens, Collection<ICompilerProblem> problems) throws Exception {
indent = 0;
numRequiredNewLines = 0;
requiredSpace = false;
inOpenTag = false;
inCloseTag = false;
skipFormatting = false;
attributeIndent = "";
prevToken = null;
prevTokenOrExtra = null;
token = null;
nextToken = null;
elementStack = new ArrayList<ElementStackItem>();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < tokens.size(); i++) {
token = tokens.get(i);
nextToken = null;
if (i < (tokens.size() - 1)) {
nextToken = tokens.get(i + 1);
}
if (token.getType() == TOKEN_TYPE_EXTRA) {
if (skipFormatting) {
builder.append(token.getText());
} else {
if (i == (tokens.size() - 1)) {
// if the last token is whitespace, include at most one
// new line, but strip the rest
numRequiredNewLines = Math.min(1, Math.max(0, countNewLinesInExtra(token)));
appendNewLines(builder, numRequiredNewLines);
break;
}
numRequiredNewLines = Math.max(numRequiredNewLines, countNewLinesInExtra(token));
}
prevTokenOrExtra = token;
continue;
} else if (token.getType() == MXMLTokenTypes.TOKEN_WHITESPACE) {
if (skipFormatting) {
builder.append(token.getText());
} else {
if (elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
numRequiredNewLines = Math.max(numRequiredNewLines, countNewLinesInExtra(token));
} else {
// if the parent element contains text, treat whitespace
// the same as text, and don't reformat it
// text is never reformatted because some components use it
// without collapsing whitespace, and developers would be
// confused if whitespace that they deliberately added were
// to be removed
builder.append(token.getText());
}
if (i == (tokens.size() - 1)) {
// if the last token is whitespace, include at most one
// new line, but strip the rest
numRequiredNewLines = Math.min(1, numRequiredNewLines);
appendNewLines(builder, numRequiredNewLines);
}
}
continue;
} else if (token.getType() == MXMLTokenTypes.TOKEN_OPEN_TAG_START
&& SCRIPT_START_PATTERN.matcher(token.getText()).matches()) {
if (prevToken != null && numRequiredNewLines > 0) {
appendNewLines(builder, numRequiredNewLines);
}
StringBuilder scriptBuilder = new StringBuilder();
scriptBuilder.append(token.getText());
boolean inScriptCloseTag = false;
while (i < (tokens.size() - 1)) {
i++;
token = tokens.get(i);
scriptBuilder.append(token.getText());
if (token.getType() == MXMLTokenTypes.TOKEN_CLOSE_TAG_START) {
inScriptCloseTag = true;
} else if (inScriptCloseTag && token.getType() == MXMLTokenTypes.TOKEN_TAG_END) {
break;
}
}
if (problems == null) {
// we need to know if there were problems because it means that we
// need to return the original, unformatted text
problems = new ArrayList<ICompilerProblem>();
}
builder.append(formatMXMLScriptElement(filePath, token.getLine(), scriptBuilder.toString(), problems));
if (hasErrors(problems)) {
return text;
}
prevToken = token;
prevTokenOrExtra = token;
requiredSpace = false;
numRequiredNewLines = 1;
continue;
}
// characters that must appear before the token
switch (token.getType()) {
case MXMLTokenTypes.TOKEN_OPEN_TAG_START: {
inOpenTag = true;
// if the parent contains text, children should be the same
boolean containsText = !elementStack.isEmpty()
&& elementStack.get(elementStack.size() - 1).containsText;
elementStack.add(new ElementStackItem(token, token.getText().substring(1), containsText));
break;
}
case MXMLTokenTypes.TOKEN_CLOSE_TAG_START: {
if (elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
indent = decreaseIndent(indent);
}
inCloseTag = true;
break;
}
case MXMLTokenTypes.TOKEN_NAME: {
requiredSpace = true;
break;
}
}
if (!skipFormatting && prevToken != null) {
if (numRequiredNewLines > 0) {
appendNewLines(builder, numRequiredNewLines);
appendIndent(builder, indent);
if (attributeIndent.length() > 0) {
builder.append(attributeIndent);
}
} else if (requiredSpace) {
builder.append(' ');
}
}
// include the token's own text
// no token gets reformatted before being appended
// whitespace is the only special case, but that's not handled here
builder.append(token.getText());
// characters that must appear after the token
requiredSpace = false;
numRequiredNewLines = 0;
switch (token.getType()) {
case MXMLTokenTypes.TOKEN_PROCESSING_INSTRUCTION: {
numRequiredNewLines = Math.max(numRequiredNewLines, 1);
break;
}
case MXMLTokenTypes.TOKEN_CLOSE_TAG_START: {
if (nextToken != null && nextToken.getType() != MXMLTokenTypes.TOKEN_TAG_END
&& nextToken.getType() != MXMLTokenTypes.TOKEN_EMPTY_TAG_END
&& nextToken.getType() != TOKEN_TYPE_EXTRA) {
requiredSpace = true;
}
if (elementStack.isEmpty()) {
// something is very wrong!
return text;
}
String elementName = token.getText().substring(2);
ElementStackItem elementItem = elementStack.remove(elementStack.size() - 1);
if (!elementName.equals(elementItem.elementName)) {
// there's a unclosed tag with a different name somewhere
return text;
}
break;
}
case MXMLTokenTypes.TOKEN_OPEN_TAG_START: {
if (nextToken != null && nextToken.getType() != MXMLTokenTypes.TOKEN_TAG_END
&& nextToken.getType() != MXMLTokenTypes.TOKEN_EMPTY_TAG_END) {
attributeIndent = getAttributeIndent(token);
if (nextToken.getType() != TOKEN_TYPE_EXTRA) {
requiredSpace = true;
}
}
break;
}
case MXMLTokenTypes.TOKEN_TAG_END: {
if (inOpenTag) {
ElementStackItem element = elementStack.get(elementStack.size() - 1);
if (!element.containsText) {
element.containsText = elementContainsText(tokens, i + 1, element.token);
}
if (elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
indent = increaseIndent(indent);
}
} else {
if (elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
numRequiredNewLines = Math.max(numRequiredNewLines, 1);
}
}
inOpenTag = false;
attributeIndent = "";
inCloseTag = false;
break;
}
case MXMLTokenTypes.TOKEN_EMPTY_TAG_END: {
if (inOpenTag) {
elementStack.remove(elementStack.size() - 1);
} else {
if (elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
numRequiredNewLines = Math.max(numRequiredNewLines, 1);
}
}
inOpenTag = false;
// no need to change nested indent after this tag
// however, we may need to remove attribute indent
attributeIndent = "";
// we shouldn't find an empty close tag, but clear flag anyway
inCloseTag = false;
break;
}
case MXMLTokenTypes.TOKEN_STRING: {
if (inOpenTag && settings.mxmlInsertNewLineBetweenAttributes && nextToken != null
&& nextToken.getType() != MXMLTokenTypes.TOKEN_TAG_END
&& nextToken.getType() != MXMLTokenTypes.TOKEN_EMPTY_TAG_END) {
numRequiredNewLines = Math.max(numRequiredNewLines, 1);
}
break;
}
case MXMLTokenTypes.TOKEN_COMMENT: {
String tokenText = token.getText();
String trimmed = tokenText.substring(4, tokenText.length() - 3).trim();
if (!skipFormatting && FORMATTER_TAG_OFF.equals(trimmed)) {
skipFormatting = true;
} else if (skipFormatting && FORMATTER_TAG_ON.equals(trimmed)) {
skipFormatting = false;
}
break;
}
}
prevToken = token;
prevTokenOrExtra = token;
}
return builder.toString();
}