package com.github.sommeri.less4j.core.output; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.github.sommeri.less4j.LessCompiler; import com.github.sommeri.less4j.LessSource; import com.github.sommeri.less4j.LessSource.CannotReadFile; import com.github.sommeri.less4j.LessSource.FileNotFound; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.BugHappened; import com.github.sommeri.less4j.utils.URIUtils; import com.github.sommeri.sourcemap.FilePosition; import com.github.sommeri.sourcemap.SourceMapFormat; import com.github.sommeri.sourcemap.SourceMapGenerator; import com.github.sommeri.sourcemap.SourceMapGeneratorFactory; /* Unused source map v3 features: source content and source root. */ public class SourceMapBuilder { private final ExtendedStringBuilder cssBuilder; private final SourceMapGenerator generator; private final LessCompiler.SourceMapConfiguration configuration; private final Collection<LessSource> additionalSourceFiles; private final Collection<LessSource> includeContentOf; private final Map<LessSource, String> cachedContent; private LessSource cssDestination; public SourceMapBuilder(ExtendedStringBuilder cssBuilder, LessSource cssDestination, Collection<LessSource> nonTreeSources, Collection<LessSource> additionalSourceFiles, LessCompiler.SourceMapConfiguration configuration) { this.cssBuilder = cssBuilder; this.cssDestination = cssDestination; this.configuration = configuration; this.additionalSourceFiles = additionalSourceFiles; this.includeContentOf = nonTreeSources; generator = SourceMapGeneratorFactory.getInstance(SourceMapFormat.V3); cachedContent = new HashMap<LessSource, String>(); } public SourceMapBuilder append(String str, HiddenTokenAwareTree sourceToken) { //indentation must be handled before measuring symbol start position FilePosition outputStartPosition = beforeSymbolPosition(); cssBuilder.append(str); FilePosition outputEndPosition = afterSymbolPosition(); createMapping(str, sourceToken, outputStartPosition, outputEndPosition); return this; } public void appendIgnoreNull(String str, HiddenTokenAwareTree sourceToken) { FilePosition outputStartPosition = beforeSymbolPosition(); cssBuilder.append(str); FilePosition outputEndPosition = afterSymbolPosition(); createMapping(str, sourceToken, outputStartPosition, outputEndPosition); } private void createMapping(String mappedSymbol, HiddenTokenAwareTree sourceToken, FilePosition outputStartPosition, FilePosition outputEndPosition) { FilePosition sourceStartPosition = toFilePosition(sourceToken); String sourceName = toSourceName(sourceToken); String sourceContent = toSourceContent(sourceToken, sourceName); generator.addMapping(sourceName, sourceContent, mappedSymbol, sourceStartPosition, outputStartPosition, outputEndPosition); } private FilePosition beforeSymbolPosition() { cssBuilder.handleIndentation(); FilePosition outputStartPosition = afterSymbolPosition(); return outputStartPosition; } private FilePosition afterSymbolPosition() { return currentPosition(); } private FilePosition currentPosition() { return new FilePosition(cssBuilder.getLine(), cssBuilder.getColumn()); } public void append(SourceMapBuilder other) { FilePosition offset = afterSymbolPosition(); cssBuilder.appendAsIs(other.cssBuilder.toString()); SourceMapGenerator otherGenerator = other.generator; generator.offsetAndAppend(otherGenerator, offset); } private FilePosition toFilePosition(HiddenTokenAwareTree underlyingStructure) { FilePosition result = new FilePosition(underlyingStructure.getLine() - 1, underlyingStructure.getCharPositionInLine()); return result; } private String toSourceName(HiddenTokenAwareTree underlyingStructure) { LessSource source = underlyingStructure.getSource(); return toSourceName(source); } private String toSourceName(LessSource source) { if (configuration.isRelativizePaths()) { return URIUtils.relativizeSourceURIs(cssDestination, source); } else { return source.getURI() == null ? null : source.getURI().toString(); } } private String toSourceContent(HiddenTokenAwareTree underlyingStructure, String sourceName) { LessSource source = underlyingStructure.getSource(); return toSourceContent(underlyingStructure, sourceName, source); } private String toSourceContent(HiddenTokenAwareTree underlyingStructure, String sourceName, LessSource source) { if (configuration.isIncludeSourcesContent() || sourceName==null || includeContentOf.contains(source)) { if (cachedContent.containsKey(source)) { return cachedContent.get(source); } String content; try { content = source.getContent(); } catch (FileNotFound e) { throw new BugHappened("How did we compiled something we did not read?", underlyingStructure); } catch (CannotReadFile e) { throw new BugHappened("How did we compiled something we did not read?", underlyingStructure); } cachedContent.put(source, content); return content; } else { return null; } } public String toSourceMap() { for (LessSource source : additionalSourceFiles) { String sourceName = toSourceName(source); String sourceContent = toSourceContent(null, sourceName, source); generator.addSourceFile(sourceName, sourceContent); } // map file is assumed to have the same location as generated css String name = ""; if (cssDestination != null && cssDestination.getName() != null) { name = cssDestination.getName(); } try { StringBuilder sb = new StringBuilder(); generator.appendTo(sb, name); return sb.toString(); } catch (IOException e) { throw new IllegalStateException("Impossible to happen exception.", e); } } protected ExtendedStringBuilder getCssBuilder() { return cssBuilder; } public void ensureSeparator() { cssBuilder.ensureSeparator(); } }