/// <summary> /// This will never return null, it will raise an exception if unable to satisfy the request /// </summary> public ITextFileLoader Get() { var scopingHtmlTagReplaceString = "REPLACEME"; ITextFileLoader singleFileLoader = new LessCssOpeningHtmlTagRenamer( _contentLoader, scopingHtmlTagReplaceString ); var sourceMappingMarkerIdGenerator = new SourceMappingMarkerIdGenerator(); if (_sourceMappingMarkerInjection == SourceMappingMarkerInjectionOptions.Inject) { singleFileLoader = new LessCssLineNumberingTextFileLoader( new LessCssCommentRemovingTextFileLoader(singleFileLoader), sourceMappingMarkerIdGenerator.MarkerGenerator, selector => selector != scopingHtmlTagReplaceString // Don't insert marker ids on wrapper selectors that will be removed ); } return(new MediaQueryGroupingCssLoader( new DotLessCssCssLoader( new SameFolderImportFlatteningCssLoader( singleFileLoader, _sourceMappingMarkerInjection == SourceMappingMarkerInjectionOptions.Inject ? SameFolderImportFlatteningCssLoader.ContentLoaderCommentRemovalBehaviourOptions.CommentsAreAlreadyRemoved : SameFolderImportFlatteningCssLoader.ContentLoaderCommentRemovalBehaviourOptions.ContentIsUnprocessed, _errorBehaviour, _errorBehaviour, _logger ), () => sourceMappingMarkerIdGenerator.GetInsertedMarkerIds(), scopingHtmlTagReplaceString, _errorBehaviour, _logger ) )); }
/// <summary> /// This will never return null, it will raise an exception if unable to satisfy the request /// </summary> public ITextFileLoader Get() { ITextFileLoader singleFileLoader = new LessCssKeyFrameScoper(_contentLoader); var sourceMappingMarkerIdGenerator = new SourceMappingMarkerIdGenerator(); if (_sourceMappingMarkerInjection == SourceMappingMarkerInjectionOptions.Inject) { singleFileLoader = new LessCssLineNumberingTextFileLoader( new LessCssCommentRemovingTextFileLoader(singleFileLoader), sourceMappingMarkerIdGenerator.MarkerGenerator, null // optionalSelectorMarkerInsertionCondition (null => insert markers for all selectors) ); } return(new DotLessCssCssLoader( new SameFolderImportFlatteningCssLoader( singleFileLoader, _sourceMappingMarkerInjection == SourceMappingMarkerInjectionOptions.Inject ? SameFolderImportFlatteningCssLoader.ContentLoaderCommentRemovalBehaviourOptions.CommentsAreAlreadyRemoved : SameFolderImportFlatteningCssLoader.ContentLoaderCommentRemovalBehaviourOptions.ContentIsUnprocessed, _errorBehaviour, _errorBehaviour, _logger ), () => sourceMappingMarkerIdGenerator.GetInsertedMarkerIds(), null, // optionalTagNameToRemove (null => no selectors need removing) _errorBehaviour, _logger )); }
/// <summary> /// This will throw an exception for null content or if otherwise unable to satisfy the request, it will never return null /// </summary> private string Process(string relativePath, string content) { if (string.IsNullOrWhiteSpace(relativePath)) { throw new ArgumentException($"Null/blank {nameof(relativePath)} specified"); } if (content == null) { throw new ArgumentNullException("content"); } relativePath = relativePath.Trim(); if (relativePath.EndsWith(".css", StringComparison.OrdinalIgnoreCase)) { relativePath = relativePath.Substring(0, relativePath.Length - 4); } else if (relativePath.EndsWith(".less", StringComparison.OrdinalIgnoreCase)) { relativePath = relativePath.Substring(0, relativePath.Length - 5); } var prefixToInsert = SourceMappingMarkerIdGenerator.TryToGetHtmlIdFriendlyVersion(relativePath); if (prefixToInsert == null) { // If the path string doesn't contain any content that may be used as a "custom ident" then make something up because we need to use something as // the prefix, otherwise the animation won't be scoped to the current files prefixToInsert = "scope" + relativePath.GetHashCode(); } // Note: Since we have enumerate the segments set twice (if any animation names are found to be renamed) then I considered calling ToArray on it so that the parsing // work would only be done once - however, this means that the content has to be full loaded into an array in memory and I don't have any information that indicates // that the additional memory use would be a worthwhile tradeoff for the double-parsing work (the parsing isn't very expensive and so doing it twice isn't necessarily // the worst thing in the world). If no animation names are found that need to be renamed then the parsing work will only be performed once. Similar logic is applied // to the result of the RewriteKeyFrameNames method - it needs to be executed once to determine whether any animation names need to be changed and (if any are found) // then it will be executed a second time when RewriteAnimationNames is called (but the RewriteKeyFrameNames work isn't very expensive and so there isn't necessarily // any harm in doing it twice). var segments = Parser.ParseLESS(content); var rewrittenNames = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); // Case insensitive (see https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident) segments = RewriteKeyFrameNames( segments, nestedAnimationName => { var rewrittedName = prefixToInsert + "_" + nestedAnimationName; rewrittenNames[nestedAnimationName] = rewrittedName; return(rewrittedName); } ); ForceEvaluation(segments); if (!rewrittenNames.Any()) { return(content); } segments = RewriteAnimationNames( segments, animationName => rewrittenNames.ContainsKey(animationName) ? rewrittenNames[animationName] : animationName ); var rewrittenContentBuilder = new StringBuilder(); foreach (var segment in segments) { rewrittenContentBuilder.Append(segment.Value); } return(rewrittenContentBuilder.ToString()); }