/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.source.formatter.checks;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.source.formatter.checks.comparator.ElementComparator;
import com.liferay.source.formatter.checks.util.SourceUtil;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.dom4j.Document;
import org.dom4j.Element;
/**
* @author Hugo Huijser
*/
public class XMLPoshiFileCheck extends BaseFileCheck {
@Override
protected String doProcess(
String fileName, String absolutePath, String content)
throws Exception {
if (fileName.endsWith(".action") || fileName.endsWith(".function") ||
fileName.endsWith(".macro") || fileName.endsWith(".testcase")) {
content = _formatPoshiXML(fileName, content);
}
return content;
}
private void _checkPoshiCharactersAfterDefinition(
String fileName, String content) {
if (content.contains("/definition>") &&
!content.endsWith("/definition>")) {
addMessage(fileName, "Characters found after definition element");
}
}
private void _checkPoshiCharactersBeforeDefinition(
String fileName, String content) {
if (!content.startsWith("<definition")) {
addMessage(fileName, "Characters found before definition element");
}
}
private String _fixPoshiXMLElementWithNoChild(String content) {
Matcher matcher = _poshiElementWithNoChildPattern.matcher(content);
while (matcher.find()) {
content = StringUtil.replace(content, matcher.group(), "\" />");
}
return content;
}
private String _fixPoshiXMLEndLines(String content) {
Matcher matcher = _poshiEndLinesPattern.matcher(content);
while (matcher.find()) {
String statement = matcher.group();
String newStatement = StringUtil.replace(
statement, matcher.group(), ">\n\n" + matcher.group(1));
content = StringUtil.replace(content, statement, newStatement);
}
return content;
}
private String _fixPoshiXMLEndLinesAfterClosingElement(String content) {
Matcher matcher = _poshiEndLinesAfterClosingElementPattern.matcher(
content);
while (matcher.find()) {
String statement = matcher.group();
String closingElementName = matcher.group(1);
if (StringUtil.equalsIgnoreCase("</and>", closingElementName) ||
StringUtil.equalsIgnoreCase("</elseif>", closingElementName) ||
StringUtil.equalsIgnoreCase("</not>", closingElementName) ||
StringUtil.equalsIgnoreCase("</or>", closingElementName) ||
StringUtil.equalsIgnoreCase("</then>", closingElementName)) {
String newStatement = StringUtil.replace(
statement, matcher.group(2), "\n");
content = StringUtil.replace(content, statement, newStatement);
}
else if (!StringUtil.equalsIgnoreCase(
"</var>", closingElementName)) {
String newStatement = StringUtil.replace(
statement, matcher.group(2), "\n\n");
content = StringUtil.replace(content, statement, newStatement);
}
}
return content;
}
private String _fixPoshiXMLEndLinesBeforeClosingElement(String content) {
Matcher matcher = _poshiEndLinesBeforeClosingElementPattern.matcher(
content);
while (matcher.find()) {
String statement = matcher.group();
String newStatement = StringUtil.replace(
statement, matcher.group(1), "\n");
content = StringUtil.replace(content, statement, newStatement);
}
return content;
}
private String _fixPoshiXMLNumberOfTabs(String content) {
Matcher matcher = _poshiTabsPattern.matcher(content);
int tabCount = 0;
boolean ignoredCdataBlock = false;
boolean ignoredCommentBlock = false;
while (matcher.find()) {
String statement = matcher.group();
Matcher quoteWithSlashMatcher = _poshiQuoteWithSlashPattern.matcher(
statement);
String fixedQuoteStatement = statement;
if (quoteWithSlashMatcher.find()) {
fixedQuoteStatement = StringUtil.replace(
statement, quoteWithSlashMatcher.group(), "\"\"");
}
Matcher closingTagMatcher = _poshiClosingTagPattern.matcher(
fixedQuoteStatement);
Matcher openingTagMatcher = _poshiOpeningTagPattern.matcher(
fixedQuoteStatement);
Matcher wholeTagMatcher = _poshiWholeTagPattern.matcher(
fixedQuoteStatement);
if (closingTagMatcher.find() && !openingTagMatcher.find() &&
!wholeTagMatcher.find() && !statement.contains("<!--") &&
!statement.contains("-->") &&
!statement.contains("<![CDATA[") &&
!statement.contains("]]>")) {
tabCount--;
}
if (statement.contains("]]>")) {
ignoredCdataBlock = false;
}
else if (statement.contains("<![CDATA[")) {
ignoredCdataBlock = true;
}
if (statement.contains("-->")) {
ignoredCommentBlock = false;
}
else if (statement.contains("<!--")) {
ignoredCommentBlock = true;
}
if (!ignoredCommentBlock && !ignoredCdataBlock) {
StringBundler sb = new StringBundler(tabCount + 1);
for (int i = 0; i < tabCount; i++) {
sb.append(StringPool.TAB);
}
sb.append(StringPool.LESS_THAN);
String replacement = sb.toString();
if (!replacement.equals(matcher.group(1))) {
String newStatement = StringUtil.replace(
statement, matcher.group(1), replacement);
return StringUtil.replaceFirst(
content, statement, newStatement, matcher.start());
}
}
if (openingTagMatcher.find() && !closingTagMatcher.find() &&
!wholeTagMatcher.find() && !statement.contains("<!--") &&
!statement.contains("-->") &&
!statement.contains("<![CDATA[") &&
!statement.contains("]]>")) {
tabCount++;
}
}
return content;
}
private String _formatPoshiXML(String fileName, String content)
throws Exception {
_checkPoshiCharactersAfterDefinition(fileName, content);
_checkPoshiCharactersBeforeDefinition(fileName, content);
try {
Document document = SourceUtil.readXML(content);
Element rootElement = document.getRootElement();
List<Element> commandElements = rootElement.elements("command");
for (Element commandElement : commandElements) {
checkElementOrder(
fileName, commandElement, "property", null,
new ElementComparator());
}
}
catch (Exception e) {
}
content = _sortPoshiCommands(content);
content = _sortPoshiVariables(content);
content = _fixPoshiXMLElementWithNoChild(content);
content = _fixPoshiXMLEndLinesAfterClosingElement(content);
content = _fixPoshiXMLEndLinesBeforeClosingElement(content);
content = _fixPoshiXMLEndLines(content);
return _fixPoshiXMLNumberOfTabs(content);
}
private String _sortPoshiCommands(String content) {
Matcher matcher = _poshiCommandsPattern.matcher(content);
Map<String, String> commandBlocksMap = new TreeMap<>(
String.CASE_INSENSITIVE_ORDER);
String previousName = StringPool.BLANK;
boolean hasUnsortedCommands = false;
while (matcher.find()) {
String commandBlock = matcher.group();
String commandName = matcher.group(1);
commandBlocksMap.put(commandName, commandBlock);
if (!hasUnsortedCommands &&
(commandName.compareToIgnoreCase(previousName) < 0)) {
hasUnsortedCommands = true;
}
previousName = commandName;
}
if (!hasUnsortedCommands) {
return content;
}
StringBundler sb = new StringBundler();
matcher = _poshiSetUpPattern.matcher(content);
if (matcher.find()) {
String setUpBlock = matcher.group();
content = content.replace(setUpBlock, "");
sb.append(setUpBlock);
}
matcher = _poshiTearDownPattern.matcher(content);
if (matcher.find()) {
String tearDownBlock = matcher.group();
content = content.replace(tearDownBlock, "");
sb.append(tearDownBlock);
}
for (Map.Entry<String, String> entry : commandBlocksMap.entrySet()) {
sb.append("\n\t");
sb.append(entry.getValue());
sb.append("\n");
}
int x = content.indexOf("<command");
int y = content.lastIndexOf("</command>");
String commandBlock = content.substring(x, y);
commandBlock = "\n\t" + commandBlock + "</command>\n";
String newCommandBlock = sb.toString();
return StringUtil.replaceFirst(content, commandBlock, newCommandBlock);
}
private String _sortPoshiVariables(String content) {
Matcher matcher = _poshiVariablesBlockPattern.matcher(content);
while (matcher.find()) {
String previousName = StringPool.BLANK;
String tabs = StringPool.BLANK;
Map<String, String> variableLinesMap = new TreeMap<>(
String.CASE_INSENSITIVE_ORDER);
String variableBlock = matcher.group(1);
variableBlock = variableBlock.trim();
Matcher variableLineMatcher = _poshiVariableLinePattern.matcher(
variableBlock);
boolean hasUnsortedVariables = false;
while (variableLineMatcher.find()) {
if (tabs.equals(StringPool.BLANK)) {
tabs = variableLineMatcher.group(1);
}
String variableLine = variableLineMatcher.group(2);
String variableName = variableLineMatcher.group(3);
variableLinesMap.put(variableName, variableLine);
if (!hasUnsortedVariables &&
(variableName.compareToIgnoreCase(previousName) < 0)) {
hasUnsortedVariables = true;
}
previousName = variableName;
}
if (!hasUnsortedVariables) {
continue;
}
StringBundler sb = new StringBundler();
for (Map.Entry<String, String> entry :
variableLinesMap.entrySet()) {
sb.append(tabs);
sb.append(entry.getValue());
sb.append("\n");
}
String newVariableBlock = sb.toString();
newVariableBlock = newVariableBlock.trim();
content = StringUtil.replaceFirst(
content, variableBlock, newVariableBlock);
}
return content;
}
private final Pattern _poshiClosingTagPattern = Pattern.compile(
"</[^>/]*>");
private final Pattern _poshiCommandsPattern = Pattern.compile(
"\\<command.*name=\\\"([^\\\"]*)\\\".*\\>[\\s\\S]*?\\</command\\>" +
"[\\n|\\t]*?(?:[^(?:/\\>)]*?--\\>)*+");
private final Pattern _poshiElementWithNoChildPattern = Pattern.compile(
"\\\"[\\s]*\\>[\\n\\s\\t]*\\</[a-z\\-]+>");
private final Pattern _poshiEndLinesAfterClosingElementPattern =
Pattern.compile("(\\</[a-z\\-]+>)(\\n+)\\t*\\<[a-z]+");
private final Pattern _poshiEndLinesBeforeClosingElementPattern =
Pattern.compile("(\\n+)(\\t*</[a-z\\-]+>)");
private final Pattern _poshiEndLinesPattern = Pattern.compile(
"\\>\\n\\n\\n+(\\t*\\<)");
private final Pattern _poshiOpeningTagPattern = Pattern.compile(
"<[^/][^>]*[^/]>");
private final Pattern _poshiQuoteWithSlashPattern = Pattern.compile(
"\"[^\"]*\\>[^\"]*\"");
private final Pattern _poshiSetUpPattern = Pattern.compile(
"\\n[\\t]++\\<set-up\\>([\\s\\S]*?)\\</set-up\\>" +
"[\\n|\\t]*?(?:[^(?:/\\>)]*?--\\>)*+\\n");
private final Pattern _poshiTabsPattern = Pattern.compile(
"\\n*([ \\t]*<).*");
private final Pattern _poshiTearDownPattern = Pattern.compile(
"\\n[\\t]++\\<tear-down\\>([\\s\\S]*?)\\</tear-down\\>" +
"[\\n|\\t]*?(?:[^(?:/\\>)]*?--\\>)*+\\n");
private final Pattern _poshiVariableLinePattern = Pattern.compile(
"([\\t]*+)(\\<var.*?name=\\\"([^\\\"]*)\\\".*?/\\>" +
".*+(?:\\</var\\>)??)");
private final Pattern _poshiVariablesBlockPattern = Pattern.compile(
"((?:[\\t]*+\\<var.*?\\>\\n[\\t]*+){2,}?)" +
"(?:(?:\\n){1,}+|\\</execute\\>)");
private final Pattern _poshiWholeTagPattern = Pattern.compile(
"<[^\\>^/]*\\/>");
}