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.core.minimize.css;
017
018import java.net.URI;
019import java.net.URISyntaxException;
020import java.util.Arrays;
021import java.util.function.Predicate;
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025/**
026 * Helper for JSASS source maps
027 */
028public final class JSASSFixHelper
029{
030
031    // Regex that matches a JSASS injected import in the sources of a source map
032    private static final Pattern SOURCEMAP_SOURCES_PATTERN = Pattern.compile("^([^\"]*\")" // Captures the start of the line until the first double quote
033                                                                        + "([^\"]*)" // Captures any character that is the path of the JSASS injected import
034                                                                        + "(\",?)$", // Captures the end of the line, which is the closing double quote, and an optional comma
035                                                                        Pattern.MULTILINE);
036    
037    // List of JSASS files injected automatically in the source map
038    private static final String[] JSASS_INJECTED_FILES = new String[] {"JSASS_CUSTOM.scss", "JSASS_PRE_IMPORT.scss", "JSASS_POST_IMPORT.scss"};
039    
040    private JSASSFixHelper()
041    {
042        // static helper
043    }
044    
045    /**
046     * Fix the common problems with JSASS generated source maps
047     * @param sourceMap The source map
048     * @param externalContextPath The context path 
049     * @return The source map fixed
050     */
051    public static String fixJsassSourceMap(String sourceMap, String externalContextPath)
052    {
053        // Replace relative path with absolute
054        Matcher matcher = SOURCEMAP_SOURCES_PATTERN.matcher(sourceMap);
055        StringBuffer sb = new StringBuffer();
056        while (matcher.find())
057        {
058            String source = matcher.group(2);
059            Predicate<String> isJsassInjectedFile = file -> source.endsWith(file);
060            if (Arrays.stream(JSASS_INJECTED_FILES).anyMatch(isJsassInjectedFile))
061            {
062                // JSASS injected file, replace with placeholder
063                matcher.appendReplacement(sb, "$1" + externalContextPath + "/plugins/core/resources/css/jsass_injected_file_placeholder.css$3");
064            }
065            else
066            {
067                try
068                {
069                    if (!source.startsWith("/") && !(new URI(source).isAbsolute()))
070                    {
071                        // To prevent JSASS from resolving URIs, URIs are transformed to relative URIs before going through JSASS,
072                        // and must be converted back to absolute (by adding the initial / at the start) afterward.
073                        matcher.appendReplacement(sb, "$1/$2$3");
074                    }
075                }
076                catch (URISyntaxException e)
077                {
078                    // invalid uri in sources of the sourceMap. 
079                }
080            }
081        }
082        
083        matcher.appendTail(sb);
084        
085        return sb.toString();
086    }
087    
088}