import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.vaadin.sass.internal.parser.ParseException;
import com.vaadin.sass.internal.parser.Parser;
import com.vaadin.sass.internal.parser.SCSSParseException;
+import com.vaadin.sass.internal.resolver.ClassloaderResolver;
+import com.vaadin.sass.internal.resolver.FilesystemResolver;
import com.vaadin.sass.internal.resolver.ScssStylesheetResolver;
-import com.vaadin.sass.internal.resolver.VaadinResolver;
import com.vaadin.sass.internal.tree.BlockNode;
import com.vaadin.sass.internal.tree.MixinDefNode;
import com.vaadin.sass.internal.tree.Node;
private String charset;
+ private List<ScssStylesheetResolver> resolvers = new ArrayList<ScssStylesheetResolver>();
+
/**
* Read in a file SCSS and parse it into a ScssStylesheet
*
* @throws CSSException
* @throws IOException
*/
- public static ScssStylesheet get(String identifier, String encoding)
- throws CSSException, IOException {
+ public static ScssStylesheet get(String identifier,
+ ScssStylesheet parentStylesheet) throws CSSException, IOException {
/*
* The encoding to be used is passed through "encoding" parameter. the
* imported children scss node will have the same encoding as their
SCSSDocumentHandler handler = new SCSSDocumentHandlerImpl();
ScssStylesheet stylesheet = handler.getStyleSheet();
-
- InputSource source = stylesheet.resolveStylesheet(identifier);
+ if (parentStylesheet == null) {
+ // Use default resolvers
+ stylesheet.addResolver(new FilesystemResolver());
+ stylesheet.addResolver(new ClassloaderResolver());
+ } else {
+ // Use parent resolvers
+ stylesheet.setResolvers(parentStylesheet.getResolvers());
+ }
+ InputSource source = stylesheet.resolveStylesheet(identifier,
+ parentStylesheet);
if (source == null) {
return null;
}
- source.setEncoding(encoding);
+ if (parentStylesheet != null) {
+ source.setEncoding(parentStylesheet.getCharset());
+ }
Parser parser = new Parser();
parser.setErrorHandler(new SCSSErrorHandler());
return stylesheet;
}
- private static ScssStylesheetResolver[] resolvers = null;
-
- public static void setStylesheetResolvers(
- ScssStylesheetResolver... styleSheetResolvers) {
- resolvers = Arrays.copyOf(styleSheetResolvers,
- styleSheetResolvers.length);
- }
-
- public InputSource resolveStylesheet(String identifier) {
- if (resolvers == null) {
- setStylesheetResolvers(new VaadinResolver());
- }
-
- for (ScssStylesheetResolver resolver : resolvers) {
- InputSource source = resolver.resolve(identifier);
+ public InputSource resolveStylesheet(String identifier,
+ ScssStylesheet parentStylesheet) {
+ for (ScssStylesheetResolver resolver : getResolvers()) {
+ InputSource source = resolver.resolve(parentStylesheet, identifier);
if (source != null) {
File f = new File(source.getURI());
setFile(f);
return null;
}
+ /**
+ * Retrieves a list of resolvers to use when resolving imports
+ *
+ * @since 7.2
+ * @return the resolvers used to resolving imports
+ */
+ public List<ScssStylesheetResolver> getResolvers() {
+ return Collections.unmodifiableList(resolvers);
+ }
+
+ /**
+ * Sets the list of resolvers to use when resolving imports
+ *
+ * @since 7.2
+ * @param resolvers
+ * the resolvers to set
+ */
+ public void setResolvers(List<ScssStylesheetResolver> resolvers) {
+ this.resolvers = new ArrayList<ScssStylesheetResolver>(resolvers);
+ }
+
+ /**
+ * Adds the given resolver to the resolver list
+ *
+ * @since 7.2
+ * @param resolver
+ * The resolver to add
+ */
+ public void addResolver(ScssStylesheetResolver resolver) {
+ resolvers.add(resolver);
+ }
+
/**
* Applies all the visitors and compiles SCSS into Css.
*
--- /dev/null
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.sass.internal.resolver;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Stack;
+
+import org.w3c.css.sac.InputSource;
+
+import com.vaadin.sass.internal.ScssStylesheet;
+
+/**
+ * Base class for resolvers. Implements functionality for locating paths which
+ * an import can be relative to and helpers for extracting path information from
+ * the identifier.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractResolver implements ScssStylesheetResolver,
+ Serializable {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.sass.internal.resolver.ScssStylesheetResolver#resolve(java
+ * .lang.String)
+ */
+ @Override
+ public InputSource resolve(ScssStylesheet parentStylesheet,
+ String identifier) {
+ InputSource source = null;
+ if (parentStylesheet != null) {
+ StringBuilder filePathBuilder = new StringBuilder(
+ parentStylesheet.getFileName());
+ filePathBuilder.append(File.separatorChar).append(identifier);
+ if (!filePathBuilder.toString().endsWith(".scss")) {
+ filePathBuilder.append(".scss");
+ }
+ source = normalizeAndResolve(filePathBuilder.toString());
+ }
+
+ if (source == null) {
+ source = normalizeAndResolve(identifier);
+ }
+
+ return source;
+ }
+
+ /**
+ * Resolves the normalized version of the given identifier
+ *
+ * @param identifier
+ * The identifier to resolve
+ * @return An input source if the resolver found one or null otherwise
+ */
+ protected InputSource normalizeAndResolve(String identifier) {
+ String normalized = normalize(identifier);
+ return resolveNormalized(normalized);
+ }
+
+ /**
+ * Resolves the identifier after it has been normalized using
+ * {@link #normalize(String)}.
+ *
+ * @param identifier
+ * The normalized identifier
+ * @return an InputSource if the resolver found a source or null otherwise
+ */
+ protected abstract InputSource resolveNormalized(String identifier);
+
+ /**
+ * Normalizes "." and ".." from the path string where parent path segments
+ * can be removed. Preserve leading "..". Also ensure / is used instead of \
+ * in all places.
+ *
+ * @param path
+ * A relative or absolute file path
+ * @return The normalized path
+ */
+ protected String normalize(String path) {
+
+ // Ensure only "/" is used, also in Windows
+ path = path.replace(File.separatorChar, '/');
+
+ // Split into segments
+ String[] segments = path.split("/");
+ Stack<String> result = new Stack<String>();
+
+ // Replace '.' and '..' segments
+ for (int i = 0; i < segments.length; i++) {
+ if (segments[i].equals(".")) {
+ // Segments marked '.' are ignored
+
+ } else if (segments[i].equals("..") && !result.isEmpty()
+ && !result.lastElement().equals("..")) {
+ // If segment is ".." then remove the previous iff the previous
+ // element is not a ".." and the result stack is not empty
+ result.pop();
+ } else {
+ // Other segments are just added to the stack
+ result.push(segments[i]);
+ }
+ }
+
+ // Reconstruct path
+ StringBuilder pathBuilder = new StringBuilder();
+ for (int i = 0; i < result.size(); i++) {
+ if (i > 0) {
+ pathBuilder.append("/");
+ }
+ pathBuilder.append(result.get(i));
+ }
+ return pathBuilder.toString();
+ }
+
+}
import org.w3c.css.sac.InputSource;
-public class ClassloaderResolver implements ScssStylesheetResolver {
+public class ClassloaderResolver extends AbstractResolver {
@Override
- public InputSource resolve(String identifier) {
+ public InputSource resolveNormalized(String identifier) {
// identifier should not have .scss, fileName should
String ext = ".scss";
if (identifier.endsWith(".css")) {
import org.w3c.css.sac.InputSource;
-public class FilesystemResolver implements ScssStylesheetResolver {
+public class FilesystemResolver extends AbstractResolver {
@Override
- public InputSource resolve(String identifier) {
+ public InputSource resolveNormalized(String identifier) {
// identifier should not have .scss, fileName should
String ext = ".scss";
if (identifier.endsWith(".css")) {
import org.w3c.css.sac.InputSource;
+import com.vaadin.sass.internal.ScssStylesheet;
+
public interface ScssStylesheetResolver {
/**
* Called with the "identifier" of a stylesheet that the resolver should try
* stylesheet was found, e.g "runo.scss" might result in a URI like
* "VAADIN/themes/runo/runo.scss".
*
+ * @param parentStylesheet
+ * The parent style sheet
* @param identifier
* used fo find stylesheet
* @return InputSource for stylesheet (with URI set) or null if not found
*/
- public InputSource resolve(String identifier);
+ public InputSource resolve(ScssStylesheet parentStylesheet,
+ String identifier);
}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.sass.internal.resolver;
-
-import java.io.File;
-import java.util.Stack;
-
-import org.w3c.css.sac.InputSource;
-
-public class VaadinResolver implements ScssStylesheetResolver {
-
- @Override
- public InputSource resolve(String identifier) {
-
- // Remove extra "." and ".."
- identifier = normalize(identifier);
-
- InputSource source = null;
-
- // Can we find the scss from the file system?
- ScssStylesheetResolver resolver = new FilesystemResolver();
- source = resolver.resolve(identifier);
-
- if (source == null) {
- // How about the classpath?
- resolver = new ClassloaderResolver();
- source = resolver.resolve(identifier);
- }
-
- return source;
- }
-
- /**
- * Normalizes "." and ".." from the path string where parent path segments
- * can be removed. Preserve leading "..".
- *
- * @param path
- * A relative or absolute file path
- * @return The normalized path
- */
- private static String normalize(String path) {
-
- // Ensure only "/" is used, also in Windows
- path = path.replace(File.separatorChar, '/');
-
- // Split into segments
- String[] segments = path.split("/");
- Stack<String> result = new Stack<String>();
-
- // Replace '.' and '..' segments
- for (int i = 0; i < segments.length; i++) {
- if (segments[i].equals(".")) {
- // Segments marked '.' are ignored
-
- } else if (segments[i].equals("..") && !result.isEmpty()
- && !result.lastElement().equals("..")) {
- // If segment is ".." then remove the previous iff the previous
- // element is not a ".." and the result stack is not empty
- result.pop();
- } else {
- // Other segments are just added to the stack
- result.push(segments[i]);
- }
- }
-
- // Reconstruct path
- StringBuilder pathBuilder = new StringBuilder();
- for (int i = 0; i < result.size(); i++) {
- if (i > 0) {
- pathBuilder.append("/");
- }
- pathBuilder.append(result.get(i));
- }
- return pathBuilder.toString();
- }
-
-}
package com.vaadin.sass.internal.visitor;
-import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
ImportNode importNode = (ImportNode) n;
if (!importNode.isPureCssImport()) {
try {
- StringBuilder filePathBuilder = new StringBuilder(
- styleSheet.getDirectory());
- filePathBuilder.append(File.separatorChar).append(
- importNode.getUri());
- if (!filePathBuilder.toString().endsWith(".scss")) {
- filePathBuilder.append(".scss");
- }
-
// set parent's charset to imported node.
ScssStylesheet imported = ScssStylesheet.get(
- filePathBuilder.toString(),
- styleSheet.getCharset());
- if (imported == null) {
- imported = ScssStylesheet.get(importNode.getUri());
- }
+ importNode.getUri(), styleSheet);
if (imported == null) {
throw new FileNotFoundException("Import '"
+ importNode.getUri() + "' in '"
import org.junit.Assert;
import org.junit.Test;
-import com.vaadin.sass.internal.resolver.VaadinResolver;
+import com.vaadin.sass.internal.resolver.AbstractResolver;
+import com.vaadin.sass.internal.resolver.ClassloaderResolver;
+import com.vaadin.sass.internal.resolver.FilesystemResolver;
public class VaadinResolverTest {
@Test
- public void testPathNormalization() throws Exception {
+ public void testFilesystemResolverPathNormalization() throws Exception {
+ testPathNormalization(new FilesystemResolver());
+ }
+
+ @Test
+ public void testClassloaderResolverPathNormalization() throws Exception {
+ testPathNormalization(new ClassloaderResolver());
+ }
- VaadinResolver resolver = new VaadinResolver();
+ public void testPathNormalization(AbstractResolver resolver)
+ throws Exception {
- Method normalizeMethod = VaadinResolver.class.getDeclaredMethod(
+ Method normalizeMethod = AbstractResolver.class.getDeclaredMethod(
"normalize", String.class);
normalizeMethod.setAccessible(true);