/// <summary>The sprite image.</summary> /// <param name="scanOutput">The scan Output.</param> /// <param name="mapXmlFile">The map xml file</param> /// <param name="imageAssemblyAnalysisLog">The image Assembly Analysis Log.</param> /// <param name="threadContext">The thread Context.</param> /// <param name="spritedImageContentItems">The sprited Image Content Items.</param> /// <returns>The <see cref="ImageLog"/>.</returns> private ImageLog SpriteImageFromLog(ImageAssemblyScanOutput scanOutput, string mapXmlFile, ImageAssemblyAnalysisLog imageAssemblyAnalysisLog, IWebGreaseContext threadContext, BlockingCollection <ContentItem> spritedImageContentItems) { if (scanOutput == null || !scanOutput.ImageReferencesToAssemble.Any()) { return(null); } ImageLog imageLog = null; var imageReferencesToAssemble = scanOutput.ImageReferencesToAssemble; if (imageReferencesToAssemble == null || imageReferencesToAssemble.Count == 0) { return(null); } var varBySettings = new { imageMap = GetRelativeSpriteCacheKey(imageReferencesToAssemble, threadContext), this.ImageAssemblyPadding }; var success = threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Spriting, SectionIdParts.Assembly) .MakeCachable(varBySettings, false, true) .RestoreFromCacheAction(cacheSection => { imageLog = RestoreSpritedImagesFromCache(mapXmlFile, cacheSection, spritedImageContentItems, threadContext.Configuration.DestinationDirectory, this.ImageAssembleScanDestinationFile); return(imageLog != null); }) .Execute(cacheSection => { imageLog = this.CreateSpritedImages(mapXmlFile, imageAssemblyAnalysisLog, imageReferencesToAssemble, cacheSection, spritedImageContentItems, threadContext); return(imageLog != null); }); return (success ? imageLog : null); }
/// <summary>Applies the css validation visitors.</summary> /// <param name="stylesheetNode">The stylesheet node.</param> /// <param name="threadContext">The thread Context.</param> /// <returns>The processed node.</returns> private AstNode ApplyValidation(AstNode stylesheetNode, IWebGreaseContext threadContext) { threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Validate).Execute(() => { // Step # 1 - Remove the Css properties from Ast which need to be excluded (Bridging) if (this.ShouldExcludeProperties) { stylesheetNode = stylesheetNode.Accept(new ExcludePropertyVisitor()); } // Step # 2 - Validate for lower case if (this.ShouldValidateForLowerCase) { stylesheetNode = stylesheetNode.Accept(new ValidateLowercaseVisitor()); } // Step # 3 - Validate for Css hacks which don't work cross browser if (this.HackSelectors != null && this.HackSelectors.Any()) { stylesheetNode = stylesheetNode.Accept(new SelectorValidationOptimizationVisitor(this.HackSelectors, false, true)); } // Step # 4 - Remove any banned selectors which are exposed for page efficiency if (this.BannedSelectors != null && this.BannedSelectors.Any()) { stylesheetNode = stylesheetNode.Accept(new SelectorValidationOptimizationVisitor(this.BannedSelectors, false, false)); } }); return(stylesheetNode); }
/// <summary>Hash the images.</summary> /// <param name="cssContent">The css content.</param> /// <param name="imageHasher">The image Hasher.</param> /// <param name="cacheSection">The cache section.</param> /// <param name="threadContext">The context.</param> /// <param name="sourceImages">The source Images.</param> /// <param name="missingImage">The missing Image.</param> /// <returns>The css with hashed images.</returns> private static Tuple <string, IEnumerable <ContentItem> > HashImages(string cssContent, FileHasherActivity imageHasher, ICacheSection cacheSection, IWebGreaseContext threadContext, IDictionary <string, string> sourceImages, string missingImage) { return(threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.ImageHash).Execute(() => { var contentImagesToHash = new HashSet <string>(); var hashedContentItems = new List <ContentItem>(); var hashedImages = new Dictionary <string, string>(); cssContent = UrlHashRegexPattern.Replace( cssContent, match => { var urlToHash = match.Groups["url"].Value; var extraInfo = match.Groups["extra"].Value; if (ResourcesResolver.LocalizationResourceKeyRegex.IsMatch(urlToHash)) { return match.Value; } var normalizedHashUrl = urlToHash.NormalizeUrl(); var imageContentFile = sourceImages.TryGetValue(normalizedHashUrl); if (imageContentFile == null && !string.IsNullOrWhiteSpace(missingImage)) { imageContentFile = sourceImages.TryGetValue(missingImage); } if (imageContentFile == null) { throw new BuildWorkflowException("Could not find a matching source image for url: {0}".InvariantFormat(urlToHash)); } if (contentImagesToHash.Add(normalizedHashUrl)) { // Add the image as end result var imageContentItem = ContentItem.FromFile(imageContentFile, normalizedHashUrl); imageContentItem = imageHasher.Hash(imageContentItem); cacheSection.AddSourceDependency(imageContentFile); hashedContentItems.Add(imageContentItem); imageContentFile = Path.Combine( imageHasher.BasePrefixToAddToOutputPath ?? Path.AltDirectorySeparatorChar.ToString(CultureInfo.InvariantCulture), imageContentItem.RelativeHashedContentPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); hashedImages.Add(normalizedHashUrl, imageContentFile); } else { imageContentFile = hashedImages[normalizedHashUrl]; } return "url(" + imageContentFile + extraInfo + ")"; }); return Tuple.Create(cssContent, (IEnumerable <ContentItem>)hashedContentItems); })); }
/// <summary>Parse the styleSheet.</summary> /// <param name="cssContent">The css content.</param> /// <param name="shouldLogDiagnostics">The should log diagnostics.</param> /// <returns>The styleSheet node.</returns> private static StyleSheetNode ParseStyleSheet(IWebGreaseContext context, string cssContent, bool shouldLogDiagnostics) { var lexer = new CssLexer(new ANTLRStringStream(cssContent)); var tokenStream = new CommonTokenStream(lexer); var parser = new CssParser(tokenStream); var commonTree = context .SectionedAction("CssParser", "Antlr") .Execute(() => { // Assign listener to parser. if (shouldLogDiagnostics) { var listener = Trace.Listeners.OfType <TextWriterTraceListener>().FirstOrDefault(); if (listener != null) { parser.TraceDestination = listener.Writer; } } var styleSheet = parser.main(); return(styleSheet.Tree as CommonTree); }); if (commonTree != null) { return(context.SectionedAction("CssParser", "CreateObjects").Execute(() => { if (shouldLogDiagnostics) { LogDiagnostics(cssContent, commonTree); } if (parser.NumberOfSyntaxErrors > 0) { throw new AggregateException("Syntax errors found.", parser._exceptions); } return CommonTreeTransformer.CreateStyleSheetNode(commonTree); })); } return(null); }
/// <summary>Assembles the background images</summary> /// <param name="stylesheetNode">the style sheet node</param> /// <param name="dpi">The dpi.</param> /// <param name="threadContext">The thread Context.</param> /// <param name="spritedImageContentItems">The sprited Image Content Items.</param> /// <returns>The stylesheet node with the sprited images aplied.</returns> private AstNode SpriteBackgroundImages(AstNode stylesheetNode, float dpi, IWebGreaseContext threadContext, BlockingCollection <ContentItem> spritedImageContentItems) { // The image assembly is a 3 step process: // 1. Scan the Css followed by Pretty Print // 2. Run the image assembly tool // 3. Update the Css with generated images followed by Pretty Print return(threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Spriting).Execute(() => { // Execute the pipeline for image assembly scan var scanLog = this.ExecuteImageAssemblyScan(stylesheetNode, threadContext); // Execute the pipeline for image assembly tool var imageMaps = new List <ImageLog>(); var count = 0; foreach (var scanOutput in scanLog.ImageAssemblyScanOutputs) { var spriteResult = this.SpriteImageFromLog(scanOutput, this.ImageAssembleScanDestinationFile + (count == 0 ? string.Empty : "." + count) + ".xml", scanLog.ImageAssemblyAnalysisLog, threadContext, spritedImageContentItems); if (spriteResult != null) { imageMaps.Add(spriteResult); count++; } } // Step # 8 - Execute the pipeline for image assembly update stylesheetNode = this.ExecuteImageAssemblyUpdate(stylesheetNode, imageMaps, dpi); if (!string.IsNullOrWhiteSpace(this.ImageSpritingLogPath)) { scanLog.ImageAssemblyAnalysisLog.Save(this.ImageSpritingLogPath); } var imageAssemblyAnalysisLog = scanLog.ImageAssemblyAnalysisLog; if (this.ErrorOnInvalidSprite && imageAssemblyAnalysisLog.FailedSprites.Any()) { foreach (var failedSprite in imageAssemblyAnalysisLog.FailedSprites) { var failureMessage = ImageAssemblyAnalysisLog.GetFailureMessage(failedSprite); if (!string.IsNullOrWhiteSpace(failedSprite.Image)) { threadContext.Log.Error( "Failed to sprite image {0}\r\nReason:{1}\r\nCss:{2}".InvariantFormat( failedSprite.Image, failureMessage, failedSprite.AstNode.PrettyPrint())); } else { threadContext.Log.Error( "Failed to sprite:{0}\r\nReason:{1}".InvariantFormat( failedSprite.Image, failureMessage)); } } } return stylesheetNode; })); }
/// <summary>Applies the css resource visitors.</summary> /// <param name="stylesheetNode">The stylesheet node.</param> /// <param name="resources">The resources.</param> /// <param name="threadContext">The thread Context.</param> /// <returns>The processed node.</returns> private static AstNode ApplyResources(AstNode stylesheetNode, IEnumerable <IDictionary <string, string> > resources, IWebGreaseContext threadContext) { if (resources.Any()) { threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.ResourcesResolution) .Execute(() => { stylesheetNode = stylesheetNode.Accept(new ResourceResolutionVisitor(resources)); }); } return(stylesheetNode); }
/// <summary>Applies the css optimization visitors.</summary> /// <param name="stylesheetNode">The stylesheet node.</param> /// <param name="threadContext">The thread Context.</param> /// <returns>The processed node.</returns> private AstNode ApplyOptimization(AstNode stylesheetNode, IWebGreaseContext threadContext) { // Step # 5 - Run the Css optimization visitors if (this.ShouldOptimize) { threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Optimize).Execute( () => { stylesheetNode = stylesheetNode.Accept(new OptimizationVisitor { ShouldMergeMediaQueries = this.ShouldMergeMediaQueries, ShouldPreventOrderBasedConflict = this.ShouldPreventOrderBasedConflict, ShouldMergeBasedOnCommonDeclarations = this.ShouldMergeBasedOnCommonDeclarations, NonMergeRuleSetSelectors = this.NonMergeSelectors }); stylesheetNode = stylesheetNode.Accept(new ColorOptimizationVisitor()); stylesheetNode = stylesheetNode.Accept(new FloatOptimizationVisitor()); }); } return(stylesheetNode); }
/// <summary>Processed the contents of the file and returns the processed content. /// returns null if anything went wrong, and reports any errors through the lot delegates.</summary> /// <param name="context">The context.</param> /// <param name="contentItem">The content of the file.</param> /// <param name="preprocessingConfig">The pre processing configuration</param> /// <param name="minimalOutput">Is the goal to have the most minimal output (true skips lots of comments)</param> /// <returns>The processed contents or null of an error occurred.</returns> public ContentItem Process(IWebGreaseContext context, ContentItem contentItem, PreprocessingConfig preprocessingConfig, bool minimalOutput) { var settingsMinimalOutput = preprocessingConfig != null && preprocessingConfig.Element != null && (bool?)preprocessingConfig.Element.Attribute("minimalOutput") == true; context.SectionedAction(SectionIdParts.Preprocessing, SectionIdParts.Process, "WgInclude") .MakeCachable(contentItem, new { minimalOutput }) .Execute(wgincludeCacheImportsSection => { var workingFolder = context.GetWorkingSourceDirectory(contentItem.RelativeContentPath); var content = contentItem.Content; if (string.IsNullOrWhiteSpace(content)) { return(true); } content = IncludeRegex.Replace(content, match => ReplaceInputs(match, workingFolder, wgincludeCacheImportsSection, minimalOutput || settingsMinimalOutput)); contentItem = ContentItem.FromContent(content, contentItem); return(true); }); return(contentItem); }
public ContentItem Process(IWebGreaseContext context, ContentItem contentItem, PreprocessingConfig preprocessingConfig, bool minimalOutput) { var settingsMinimalOutput = preprocessingConfig != null && preprocessingConfig.Element != null && (bool?)preprocessingConfig.Element.Attribute("minimalOutput") == true; var relativeContentPath = contentItem.RelativeContentPath; context.Log.Information("Sass: Processing contents for file {0}".InvariantFormat(relativeContentPath)); context.SectionedAction(SectionIdParts.Preprocessing, SectionIdParts.Process, "Sass") .Execute( () => { var sassCacheImportsSection = context.Cache.CurrentCacheSection; string fileToProcess = null; var isTemp = false; try { var workingDirectory = Path.IsPathRooted(relativeContentPath) ? Path.GetDirectoryName(relativeContentPath) : context.GetWorkingSourceDirectory(relativeContentPath); var content = ParseImports(contentItem.Content, workingDirectory, sassCacheImportsSection, minimalOutput || settingsMinimalOutput); var currentContentHash = context.GetValueHash(content); var contentIsUnchangedFromDisk = !string.IsNullOrWhiteSpace(contentItem.AbsoluteDiskPath) && File.Exists(contentItem.AbsoluteDiskPath) && context.GetFileHash(contentItem.AbsoluteDiskPath).Equals(currentContentHash); if (contentIsUnchangedFromDisk) { fileToProcess = contentItem.AbsoluteDiskPath; } else if (!string.IsNullOrWhiteSpace(relativeContentPath)) { fileToProcess = Path.Combine(context.Configuration.SourceDirectory ?? string.Empty, relativeContentPath); fileToProcess = PrependToExtension(fileToProcess, ".imports"); relativeContentPath = PrependToExtension(relativeContentPath, ".imports"); if (!File.Exists(fileToProcess) || !context.GetFileHash(fileToProcess).Equals(currentContentHash)) { File.WriteAllText(fileToProcess, content); } } else { isTemp = true; fileToProcess = Path.GetTempFileName() + Path.GetExtension(relativeContentPath); File.WriteAllText(fileToProcess, content); } content = ProcessFile(fileToProcess, workingDirectory, relativeContentPath, context); contentItem = content != null ? ContentItem.FromContent(content, contentItem) : null; return(true); } finally { if (isTemp && !string.IsNullOrWhiteSpace(fileToProcess)) { try { File.Delete(fileToProcess); } catch (Exception) { } } } }); return(contentItem); }
/// <summary>The execute.</summary> /// <param name="fileType">The file type.</param> /// <param name="activity">The activity.</param> /// <param name="configurationPath">The configuration path</param> /// <param name="sessionContext">The session context</param> /// <returns>The <see cref="Tuple"/>.</returns> private bool Execute(FileTypes fileType, ActivityName activity, string configurationPath, IWebGreaseContext sessionContext) { var availableFileSetsForFileType = 0; var fullPathToConfigFiles = Path.GetFullPath(configurationPath); var contentTypeSectionId = new[] { fileType.ToString(), SectionIdParts.WebGreaseBuildTask }; var sessionSuccess = sessionContext .SectionedAction(SectionIdParts.WebGreaseBuildTaskSession, fileType.ToString()) .MakeCachable(new { fullPathToConfigFiles, activity }) .Execute(sessionCacheSection => { var sessionCacheData = sessionCacheSection.GetCacheData <SessionCacheData>(CacheFileCategories.SolutionCacheConfig); var inputFiles = new InputSpec { Path = fullPathToConfigFiles, IsOptional = true, SearchOption = this.ConfigurationPathRecursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly }.GetFiles(); var contentTypeSuccess = sessionContext .SectionedAction(contentTypeSectionId) .MakeCachable(new { activity, inputFiles, sessionContext.Configuration }, activity == ActivityName.Bundle) .Execute(contentTypeCacheSection => { var success = true; try { // get a list of the files in the configuration folder foreach (var configFile in inputFiles) { var executeConfigFileResult = ExecuteConfigFile(sessionContext, configFile, activity, fileType, this.Measure, fullPathToConfigFiles); success &= executeConfigFileResult.Item1; availableFileSetsForFileType += executeConfigFileResult.Item2; GC.Collect(); } } catch (BuildWorkflowException exception) { this.LogError(exception.Subcategory, exception.ErrorCode, exception.HelpKeyword, exception.File, exception.LineNumber, exception.EndLineNumber, exception.ColumnNumber, exception.EndColumnNumber, exception.Message); return(false); } catch (Exception exception) { this.LogError(exception, exception.Message, null); return(false); } if (success) { // Add the current cachesection to the sessionCacheData if (sessionCacheData != null) { sessionCacheData.SetConfigTypeUniqueKey(this.ConfigType, contentTypeCacheSection.UniqueKey); sessionCacheSection.SetCacheData(CacheFileCategories.SolutionCacheConfig, sessionCacheData); } } return(success); }); if (contentTypeSuccess) { // Add the cache sections of the other already cached contentTypes (Debug/Release) so that they will not get removed. this.HandleOtherContentTypeCacheSections(sessionContext, sessionCacheSection, sessionCacheData, contentTypeSectionId); } return(contentTypeSuccess); }); if (sessionSuccess && (availableFileSetsForFileType > 0)) { sessionContext.Cache.CleanUp(); } // Return tuple, that contains,the success, the results and the configuration for this thread. return(sessionSuccess); }