├── .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 |
--------------------------------------------------------------------------------