Concatenate CSS files from subdirectories into one single styles.css

Good morning,

my theme consists of a hierarchy of several CSS files (via @import) like the vaadin themes. Now I need to concatenate each of them into one single styles.css - exactly like vaadin does. I searched the internet for a script that does this job. There are a few but none of them follows the @import tags AND replaces internal relative urls for images.

Is the script/program/ant task which obviously does this job available or should I better write my own?

Cheers,
Stephan

Here you go, sir! :slight_smile:


CompileDefaultTheme.java

Nevermind the name of the class, echoes from the past, but this little Java program compiles our themes. Not bulletproof, be warned, especially with import statements,
only one syntax is currently supported
.

Great response time… :wink:

Perfect, thank you.

I ended up writing a more “generic” solution this morning. This little Java program takes the path to the root css file (style.css) and follows each “@import url(…)” reference. In contrast to the above mentioned CompileDefaultTheme.java there are no restrictions on further subdirectories in the @import statement. However, for the moment the program assumes that the @import statement make use of the url(…) syntax. Any relative image links inside the referenced css files are replaced according to the base directory of the final/concatenated css file named …-all.css (styles-all.css).

Maybe this is helpful for some of you.


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ConcatenateCss {

    private static final String SEPARATOR = "----------------------------------------------------------------------";
    private static final Pattern URL_PATTERN = Pattern.compile("url\\(['\"]
?(.*?)['\"]
?\\)");

    private static File cssRootDir;
    private static File cssRootFile;
    private static File cssRootFileAll;
    private static URI cssRootUri;

    private static int fileCount;
    private static int lineCount;

    public static void main(final String[] args) throws IOException {
        if (args.length != 1) {
            System.err.println("Usage: java ConcatenateCss [path to root css file]
");
            return;
        }

        final String fileName = args[0]
;
        cssRootFile = new File(fileName);
        cssRootDir = cssRootFile.getParentFile();
        cssRootFileAll = new File(fileName.replace(".css", "-all.css"));
        cssRootUri = cssRootDir.toURI();

        System.out.println("CSS root file is: " + cssRootFile);
        System.out.println("Concatenated CSS: " + cssRootFileAll);
        System.out.println("---");

        final StringBuilder cssContent = new StringBuilder();
        cssContent.append("/* CSS CONCATENATED FROM SUBDIRECTORIES */\n");

        concatenate(cssRootFile, cssContent);
        ++fileCount;

        final BufferedWriter out = new BufferedWriter(new FileWriter(cssRootFileAll));
        out.write(cssContent.toString());
        out.close();

        System.out.println("---");
        System.out.println("Successfully concatenated " + fileCount + " files (" + lineCount + " lines)");
    }

    private static boolean concatenate(final File cssFile, final StringBuilder cssContent) throws IOException {
        if (!cssFile.isFile()) {
            System.out.println("Ignored file: " + cssFile + " (missing or not a file)");
            return false;
        }

        cssContent.append("/* " + SEPARATOR + " " + cssFile.getName().toUpperCase() + " */\n");
        ++lineCount;

        final DataInputStream in = new DataInputStream(new FileInputStream(cssFile));
        final BufferedReader br = new BufferedReader(new InputStreamReader(in));

        String line;
        while ((line = br.readLine()) != null) {
            if (line.isEmpty()) {
                continue;
            }

            if (line.startsWith("@import")) {
                final Matcher urlMatcher = URL_PATTERN.matcher(line);
                if (urlMatcher.find()) {
                    final String url = urlMatcher.group(1);
                    File importFile = new File(url);
                    if (!importFile.isAbsolute()) {
                        importFile = new File(cssFile.getParentFile(), url);
                    }
                    if (!concatenate(importFile, cssContent)) {
                        cssContent.append(line).append("\n");
                        ++lineCount;
                    } else {
                        ++fileCount;
                        System.out.println("Concatenated: " + importFile);
                    }
                } else {
                    cssContent.append(line).append("\n");
                    ++lineCount;
                    System.out.println("Warning: Could not find url(...) pattern");
                }
                continue;
            }

            final Matcher urlMatcher = URL_PATTERN.matcher(line);
            if (urlMatcher.find()) {
                final String url = urlMatcher.group(1);
                File imageFile = new File(url);
                if (!imageFile.isAbsolute()) {
                    imageFile = new File(cssFile.getParentFile(), url);
                    line = line.replaceAll(url, cssRootUri.relativize(imageFile.toURI()).getPath());
                }
            }

            cssContent.append(line).append("\n");
            ++lineCount;
        }
        in.close();

        return true;
    }

}

Thanks, I’m sure this will be helpful to many others. The ugly script we’re using currently is very specific to our needs, and needs refactoring once I get my hands on our theming system overall.

Could you possibly package this program into a Vaadin Add-on zip file, and upload it to the Directory (in the Tools category)? People might find it better from there.

if you think its useful, i’ll promise to do that asap.

Althought this thread is rather old, I used the script to concatenate my CSS files. I recognized that it does not cut out comments nor relative URLs from folders below the root folder work correctly, so I fixed them. As I did not found this tool as an addon within the repository, I will just drop the code here, maybe someone will find it useful :wink:


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ConcatenateCss {
	private static final String SEPARATOR = "----------------------------------------------------------------------";
	private static final Pattern URL_PATTERN = Pattern.compile("url\\(['\"]
?(.*?)['\"]
?\\)");
	private static final Pattern COMMENT_START = Pattern.compile("/\\*");
	private static final Pattern COMMENT_END = Pattern.compile("\\*/");
	private static File cssRootDir;
	private static File cssRootFile;
	private static File cssRootFileAll;
	private static URI cssRootUri;
	private static int fileCount;
	private static int lineCount;

	public static void main(final String[] args) throws IOException {
		if (args.length != 2) {
			System.err.println("Usage: java ConcatenateCss [path to root css file]
 [path to target css file]
");
			return;
		}
		final String fileName = args[0]
;
		cssRootFile = new File(fileName);
		cssRootDir = cssRootFile.getParentFile();
		cssRootFileAll = new File(args[1]
);
		cssRootUri = cssRootDir.toURI();
		System.out.println("CSS root file is: " + cssRootFile);
		System.out.println("Concatenated CSS: " + cssRootFileAll);
		System.out.println("---");
		final StringBuilder cssContent = new StringBuilder();
		cssContent.append("/* CSS CONCATENATED FROM SUBDIRECTORIES */\n");
		concatenate(cssRootFile, cssContent);
		++fileCount;
		final BufferedWriter out = new BufferedWriter(new FileWriter(
				cssRootFileAll));
		out.write(cssContent.toString());
		out.close();
		System.out.println("---");
		System.out.println("Successfully concatenated " + fileCount
				+ " files (" + lineCount + " lines)");
	}

	private static boolean concatenate(final File cssFile,
			final StringBuilder cssContent) throws IOException {
		if (!cssFile.isFile()) {
			System.out.println("Ignored file: " + cssFile
					+ " (missing or not a file)");
			return false;
		}
		cssContent.append("/* " + SEPARATOR + " "
				+ cssFile.getName().toUpperCase() + " */\n");
		++lineCount;
		final DataInputStream in = new DataInputStream(new FileInputStream(
				cssFile));
		final BufferedReader br = new BufferedReader(new InputStreamReader(in));
		String line;
		boolean commentActive = false;
		while ((line = br.readLine()) != null) {
			
			// Remove comments:
			if (!commentActive) {
				Matcher commentStartMatcher = COMMENT_START.matcher(line);
				if (commentStartMatcher.find()) {
					//	Filter for Comments within one line:
					String restLine = line.substring(commentStartMatcher.end(0));
					Matcher commentEndMatcher = COMMENT_END.matcher(restLine);
					if (commentEndMatcher.find()) {
						line = line.substring(0, commentStartMatcher.start(0)) + restLine.substring(commentEndMatcher.end(0));
					} else {
						commentActive = true;
						line = line.substring(0, commentStartMatcher.start(0));
					}
				}
			} else {
				Matcher commentEndMatcher = COMMENT_END.matcher(line);
				if (commentEndMatcher.find()) {
					commentActive = false;
					line = line.substring(commentEndMatcher.end(0));
				} else {
					continue;
				}
			}
			
			if (line == null || line.equals("")) {
				continue;
			}
			if (line.startsWith("@import")) {
				final Matcher urlMatcher = URL_PATTERN.matcher(line);
				if (urlMatcher.find()) {
					final String url = urlMatcher.group(1);
					File importFile = new File(url);
					if (!importFile.isAbsolute()) {
						importFile = new File(cssFile.getParentFile(), url);
					}
					if (!concatenate(importFile, cssContent)) {
						cssContent.append(line).append("\n");
						++lineCount;
					} else {
						++fileCount;
						System.out.println("Concatenated: " + importFile);
					}
				} else {
					cssContent.append(line).append("\n");
					++lineCount;
					System.out
							.println("Warning: Could not find url(...) pattern");
				}
				continue;
			}
			final Matcher urlMatcher = URL_PATTERN.matcher(line);
			if (urlMatcher.find()) {
				final String url = urlMatcher.group(1);
				File imageFile = new File(url);
				if (!imageFile.isAbsolute()) {
					imageFile = new File(cssFile.getParentFile(), url);
					String newUrl = cssRootUri.relativize(imageFile.toURI()).getPath();
					newUrl = newUrl.replaceAll(cssRootUri.getPath(), "");
					line = line.replaceAll(url, newUrl);
				}
			}
			cssContent.append(line).append("\n");
			++lineCount;
		}
		in.close();
		return true;
	}
}

Cheers,

Martin