001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.core.ui.resources.css; 017 018import java.net.URI; 019import java.net.URISyntaxException; 020import java.util.regex.Matcher; 021import java.util.regex.Pattern; 022 023import org.apache.commons.io.FilenameUtils; 024 025/** 026 * Helper for CSS Files 027 */ 028public final class CSSFileHelper 029{ 030 /** Regex that matches an @import statement in a css file, with optional media */ 031 public static final Pattern IMPORT_PATTERN = Pattern.compile("(?<=^|\n|;)" // Matches the start of the line or end of previous statement 032 + "[ \t]*@import[ \t]*" // Matches the '@import' mention 033 + "(?:url)?" // Optional match with 'url' 034 + "\\(?[ \t]*[\"']?" // Optional match with parenthesis, whitespace and/or quotes 035 + "([^)\"'\n]*)" // Captures a string that does not contains a parenthesis or quote, the import url 036 + "[\"']?\\)?[ \t]*" // Optional match with closing parenthesis, whitespace and/or quotes 037 + "([^; \t\r\n]*)[ \t]*" // Captures an optional string, which is the "media" part of the @import 038 + "(?:;|\n|$)?", // Matches optional end of statement or line 039 Pattern.CASE_INSENSITIVE); 040 041 // This regex matches 'src=' declarations 042 private static final Pattern CSS_URL_PATTERN_SRC = Pattern.compile("src" // the string 'src' literally 043 + "\\s*=\\s*" // the symbol equal, with any whitespace before or after 044 + "['\"](.*?)['\"]", // capture a string in between quotes, singles or double 045 Pattern.DOTALL | Pattern.CASE_INSENSITIVE); 046 047 // This regex matches any 'url()' that is not preceded by an @import directive 048 private static final Pattern CSS_URL_PATTERN_NOTIMPORT_URL = Pattern.compile("(?:@import\\s+url)" // The pattern '@import url' that we want to ignore. When matching this, group(1) will be null 049 + "|" // The right side of this "or" is only evaluated when we don't match the previous pattern 050 051 + "(?:" 052 + "\\burl" // The word 'url' 053 + "\\s*" 054 + "\\(" // The opening parenthesis before the url 055 + "\\s*['\"]?(.*?)['\"]?\\s*" // Capture a string that could be surrounded by whitespace and quotes 056 + "\\)" // The closing parenthesis after the url 057 + ")", 058 Pattern.DOTALL | Pattern.CASE_INSENSITIVE); 059 060 // This regex matches any 'url()' that is preceded by an @import directive 061 private static final Pattern CSS_URL_PATTERN_IMPORT_URL = Pattern.compile("@import\\s+url\\s*" // The literal '@import url' with any number of whitespace 062 + "\\(" // The opening parenthesis of the 'url()' 063 + "\\s*['\"]?(.*?)['\"]?\\s*" // Capture a string that could be surrounded by whitespace and quotes 064 + "\\)", // The closing parenthesis of the 'url()' 065 Pattern.DOTALL | Pattern.CASE_INSENSITIVE); 066 067 // This regex matches '@import' directives without 'url()' 068 private static final Pattern CSS_URL_PATTERN_IMPORT = Pattern.compile("@import\\s+" // The literal '@import' followed by whitespace 069 + "['\"](.*?)['\"]", // Capture a string that could be surrounded by quotes 070 Pattern.DOTALL | Pattern.CASE_INSENSITIVE); 071 072 private CSSFileHelper() 073 { 074 } 075 076 /** 077 * Replace the relative URI inside a css file with the new context path 078 * @param content The file content 079 * @param fileUri The file Uri 080 * @param jsassResourceURIExtensionPoint The JSASS Resource URI extension point 081 * @param internalContextPath The internal context path of the application 082 * @param externalContextPath The external context path of the application 083 * @return The file content, with URIs replaced 084 * @throws URISyntaxException If an exception occurred 085 */ 086 public static String replaceRelativeUri(String content, String fileUri, JSASSResourceURIExtensionPoint jsassResourceURIExtensionPoint, String internalContextPath, String externalContextPath) throws URISyntaxException 087 { 088 String c1 = _replaceRelativeUri(content, externalContextPath, fileUri, CSS_URL_PATTERN_SRC, jsassResourceURIExtensionPoint); 089 String c2 = _replaceRelativeUri(c1, externalContextPath, fileUri, CSS_URL_PATTERN_NOTIMPORT_URL, jsassResourceURIExtensionPoint); 090 String c3 = _replaceRelativeUri(c2, internalContextPath, fileUri, CSS_URL_PATTERN_IMPORT_URL, jsassResourceURIExtensionPoint); 091 String result = _replaceRelativeUri(c3, internalContextPath, fileUri, CSS_URL_PATTERN_IMPORT, jsassResourceURIExtensionPoint); 092 093 return result; 094 } 095 096 /** 097 * Replace the relative URI inside a css file with the new context path 098 * @param content The file content 099 * @param fileUri The file Uri, without context path, for example "/plugins/pluginName/resources/style.css" or "/skins/skinName/resources/style.scss" 100 * @param jsassResourceURIExtensionPoint The JSASS Resource URI extension point 101 * @param externalContextPath The external context path of the application 102 * @return The file content, with URIs replaced 103 * @throws URISyntaxException If an exception occurred 104 */ 105 public static String replaceRelativeResourcesUri(String content, String fileUri, JSASSResourceURIExtensionPoint jsassResourceURIExtensionPoint, String externalContextPath) throws URISyntaxException 106 { 107 String c1 = _replaceRelativeUri(content, externalContextPath, fileUri, CSS_URL_PATTERN_SRC, jsassResourceURIExtensionPoint); 108 String result = _replaceRelativeUri(c1, externalContextPath, fileUri, CSS_URL_PATTERN_NOTIMPORT_URL, jsassResourceURIExtensionPoint); 109 110 return result; 111 } 112 113 private static String _replaceRelativeUri(String content, String contextPath, String fileUri, Pattern pattern, JSASSResourceURIExtensionPoint jsassResourceURIExtensionPoint) 114 { 115 Matcher urlMatcher = pattern.matcher(content); 116 StringBuffer sb = new StringBuffer(); 117 118 while (urlMatcher.find()) 119 { 120 String fullMatch = urlMatcher.group(); 121 String cssUrl = urlMatcher.group(1); 122 123 if (cssUrl != null && !cssUrl.startsWith("data:")) 124 { 125 URI uri; 126 try 127 { 128 uri = new URI(cssUrl.replaceAll("\\|", "%7C")); 129 130 if (!uri.isAbsolute() && cssUrl.indexOf("/") != 0 && cssUrl.indexOf("#") != 0) 131 { 132 String fullFileUri = fileUri.indexOf('/') == 0 ? contextPath + fileUri : fileUri; 133 String fullPathUrl = new URI(FilenameUtils.getFullPath(fullFileUri) + cssUrl).normalize().toString(); 134 urlMatcher.appendReplacement(sb, Matcher.quoteReplacement(fullMatch.replace(cssUrl, fullPathUrl))); 135 } 136 else 137 { 138 String relativeUri = jsassResourceURIExtensionPoint.resolve(cssUrl); 139 if (relativeUri != null) 140 { 141 urlMatcher.appendReplacement(sb, Matcher.quoteReplacement(fullMatch.replace(cssUrl, relativeUri))); 142 } 143 } 144 } 145 catch (URISyntaxException e) 146 { 147 urlMatcher.appendTail(sb); 148 return sb.toString(); 149 } 150 } 151 } 152 153 urlMatcher.appendTail(sb); 154 155 return sb.toString(); 156 } 157 158}