private String parseTokens()

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();
	}