├── .gitignore ├── CSSCompare.sln ├── CSSCompare ├── CSSCompare.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── app.config ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Rr]elease/ 12 | x64/ 13 | build/ 14 | [Bb]in/ 15 | [Oo]bj/ 16 | 17 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 18 | !packages/*/build/ 19 | 20 | # MSTest test Results 21 | [Tt]est[Rr]esult*/ 22 | [Bb]uild[Ll]og.* 23 | 24 | *_i.c 25 | *_p.c 26 | *.ilk 27 | *.meta 28 | *.obj 29 | *.pch 30 | *.pdb 31 | *.pgc 32 | *.pgd 33 | *.rsp 34 | *.sbr 35 | *.tlb 36 | *.tli 37 | *.tlh 38 | *.tmp 39 | *.tmp_proj 40 | *.log 41 | *.vspscc 42 | *.vssscc 43 | .builds 44 | *.pidb 45 | *.log 46 | *.scc 47 | 48 | # Visual C++ cache files 49 | ipch/ 50 | *.aps 51 | *.ncb 52 | *.opensdf 53 | *.sdf 54 | *.cachefile 55 | 56 | # Visual Studio profiler 57 | *.psess 58 | *.vsp 59 | *.vspx 60 | 61 | # Guidance Automation Toolkit 62 | *.gpState 63 | 64 | # ReSharper is a .NET coding add-in 65 | _ReSharper*/ 66 | *.[Rr]e[Ss]harper 67 | 68 | # TeamCity is a build add-in 69 | _TeamCity* 70 | 71 | # DotCover is a Code Coverage Tool 72 | *.dotCover 73 | 74 | # NCrunch 75 | *.ncrunch* 76 | .*crunch*.local.xml 77 | 78 | # Installshield output folder 79 | [Ee]xpress/ 80 | 81 | # DocProject is a documentation generator add-in 82 | DocProject/buildhelp/ 83 | DocProject/Help/*.HxT 84 | DocProject/Help/*.HxC 85 | DocProject/Help/*.hhc 86 | DocProject/Help/*.hhk 87 | DocProject/Help/*.hhp 88 | DocProject/Help/Html2 89 | DocProject/Help/html 90 | 91 | # Click-Once directory 92 | publish/ 93 | 94 | # Publish Web Output 95 | *.Publish.xml 96 | 97 | # NuGet Packages Directory 98 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 99 | #packages/ 100 | 101 | # Windows Azure Build Output 102 | csx 103 | *.build.csdef 104 | 105 | # Windows Store app package directory 106 | AppPackages/ 107 | 108 | # Others 109 | sql/ 110 | *.Cache 111 | ClientBin/ 112 | [Ss]tyle[Cc]op.* 113 | ~$* 114 | *~ 115 | *.dbmdl 116 | *.[Pp]ublish.xml 117 | *.pfx 118 | *.publishsettings 119 | 120 | # RIA/Silverlight projects 121 | Generated_Code/ 122 | 123 | # Backup & report files from converting an old project file to a newer 124 | # Visual Studio version. Backup files are not needed, because we have git ;-) 125 | _UpgradeReport_Files/ 126 | Backup*/ 127 | UpgradeLog*.XML 128 | UpgradeLog*.htm 129 | 130 | # SQL Server files 131 | App_Data/*.mdf 132 | App_Data/*.ldf 133 | 134 | 135 | #LightSwitch generated files 136 | GeneratedArtifacts/ 137 | _Pvt_Extensions/ 138 | ModelManifest.xml 139 | 140 | # ========================= 141 | # Windows detritus 142 | # ========================= 143 | 144 | # Windows image file caches 145 | Thumbs.db 146 | ehthumbs.db 147 | 148 | # Folder config file 149 | Desktop.ini 150 | 151 | # Recycle Bin used on file shares 152 | $RECYCLE.BIN/ 153 | 154 | # Mac desktop service store files 155 | .DS_Store 156 | 157 | # OpaqueMail settings 158 | OpaqueMail.TestClient.exe.config -------------------------------------------------------------------------------- /CSSCompare.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSSCompare", "CSSCompare\CSSCompare.csproj", "{11D1E1B0-6AAB-4B2E-B804-1FD67F13DBF1}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4AFB9CA8-CC18-405B-82C7-214B63A51A85}" 9 | ProjectSection(SolutionItems) = preProject 10 | LICENSE = LICENSE 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|x86 = Debug|x86 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {11D1E1B0-6AAB-4B2E-B804-1FD67F13DBF1}.Debug|x86.ActiveCfg = Debug|x86 21 | {11D1E1B0-6AAB-4B2E-B804-1FD67F13DBF1}.Debug|x86.Build.0 = Debug|x86 22 | {11D1E1B0-6AAB-4B2E-B804-1FD67F13DBF1}.Release|x86.ActiveCfg = Release|x86 23 | {11D1E1B0-6AAB-4B2E-B804-1FD67F13DBF1}.Release|x86.Build.0 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /CSSCompare/CSSCompare.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {11D1E1B0-6AAB-4B2E-B804-1FD67F13DBF1} 9 | Exe 10 | Properties 11 | SPBert.CSSCompare 12 | CSSCompare 13 | v4.5 14 | 512 15 | SAK 16 | SAK 17 | SAK 18 | SAK 19 | 20 | 21 | 22 | x86 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | false 31 | 32 | 33 | x86 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | false 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /CSSCompare/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace SPBert.CSSCompare 8 | { 9 | /// 10 | /// CSS Compare - naive comparison tool between two stylesheets. 11 | /// Accepts filenames of two stylesheets. 12 | /// Compares and outputs differences from v1 to v2. 13 | /// 14 | class Program 15 | { 16 | /// 17 | /// CSS Compare - naive comparison tool between two stylesheets. 18 | /// 19 | /// Strings specifying filenames of v1file and v2file. 20 | static void Main(string[] args) 21 | { 22 | #region Parse Arguments 23 | string v1file = ""; 24 | string v2file = ""; 25 | 26 | // Parse command line parameters. 27 | string lastArg = ""; 28 | foreach (string arg in args) 29 | { 30 | switch (lastArg) 31 | { 32 | case "-v1": 33 | case "-v1file": 34 | case "-file1": 35 | v1file = arg; 36 | break; 37 | case "-v2": 38 | case "-v2file": 39 | case "-file2": 40 | v2file = arg; 41 | break; 42 | } 43 | lastArg = arg.ToLower(); 44 | } 45 | #endregion Parse Arguments 46 | 47 | #region Error Handling 48 | // Handle invalid parameters. 49 | if (string.IsNullOrEmpty(v1file) || string.IsNullOrEmpty(v2file)) 50 | { 51 | Console.WriteLine("Please specify an original file with the -v1 parameter and the comparison file with the -v2 parameter."); 52 | return; 53 | } 54 | 55 | // Handle invalid files 56 | if (!File.Exists(v1file)) 57 | { 58 | Console.WriteLine("File \"" + v1file + "\" not found."); 59 | return; 60 | } 61 | if (!File.Exists(v2file)) 62 | { 63 | Console.WriteLine("File \"" + v2file + "\" not found."); 64 | return; 65 | } 66 | #endregion Error Handling 67 | 68 | #region Read and Normalize Input Files 69 | // Read and parse both files. 70 | StreamReader sr = new StreamReader(v1file); 71 | string v1 = sr.ReadToEnd(); 72 | sr.Dispose(); 73 | 74 | sr = new StreamReader(v2file); 75 | string v2 = sr.ReadToEnd(); 76 | sr.Dispose(); 77 | 78 | string normalizedV1 = NormalizeCSS(v1); 79 | string normalizedV2 = NormalizeCSS(v2); 80 | #endregion Read and Normalize Input Files 81 | 82 | #region Build Original Objects 83 | // Object to keep track of elements and their styles. 84 | Dictionary> Styles = new Dictionary>(); 85 | 86 | bool inComment = false; 87 | string currentMediaBlock = ""; 88 | string currentElement = ""; 89 | string lastLine = ""; 90 | string[] lines = normalizedV1.Split('\n'); 91 | 92 | // Loop through and track all styles. 93 | foreach (string line in lines) 94 | { 95 | if (line.StartsWith("@")) 96 | { 97 | currentMediaBlock = line.Substring(0, line.Length); 98 | } 99 | else if (line == "*/") 100 | { 101 | inComment = false; 102 | } 103 | else if (!inComment) 104 | { 105 | switch (line) 106 | { 107 | case "/*": 108 | inComment = true; 109 | break; 110 | case "{": 111 | if (!lastLine.StartsWith("@")) 112 | { 113 | currentElement = lastLine; 114 | 115 | if (!Styles.ContainsKey(currentMediaBlock + "|" + currentElement)) 116 | Styles.Add(currentMediaBlock + "|" + currentElement, new HashSet()); 117 | } 118 | break; 119 | case "}": 120 | if (!string.IsNullOrEmpty(currentElement)) 121 | currentElement = ""; 122 | else 123 | currentMediaBlock = ""; 124 | break; 125 | default: 126 | if (!string.IsNullOrEmpty(currentElement)) 127 | Styles[currentMediaBlock + "|" + currentElement].Add(line.Trim()); 128 | break; 129 | } 130 | } 131 | lastLine = line; 132 | } 133 | #endregion Build Original Objects 134 | 135 | #region Compare Updated Objects 136 | currentMediaBlock = ""; 137 | currentElement = ""; 138 | lastLine = ""; 139 | lines = normalizedV2.Split('\n'); 140 | 141 | // Loop through and remove duplicate styles. 142 | foreach (string line in lines) 143 | { 144 | if (line.StartsWith("@")) 145 | currentMediaBlock = line; 146 | else if (line == "*/") 147 | inComment = false; 148 | else if (!inComment) 149 | { 150 | switch (line) 151 | { 152 | case "/*": 153 | inComment = true; 154 | break; 155 | case "{": 156 | if (!lastLine.StartsWith("@")) 157 | { 158 | currentElement = lastLine; 159 | 160 | if (!Styles.ContainsKey(currentMediaBlock + "|" + currentElement)) 161 | Styles.Add(currentMediaBlock + "|" + currentElement, new HashSet()); 162 | } 163 | break; 164 | case "}": 165 | if (!string.IsNullOrEmpty(currentElement)) 166 | currentElement = ""; 167 | else 168 | currentMediaBlock = ""; 169 | break; 170 | default: 171 | if (!string.IsNullOrEmpty(currentElement)) 172 | { 173 | string normalizedLine = line.Trim(); 174 | if (Styles[currentMediaBlock + "|" + currentElement].Contains(normalizedLine)) 175 | Styles[currentMediaBlock + "|" + currentElement].Remove(normalizedLine); 176 | } 177 | break; 178 | } 179 | } 180 | lastLine = line; 181 | } 182 | #endregion Compare Updated Objects 183 | 184 | #region Print Remaining Objects 185 | string lastMediaBlock = ""; 186 | 187 | // Loop through and print out styles unique to the first CSS file. 188 | foreach (string key in Styles.Keys) 189 | { 190 | if (Styles[key].Count > 0) 191 | { 192 | string extraIndentation = ""; 193 | string mediaBlock = key.Substring(0, key.IndexOf("|")); 194 | if (mediaBlock.Length > 0) 195 | { 196 | if (mediaBlock != lastMediaBlock) 197 | { 198 | if (!string.IsNullOrEmpty(lastMediaBlock)) 199 | Console.WriteLine("}"); 200 | Console.WriteLine(mediaBlock + "\n" + "{"); 201 | } 202 | extraIndentation = "\t"; 203 | } 204 | else 205 | { 206 | if (!string.IsNullOrEmpty(lastMediaBlock)) 207 | Console.WriteLine("}"); 208 | extraIndentation = ""; 209 | } 210 | string element = key.Substring(key.IndexOf("|") + 1); 211 | 212 | Console.WriteLine(extraIndentation + element + "{"); 213 | foreach (string value in Styles[key]) 214 | Console.WriteLine(extraIndentation + "\t" + value); 215 | Console.WriteLine(extraIndentation + "}"); 216 | 217 | lastMediaBlock = mediaBlock; 218 | } 219 | } 220 | 221 | if (!string.IsNullOrEmpty(lastMediaBlock)) 222 | Console.WriteLine("}"); 223 | #endregion Print Remaining Objects 224 | } 225 | 226 | /// 227 | /// Function to standardize CSS file spacing and other formatting. 228 | /// 229 | /// The raw input to be normalized. 230 | /// Normalized output, with whitespace minimized. 231 | private static string NormalizeCSS(string input) 232 | { 233 | #region Preliminary Cleanup 234 | // Collapse whitespace. 235 | input = input.Replace('\r', '\n'); 236 | input = input.Replace("\t", ""); 237 | 238 | while (input.IndexOf(" ") > -1) 239 | input = input.Replace(" ", " "); 240 | #endregion Preliminary Cleanup 241 | 242 | string[] lines = input.Split('\n'); 243 | 244 | StringBuilder output = new StringBuilder(); 245 | 246 | #region Normalize Lines 247 | // Iterate through and normalize each line. 248 | foreach (string line in lines) 249 | { 250 | if (!string.IsNullOrEmpty(line)) 251 | AddNormalizedLine(line, ref output); 252 | } 253 | #endregion Normalize Lines 254 | 255 | return output.ToString().Replace("\n ", "\n"); 256 | } 257 | 258 | /// 259 | /// Function to standardize CSS line spacing and other formatting. 260 | /// 261 | /// Line to be normalized. 262 | /// Normalized line. 263 | private static void AddNormalizedLine(string line, ref StringBuilder output) 264 | { 265 | // Eliminate trailing and leading whitespace. 266 | string currentLine = line.Trim(); 267 | 268 | // Break individual styles into their own lines. 269 | int semicolon = currentLine.IndexOf(";"); 270 | while (semicolon > -1 && !string.IsNullOrEmpty(currentLine)) 271 | { 272 | // Handle empty lines. 273 | if (semicolon == 0) { 274 | if (currentLine.Length > 1) 275 | currentLine = currentLine.Substring(1); 276 | else 277 | return; 278 | } 279 | 280 | if (semicolon < currentLine.Length - 1) 281 | { 282 | { 283 | AddNormalizedLine(currentLine.Substring(0, semicolon + 1), ref output); 284 | currentLine = currentLine.Substring(semicolon); 285 | } 286 | } 287 | else 288 | { 289 | // Remove trailing semicolon. 290 | currentLine = currentLine.Substring(0, currentLine.Length - 1); 291 | } 292 | semicolon = currentLine.IndexOf(";"); 293 | } 294 | 295 | // Move each of the following characters to its own line. 296 | string[] specialCharacters = new string[] { "{", "}", "/*", "*/" }; 297 | foreach (string specialCharacter in specialCharacters) 298 | { 299 | int openCharacter = currentLine.IndexOf(specialCharacter); 300 | while (openCharacter > -1 && !string.IsNullOrEmpty(currentLine)) 301 | { 302 | if (openCharacter == 0) 303 | output.Append(specialCharacter + "\n"); 304 | else 305 | { 306 | AddNormalizedLine(currentLine.Substring(0, openCharacter), ref output); 307 | output.Append(specialCharacter + "\n"); 308 | } 309 | 310 | currentLine = currentLine.Substring(openCharacter + specialCharacter.Length); 311 | openCharacter = currentLine.IndexOf(specialCharacter); 312 | } 313 | } 314 | 315 | // Append newline. 316 | if (!string.IsNullOrEmpty(currentLine)) 317 | output.Append(currentLine + "\n"); 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /CSSCompare/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CSSCompare")] 9 | [assembly: AssemblyDescription("Compares two CSS files and shows what's unique to the first.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Bert Johnson")] 12 | [assembly: AssemblyProduct("CSSCompare")] 13 | [assembly: AssemblyCopyright("Copyright © Bert Johnson 2011-2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7e537f21-41d9-471a-9706-ad86d47b2187")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("2.2.*")] 35 | [assembly: AssemblyVersion("2.3.0.0")] 36 | [assembly: AssemblyFileVersion("2.3.0.0")] 37 | -------------------------------------------------------------------------------- /CSSCompare/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2015 Bert Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CSSCompare 2 | ========== 3 | 4 | CSS Compare is a utility to compare two CSS files and output unique styles. It's useful for exporting customizations and managing stylesheet versions. 5 | 6 | Unlike traditional text comparison tools, CSS Compare evaluates individual CSS styles instead of pure text blocks, allowing for net comparisons regardless of where a style may appear in the file. CSS Compare is compatible with all levels of CSS. 7 | 8 | Sample Usage 9 | ============ 10 | 11 | `CSSCompare.exe -v1 C:\customized.css -v2 C:\original.css > C:\difference.css` 12 | 13 | Background 14 | ========== 15 | 16 | This project originated when I was working with a highly customized SharePoint 2007 farm. Its CSS files had been directly modified all over the place. Since SharePoint 2010 had new CSS files, I had to extract all styles that were tailored for that site. 17 | 18 | This utility allowed me to easily export all of the customizations to one file and drop it into SharePoint 2010, which worked perfectly. I've since found a host of other uses for it. 19 | 20 | Since it does style-by-style CSS comparisons instead of block-level text comparisons like other tools, it works better than pure text comparison tools. 21 | 22 | History 23 | ======= 24 | 25 | For previous releases and context, [view this project archive on CodePlex](https://csscompare.codeplex.com/). 26 | 27 | License 28 | ======= 29 | 30 | Copyright © 2011-2015 [Bert Johnson](https://bertjohnson.com) 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 37 | --------------------------------------------------------------------------------