All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.source.formatter.check.XMLIndentationCheck Maven / Gradle / Ivy

The newest version!
/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

package com.liferay.source.formatter.check;

import com.liferay.petra.string.CharPool;
import com.liferay.petra.string.StringBundler;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.tools.ToolsUtil;
import com.liferay.source.formatter.check.util.XMLSourceUtil;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Hugo Huijser
 */
public class XMLIndentationCheck extends BaseFileCheck {

	@Override
	protected String doProcess(
		String fileName, String absolutePath, String content) {

		content = _fixLineBreak(content);

		while (true) {
			String newContent = _fixTagsIndentation(content);

			if (newContent.equals(content)) {
				return newContent;
			}

			content = newContent;
		}
	}

	private String _fixIndentation(
		String content, String line, int expectedTabCount, int lineNumber) {

		StringBundler sb = new StringBundler(expectedTabCount + 1);

		for (int i = 0; i < expectedTabCount; i++) {
			sb.append(CharPool.TAB);
		}

		sb.append(StringUtil.trim(line));

		String newLine = sb.toString();

		if (line.equals(newLine)) {
			return content;
		}

		return StringUtil.replaceFirst(
			content, line, newLine, getLineStartPos(content, lineNumber));
	}

	private String _fixLineBreak(String content) {
		int x = -1;

		while (true) {
			x = content.indexOf(">", x + 2);

			if (x == -1) {
				break;
			}

			String s = StringUtil.trimLeading(content.substring(x + 1), '\n');

			if (Validator.isNull(s) || StringUtil.startsWith(s, "\n") ||
				!s.startsWith("<") || s.startsWith("") ||
				XMLSourceUtil.isInsideCDATAMarkup(content, matcher.start()) ||
				XMLSourceUtil.isInsideComment(content, matcher.start())) {

				continue;
			}

			return StringUtil.replaceFirst(
				content, matcher.group(2), "\n", matcher.start(2));
		}

		matcher = _incorrectLineBreakPattern2.matcher(content);

		while (matcher.find()) {
			if (XMLSourceUtil.isInsideCDATAMarkup(content, matcher.start()) ||
				XMLSourceUtil.isInsideComment(content, matcher.start())) {

				continue;
			}

			return StringUtil.replaceFirst(
				content, matcher.group(2), "\n", matcher.start(2));
		}

		return content;
	}

	private String _fixMultiLineTagAttributesIndentation(
		String content, String[] lines, TokenOccurrence previousTokenOccurrence,
		TokenOccurrence tokenOccurrence, int expectedTabCount) {

		if ((previousTokenOccurrence == null) ||
			(tokenOccurrence.getLineNumber() ==
				previousTokenOccurrence.getLineNumber())) {

			return content;
		}

		String previousToken = previousTokenOccurrence.getToken();

		if (!previousToken.equals(_TAG_OPEN)) {
			return content;
		}

		String token = tokenOccurrence.getToken();

		if (!token.equals(_MULTI_LINE_TAG_CLOSE) && !token.equals(_TAG_CLOSE)) {
			return content;
		}

		String startLine = lines[previousTokenOccurrence.getLineNumber() - 1];

		if (!startLine.matches("\\s*<[-\\w:]+")) {
			return content;
		}

		for (int i = previousTokenOccurrence.getLineNumber() + 1;
			 i < tokenOccurrence.getLineNumber(); i++) {

			String line = lines[i - 1];

			if (line.matches("\\s*[\\w-]+=.*")) {
				content = _fixIndentation(content, line, expectedTabCount, i);
			}
		}

		return content;
	}

	private String _fixTagIndentation(
		String content, String line, TokenOccurrence tokenOccurrence,
		int expectedTabCount) {

		if (tokenOccurrence.getLinePos() != 0) {
			return content;
		}

		String token = tokenOccurrence.getToken();

		if (token.equals(_CDATA_CLOSE) || token.equals(_CDATA_OPEN) ||
			token.equals(_COMMENT_TAG_CLOSE)) {

			return content;
		}

		if (token.equals(_CLOSING_TAG_OPEN) || token.equals(_TAG_CLOSE) ||
			token.equals(_MULTI_LINE_TAG_CLOSE)) {

			expectedTabCount--;
		}

		return _fixIndentation(
			content, line, expectedTabCount, tokenOccurrence.getLineNumber());
	}

	private String _fixTagsIndentation(String content) {
		String[] lines = StringUtil.splitLines(content);

		int level = 0;
		String[] tokens = {
			_COMMENT_TAG_OPEN, _DOCTYPE_TAG_OPEN, _HEADER_TAG_OPEN, _TAG_OPEN
		};

		TokenOccurrence previousTokenOccurrence = null;

		while (true) {
			TokenOccurrence tokenOccurrence = _getNextTokenOccurrence(
				lines, previousTokenOccurrence, tokens);

			if (tokenOccurrence == null) {
				return content;
			}

			String newContent = _fixTagIndentation(
				content, lines[tokenOccurrence.getLineNumber() - 1],
				tokenOccurrence, level);

			newContent = _fixMultiLineTagAttributesIndentation(
				newContent, lines, previousTokenOccurrence, tokenOccurrence,
				level);

			if (!newContent.equals(content)) {
				return newContent;
			}

			String token = tokenOccurrence.getToken();

			if (token.equals(_CDATA_OPEN)) {
				tokens = new String[] {_CDATA_CLOSE};
			}
			else if (token.equals(_CLOSING_TAG_OPEN)) {
				level--;
				tokens = new String[] {_TAG_CLOSE};
			}
			else if (token.equals(_COMMENT_TAG_OPEN)) {
				tokens = new String[] {_COMMENT_TAG_CLOSE};
			}
			else if (token.equals(_DOCTYPE_TAG_OPEN) ||
					 token.equals(_HEADER_TAG_OPEN)) {

				tokens = new String[] {_TAG_CLOSE};
			}
			else if (token.equals(_TAG_OPEN)) {
				level++;
				tokens = new String[] {_MULTI_LINE_TAG_CLOSE, _TAG_CLOSE};
			}
			else {
				if (token.equals(_MULTI_LINE_TAG_CLOSE)) {
					level--;
				}

				tokens = new String[] {
					_CDATA_OPEN, _CLOSING_TAG_OPEN, _COMMENT_TAG_OPEN,
					_DOCTYPE_TAG_OPEN, _HEADER_TAG_OPEN, _TAG_OPEN
				};
			}

			previousTokenOccurrence = tokenOccurrence;
		}
	}

	private int _getFirstMatchPos(String line, int startPos, String token) {
		int pos = startPos;

		while (true) {
			pos = line.indexOf(token, pos + 1);

			if ((pos == -1) ||
				(!token.equals(_MULTI_LINE_TAG_CLOSE) &&
				 !token.equals(_TAG_CLOSE))) {

				return pos;
			}

			int i = Math.max(0, startPos);

			if (!ToolsUtil.isInsideQuotes(line.substring(i), pos - i, false)) {
				return pos;
			}
		}
	}

	private TokenOccurrence _getNextTokenOccurrence(
		String[] lines, TokenOccurrence previousTokenOccurrence,
		String... tokens) {

		int startLine = 1;
		int startPos = -1;

		if (previousTokenOccurrence != null) {
			startLine = previousTokenOccurrence.getLineNumber();
			startPos = previousTokenOccurrence.getLinePos();
		}

		String match = null;
		int min = -1;

		for (int i = startLine; i < lines.length; i++) {
			String line = StringUtil.trim(lines[i - 1]);

			if (Validator.isNull(line)) {
				continue;
			}

			for (String token : tokens) {
				int matchPos = _getFirstMatchPos(line, startPos, token);

				if (matchPos == -1) {
					continue;
				}

				if ((min == -1) || (min > matchPos)) {
					match = token;
					min = matchPos;
				}
			}

			if (min != -1) {
				return new TokenOccurrence(i, min, match);
			}

			startPos = -1;
		}

		return null;
	}

	private static final String _CDATA_CLOSE = "]]>";

	private static final String _CDATA_OPEN = "";

	private static final String _COMMENT_TAG_OPEN = "