├── CRDiff.sln ├── CRDiff ├── App.config ├── CRDiff.cs ├── CRDiff.csproj ├── CRDiff_ReadMe.html ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── CRSerializer ├── CRSerialize.cs ├── CRSerializer.csproj ├── CustomJsonResolver.cs ├── Extensions.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config └── README.md /CRDiff.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRDiff", "CRDiff\CRDiff.csproj", "{5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRSerializer", "CRSerializer\CRSerializer.csproj", "{612827CE-316D-40E7-9082-CB0A7B9BE047}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Debug|x64.ActiveCfg = Debug|x64 23 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Debug|x64.Build.0 = Debug|x64 24 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Debug|x86.ActiveCfg = Debug|x86 25 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Debug|x86.Build.0 = Debug|x86 26 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Release|x64.ActiveCfg = Release|x64 29 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Release|x64.Build.0 = Release|x64 30 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Release|x86.ActiveCfg = Release|x86 31 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89}.Release|x86.Build.0 = Release|x86 32 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Debug|x64.Build.0 = Debug|Any CPU 36 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Debug|x86.Build.0 = Debug|Any CPU 38 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Release|x64.ActiveCfg = Release|Any CPU 41 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Release|x64.Build.0 = Release|Any CPU 42 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Release|x86.ActiveCfg = Release|Any CPU 43 | {612827CE-316D-40E7-9082-CB0A7B9BE047}.Release|x86.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {A498A971-0DAF-4AA7-97D5-CFA1BA9D98E6} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /CRDiff/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CRDiff/CRDiff.cs: -------------------------------------------------------------------------------- 1 | using CRSerializer; 2 | using System; 3 | using System.Configuration; 4 | using System.Collections.Specialized; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Reflection; 8 | using System.Collections.Generic; 9 | 10 | namespace CRDiff 11 | { 12 | /* Features: 13 | * - Integratable with TortoiseGit (as well as subversion and stand-alone) 14 | * - Checks file extension, and if it is not .rpt, file passes directly to text diff tool. 15 | * - When a report, opens it/them. If error, output message 16 | * - Serialize all the report details into a text file we want to be able to compare, like 17 | * Command query 18 | * element dimensions and settings 19 | * subreports 20 | * - Save serialized files and pass to the text diff tool 21 | * - Clean-up (delete) temp files after text diff tool is closed. 22 | * 23 | * Road Map: 24 | * - Add command-line switches such as /d[ifftool], /1[stReport], /2[ndReport], /t[empOutput] 25 | * - Add configuration (either in registry or app.config) 26 | * - Hook into shell context menu to select report files to diff. 27 | * - Serialization can be into either xml or json (command line switch -x[ml] or -j[son] 28 | * 29 | * Task list: 30 | * - Add versioning ie YY.MM.dd.gitCommit (done) 31 | * 32 | TODO: Need to serialize: 33 | element suppression formulas (and other formulas for elements) 34 | sub-textbox formatting (ie font changes, coloring, tab settings, etc) 35 | "Lock Position" 36 | "change number sign" 37 | "Keep Data" setting 38 | 39 | Done: 40 | Page Setup (ie Printer, margins) 41 | 42 | * Testing arguments: 43 | * "C:\Program Files (x86)\Compare It!\wincmp3.exe" "C:\dev\svn\Reports\Crystal Reports\trunk\KinderMorgan_CS_MT_Testing.rpt" "C:\dev\svn\Reports\Crystal Reports\trunk\KinderMorgan_CS_MT.rpt" 44 | * "C:\dev\svn\Reports\Crystal Reports\trunk\KinderMorgan_CS_MT_Testing.rpt" KM_CS_MT_Testing.json 45 | */ 46 | 47 | internal class CRDiff 48 | { 49 | private static void Main(string[] args) 50 | { 51 | switch (args.Length) 52 | { 53 | case 1: 54 | { 55 | // Assumption is a report file is being serialized to .json 56 | if (Path.GetExtension(args[0]) == ".rpt") 57 | { 58 | SerializeToFile(args[0]); 59 | //SerializeToStdOut(args[0]); 60 | return; 61 | } 62 | 63 | var pathExt = new List(System.Environment.GetEnvironmentVariable("PATHEXT").Split(';')); 64 | if (pathExt.Contains(Path.GetExtension(args[0]).ToUpper())) 65 | { 66 | //assume it's an executable that can compare files 67 | if (ValidateFile(args[0])) 68 | { 69 | SetTextDiffTool(args[0]); 70 | } 71 | return; 72 | } 73 | 74 | { 75 | Console.ForegroundColor = ConsoleColor.Red; 76 | Console.WriteLine(args[0]); 77 | Console.WriteLine("doesn't appear to be a report or executable file"); 78 | Console.ResetColor(); 79 | Usage(); 80 | ReadKey(); 81 | } 82 | break; 83 | } 84 | case 2: 85 | { 86 | // Assumption is that the diff tool calls with the report file to convert 87 | // and a temp filepath which it will clean up when done. 88 | if (Path.GetExtension(args[0]) == ".rpt" 89 | && File.Exists(args[0]) 90 | && Path.GetExtension(args[1]) != ".rpt" // 2nd param is not a report file 91 | ) 92 | { 93 | //Console.WriteLine($"Creating \"{args[1]}\" from \"{args[0]}\""); 94 | SerializeToFile(args[0], args[1]); 95 | return; 96 | } 97 | 98 | // If the 2nd filepath is also .rpt, check config for difftool and use it. If difftool doesn't exist, prompt. 99 | // 2nd param is a report (presumably to compare with) 100 | // Do we have a diff tool? 101 | var diffTool = GetTextDiffTool(); 102 | if (string.IsNullOrEmpty(diffTool)) 103 | { 104 | Console.WriteLine("Please provide the path to your text diff tool:"); 105 | diffTool = Console.ReadLine().Replace("\"",""); 106 | } 107 | 108 | if (CompareFiles(diffTool, args[0], args[1])) 109 | { 110 | if (diffTool != GetTextDiffTool()) 111 | { 112 | SetTextDiffTool(diffTool); 113 | } 114 | } 115 | else 116 | { 117 | ReadKey(); 118 | } 119 | 120 | break; 121 | } 122 | case 3: 123 | { 124 | // Allows use of an alternate text diff tool without updating config 125 | CompareFiles(args[0], args[1], args[2]); 126 | 127 | break; 128 | } 129 | default: 130 | { 131 | Usage(); 132 | ReadKey(); 133 | break; 134 | } 135 | } 136 | } // end Main() 137 | 138 | /// 139 | /// Creates json files for two reports and sends them to the diff tool. 140 | /// If json files already exist, they are updated. If they didn't previously exist, they are deleted. 141 | /// 142 | /// 143 | /// 144 | /// 145 | /// true if successful 146 | private static bool CompareFiles(string diffTool, string filePath1, string filePath2) 147 | { 148 | string 149 | textFile1, 150 | textFile2; 151 | bool 152 | file1IsRpt = false, 153 | file2IsRpt = false, 154 | textFile1Exists = false, 155 | textFile2Exists = false; 156 | 157 | if (!ValidateFile(diffTool, "The diff tool") 158 | || !ValidateFile(filePath1, "The first file") 159 | || !ValidateFile(filePath2, "The second file")) 160 | { 161 | return false; 162 | } 163 | 164 | try 165 | { 166 | if (Path.GetExtension(filePath1) == ".rpt") 167 | { 168 | file1IsRpt = true; 169 | textFile1 = Path.ChangeExtension(filePath1, "json"); 170 | textFile1Exists = File.Exists(textFile1); 171 | textFile1 = SerializeToFile(filePath1, textFile1, reportOrder: 1); 172 | } 173 | else 174 | { 175 | textFile1 = filePath1; 176 | } 177 | 178 | if (Path.GetExtension(filePath2) == ".rpt") 179 | { 180 | file2IsRpt = true; 181 | textFile2 = Path.ChangeExtension(filePath2, "json"); 182 | textFile2Exists = File.Exists(textFile2); 183 | textFile2 = SerializeToFile(filePath2, textFile2, reportOrder: 2); 184 | } 185 | else 186 | { 187 | textFile2 = filePath2; 188 | } 189 | 190 | var diffParams = $"\"{textFile1}\" \"{textFile2}\""; 191 | Console.Write($"Starting \"{diffTool}\" {diffParams}"); 192 | 193 | var diffProc = Process.Start( 194 | diffTool, 195 | diffParams 196 | ); 197 | 198 | diffProc.WaitForExit(); 199 | 200 | // Clean up (delete) temp files after text diff tool is finished with them (if they didn't exist already) 201 | if (file1IsRpt && !textFile1Exists) 202 | File.Delete(textFile1); 203 | 204 | if (file2IsRpt && !textFile2Exists) 205 | File.Delete(textFile2); 206 | return true; 207 | } 208 | catch (Exception ex) 209 | { 210 | Console.WriteLine($"Error: {ex.Message} {ex.InnerException?.Message}"); 211 | ReadKey(); 212 | return false; 213 | } 214 | } 215 | 216 | /// 217 | /// Checks if file exists 218 | /// 219 | /// 220 | /// 221 | /// true if file exists 222 | private static bool ValidateFile(string filePath, string ifEmpty = "A file") 223 | { 224 | //TODO: If file is an executable, search path environment 225 | // ref: https://stackoverflow.com/questions/3855956/check-if-an-executable-exists-in-the-windows-path 226 | // ref: https://stackoverflow.com/a/58523041/2394826 227 | 228 | if (!File.Exists(filePath)) 229 | { 230 | if (string.IsNullOrEmpty(filePath)) 231 | { 232 | filePath = ifEmpty; 233 | } 234 | Console.ForegroundColor = ConsoleColor.Red; 235 | Console.WriteLine($"{filePath} was not found"); 236 | Console.ResetColor(); 237 | Usage(); 238 | return false; 239 | } 240 | return true; 241 | } 242 | 243 | /// 244 | /// 245 | /// 246 | /// 247 | private static void Usage(string[] args = null) 248 | { 249 | Console.WriteLine("Usage:"); 250 | Console.WriteLine("CRDiff ReportFilename.rpt"); 251 | Console.WriteLine(" - Creates a serialized .json file of the report"); 252 | Console.WriteLine("CRDiff ReportFilename.rpt TempFilename.json"); 253 | Console.WriteLine(" - Writes json to TempFileName. Allows use as a .rpt converter in many text diff tools"); 254 | Console.WriteLine("CRDiff ReportFilename1.rpt ReportFilename2.rpt"); 255 | Console.WriteLine("CRDiff DiffAppPath.exe ReportFilename1.rpt ReportFilename2.rpt"); 256 | Console.WriteLine(" - Serializes 2 reports and passes serialized .json files to DiffApp for comparison"); 257 | Console.WriteLine(); 258 | Console.WriteLine($"CRDiff Version: {CRDiffProductVersion()}"); 259 | Console.WriteLine($"Path: {AppDomain.CurrentDomain.BaseDirectory}{Environment.GetCommandLineArgs()[0]}"); 260 | Console.WriteLine(); 261 | 262 | try 263 | { 264 | var version = (new CRSerialize()).CRVersion(); 265 | Console.WriteLine($"CR Version: {version}"); 266 | } 267 | catch (Exception e) 268 | { 269 | Console.WriteLine($"CR library is not available ({e.Message} {e.InnerException.Message})"); 270 | } 271 | } 272 | 273 | private static void Instructions() 274 | { 275 | Console.WriteLine(@" 276 | 277 | "); 278 | } 279 | 280 | private static void ReadKey() 281 | { 282 | Console.WriteLine("Press any key to exit..."); 283 | Console.ReadKey(); 284 | } 285 | 286 | private static string SerializeToFile(string rptFile, string textFile = null, int reportOrder = 1) 287 | { 288 | var serializer = new CRSerialize(); 289 | 290 | textFile = textFile ?? Path.ChangeExtension(rptFile, "json"); 291 | var file = File.CreateText(textFile); 292 | Console.WriteLine($"Loading {rptFile}"); 293 | 294 | Console.WriteLine($"Saving {textFile}"); 295 | var serialized = serializer.Serialize(rptFile, textFile, reportOrder); 296 | file.Write(serialized); 297 | file.Flush(); 298 | file.Close(); 299 | return textFile; 300 | } 301 | 302 | private static void SerializeToStdOut(string rptFile) 303 | { 304 | var serializer = new CRSerialize(); 305 | Console.Write(serializer.Serialize(rptFile)); 306 | } 307 | 308 | private static string CRDiffProductVersion() 309 | { 310 | return FileVersionInfo 311 | .GetVersionInfo( 312 | Assembly 313 | .GetExecutingAssembly() 314 | .Location 315 | ) 316 | .ProductVersion; 317 | } 318 | 319 | internal static string GetAppSetting(string Key) 320 | { 321 | return ConfigurationManager.AppSettings.Get(Key); 322 | } 323 | internal static bool SetAppSetting(string Key, string Value) 324 | { 325 | bool result = false; 326 | try 327 | { 328 | var config = 329 | ConfigurationManager.OpenExeConfiguration( 330 | ConfigurationUserLevel.None); 331 | 332 | config.AppSettings.Settings.Remove(Key); 333 | var kvElem = new KeyValueConfigurationElement(Key, Value); 334 | config.AppSettings.Settings.Add(kvElem); 335 | 336 | // Save the configuration file. 337 | config.Save(ConfigurationSaveMode.Modified); 338 | 339 | // Force a reload of a changed section. 340 | ConfigurationManager.RefreshSection("appSettings"); 341 | 342 | result = true; 343 | } 344 | finally 345 | { } 346 | return result; 347 | } // function 348 | 349 | private static string GetTextDiffTool() 350 | { 351 | return GetAppSetting("TextDiffTool"); 352 | } 353 | private static bool SetTextDiffTool(string tool) 354 | { 355 | return SetAppSetting("TextDiffTool", tool); 356 | } 357 | } 358 | } -------------------------------------------------------------------------------- /CRDiff/CRDiff.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {5B41A5F6-2F80-4BDC-A6A6-D0433FAB2D89} 9 | Exe 10 | CRDiff 11 | CRDiff 12 | v4.6.2 13 | 512 14 | true 15 | true 16 | 17 | false 18 | 19 | 20 | \\aus2-odx-dfs01.pdsenergy.local\devTools\CRDiff\ 21 | true 22 | Unc 23 | true 24 | Background 25 | 7 26 | Days 27 | false 28 | false 29 | true 30 | CR Diff 31 | Dev Tools 32 | true 33 | publish.htm 34 | 6 35 | 1.0.0.%2a 36 | false 37 | true 38 | true 39 | true 40 | 41 | 42 | AnyCPU 43 | true 44 | full 45 | false 46 | bin\Debug\ 47 | DEBUG;TRACE 48 | prompt 49 | 4 50 | false 51 | true 52 | 53 | 54 | AnyCPU 55 | pdbonly 56 | true 57 | bin\Release\ 58 | TRACE 59 | prompt 60 | 4 61 | 62 | 63 | true 64 | bin\x64\Debug\ 65 | DEBUG;TRACE 66 | full 67 | x64 68 | prompt 69 | MinimumRecommendedRules.ruleset 70 | true 71 | 72 | 73 | bin\x64\Release\ 74 | TRACE 75 | true 76 | pdbonly 77 | x64 78 | prompt 79 | MinimumRecommendedRules.ruleset 80 | true 81 | 82 | 83 | true 84 | bin\x86\Debug\ 85 | DEBUG;TRACE 86 | full 87 | x86 88 | prompt 89 | MinimumRecommendedRules.ruleset 90 | true 91 | 92 | 93 | bin\x86\Release\ 94 | TRACE 95 | true 96 | pdbonly 97 | x86 98 | prompt 99 | MinimumRecommendedRules.ruleset 100 | true 101 | 102 | 103 | CRDiff.CRDiff 104 | 105 | 106 | 90DE5FD434CA3D24CD6F009B471D4AA4ABDC171B 107 | 108 | 109 | CRDiff_TemporaryKey.pfx 110 | 111 | 112 | true 113 | 114 | 115 | true 116 | 117 | 118 | LocalIntranet 119 | 120 | 121 | Properties\app.manifest 122 | 123 | 124 | 125 | ..\packages\CommandLineParser.2.5.0\lib\net461\CommandLine.dll 126 | 127 | 128 | ..\packages\log4net.1.2.10\lib\2.0\log4net.dll 129 | 130 | 131 | ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll 132 | False 133 | False 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | Designer 154 | 155 | 156 | 157 | 158 | 159 | {612827CE-316D-40E7-9082-CB0A7B9BE047} 160 | CRSerializer 161 | False 162 | 163 | 164 | 165 | 166 | False 167 | Microsoft .NET Framework 4.6.2 %28x86 and x64%29 168 | true 169 | 170 | 171 | False 172 | .NET Framework 3.5 SP1 173 | false 174 | 175 | 176 | 177 | 178 | False 179 | 180 | 181 | 182 | 183 | Include 184 | True 185 | Assembly 186 | 187 | 188 | False 189 | 190 | 191 | 192 | 193 | Include 194 | True 195 | Assembly 196 | 197 | 198 | False 199 | 200 | 201 | 202 | 203 | Include 204 | True 205 | Assembly 206 | 207 | 208 | False 209 | 210 | 211 | 212 | 213 | Include 214 | True 215 | Assembly 216 | 217 | 218 | False 219 | 220 | 221 | 222 | 223 | Include 224 | True 225 | Assembly 226 | 227 | 228 | False 229 | 230 | 231 | 232 | 233 | Include 234 | True 235 | Assembly 236 | 237 | 238 | False 239 | 240 | 241 | 242 | 243 | Include 244 | True 245 | Assembly 246 | 247 | 248 | False 249 | 250 | 251 | 252 | 253 | Include 254 | True 255 | Assembly 256 | 257 | 258 | False 259 | 260 | 261 | 262 | 263 | Include 264 | True 265 | Assembly 266 | 267 | 268 | False 269 | 270 | 271 | 272 | 273 | Include 274 | True 275 | Assembly 276 | 277 | 278 | False 279 | 280 | 281 | 282 | 283 | Include 284 | True 285 | Assembly 286 | 287 | 288 | False 289 | 290 | 291 | 292 | 293 | Include 294 | True 295 | Assembly 296 | 297 | 298 | False 299 | 300 | 301 | 302 | 303 | Include 304 | True 305 | Assembly 306 | 307 | 308 | False 309 | 310 | 311 | 312 | 313 | Include 314 | True 315 | Assembly 316 | 317 | 318 | False 319 | 320 | 321 | 322 | 323 | Include 324 | True 325 | Assembly 326 | 327 | 328 | False 329 | 330 | 331 | 332 | 333 | Include 334 | True 335 | Assembly 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | copy $(TargetPath) c:\Utils /y 348 | 349 | 350 | {semvertag} 351 | true 352 | true 353 | true 354 | v[0-9]* 355 | true 356 | git 357 | true 358 | 359 | 360 | 361 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 362 | 363 | 364 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /CRDiff/CRDiff_ReadMe.html: -------------------------------------------------------------------------------- 1 | 

2 |

CRDiff

3 |

CRDiff is a program that can serialize the binary Crystal reports .rpt files to human readable .json files, and works in conjunction with your favorite text diff tool. If your tool is configurable to use file transforms, it works even better.

4 |

Installation

5 |

The published location is \\aus2-odx-dfs01.pdsenergy.local\devTools\CRDiff

6 |

Copy the contents to the local folder of your choice (referred to below as <CRDiff Path>) - Having that folder in your PATH environment variable can be helpful.

7 |

You may get a message "Are you sure you want to run this software?"

8 | 12 |

If when CRDiff.exe is run with no parameters, you don't see a valid CR Version displayed, you may need to install the Crystal Reports Runtime, available in the Crystal Reports Runtime folder.

13 |

Usage:

14 | 55 |

Configuration

56 |

CompareIt!

57 |

Open CompareIt!, open Tools/Options, and select Converters. Press "Add", and specify Name: "Crystal Reports", Mask: "*.rpt", Command: "<CRDiff path>CRDiff.exe", Arguments: "{$Source_File} {$Converted_File}"

58 |

Beyond Compare

59 |

In Tools, File Formats..., press "+" and select Text Format. In the General tab, specify Mask: "*.rpt". In the Conversion tab, specify "External program (Unicode filenames)", Loading: "<CRDiff path>CRDiff.exe %s %t", check Disable editing, then Save.

60 |

TortoiseGit

61 |

In Settings, Diff Viewer, click Advanced, Add..., and specify Extension: ".rpt", External Program: "<CRDiff path>CRDiff.exe <path to your text compare tool> %base %mine"

62 |

Others

63 |

TBA

64 |

Features

65 | 78 |

Still to Implement

79 | 92 | -------------------------------------------------------------------------------- /CRDiff/Program.cs: -------------------------------------------------------------------------------- 1 | using CRSerializer; 2 | using System; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | namespace CRDiff 8 | { 9 | /* Features: 10 | * - Integratable with TortoiseGit (as well as subversion and stand-alone) 11 | * - Checks file extension, and if it is not .rpt, file passes directly to text diff tool. 12 | * - When a report, opens it/them. If error, output message 13 | * - Serialize all the report details into a text file we want to be able to compare, like 14 | * Command query 15 | * element dimensions and settings 16 | * subreports 17 | * - Save serialized files and pass to the text diff tool 18 | * - Clean-up (delete) temp files after text diff tool is closed. 19 | * 20 | * Road Map: 21 | * - Add command-line switches such as /d[ifftool], /1[stReport], /2[ndReport], /t[empOutput] 22 | * - Add configuration (either in registry or app.config) 23 | * - Hook into shell context menu to select report files to diff. 24 | * - Serialization can be into either xml or json (command line switch -x[ml] or -j[son] 25 | * 26 | * Task list: 27 | * - Add versioning ie YY.MM.dd.gitCommit (done) 28 | * 29 | TODO: Need to serialize: 30 | element suppression formulas (and other formulas for elements) 31 | sub-textbox formatting (ie font changes, coloring, tab settings, etc) 32 | "Lock Position" 33 | "change number sign" 34 | Page Setup (ie Printer, margins) 35 | "Keep Data" setting 36 | 37 | * Testing arguments: 38 | * "C:\Program Files (x86)\Compare It!\wincmp3.exe" "C:\dev\svn\Reports\Crystal Reports\trunk\KinderMorgan_CS_MT_Testing.rpt" "C:\dev\svn\Reports\Crystal Reports\trunk\KinderMorgan_CS_MT.rpt" 39 | * "C:\dev\svn\Reports\Crystal Reports\trunk\KinderMorgan_CS_MT_Testing.rpt" KM_CS_MT_Testing.json 40 | */ 41 | 42 | internal class Program 43 | { 44 | private static void Main(string[] args) 45 | { 46 | switch (args.Length) 47 | { 48 | case 1: 49 | { 50 | // Assumption is a report file is being serialized to .json 51 | if (Path.GetExtension(args[0]) == ".rpt") 52 | { 53 | SerializeToFile(args[0]); 54 | //SerializeToStdOut(args[0]); 55 | } 56 | else 57 | { 58 | Console.WriteLine(string.Join(" ", args)); 59 | Console.WriteLine("Not a report file"); 60 | Usage(); 61 | } 62 | break; 63 | } 64 | case 2: 65 | { 66 | // Assumption is that the diff tool calls with the report file to convert 67 | // and a temp filepath which it will clean up when done. 68 | if (Path.GetExtension(args[0]) == ".rpt" 69 | && File.Exists(args[0]) 70 | && Path.GetExtension(args[1]) != ".rpt" 71 | ) 72 | { 73 | //Console.WriteLine($"Creating \"{args[1]}\" from \"{args[0]}\""); 74 | SerializeToFile(args[0], args[1]); 75 | } 76 | else 77 | { 78 | Console.WriteLine($"Create \"{args[1]}\" from \"{args[0]}\"?"); 79 | Console.WriteLine(string.Join(" ", args)); 80 | Usage(); 81 | } 82 | break; 83 | } 84 | case 3: 85 | { 86 | string 87 | textDiffTool = args[0], 88 | rptFile1 = args[1], 89 | rptFile2 = args[2], 90 | textFile1, 91 | textFile2; 92 | bool 93 | file1IsRpt = false, 94 | file2IsRpt = false, 95 | textFile1Exists = false, 96 | textFile2Exists = false; 97 | 98 | if (!File.Exists(textDiffTool) 99 | || !File.Exists(rptFile1) 100 | || !File.Exists(rptFile2)) 101 | { 102 | Console.WriteLine(string.Join(" ", args)); 103 | Usage(); 104 | return; 105 | } 106 | 107 | try 108 | { 109 | if (Path.GetExtension(rptFile1) == ".rpt") 110 | { 111 | file1IsRpt = true; 112 | textFile1 = Path.ChangeExtension(rptFile1, "json"); 113 | textFile1Exists = File.Exists(textFile1); 114 | textFile1 = SerializeToFile(rptFile1, textFile1, reportOrder: 1); 115 | } 116 | else 117 | { 118 | textFile1 = rptFile1; 119 | } 120 | 121 | if (Path.GetExtension(rptFile2) == ".rpt") 122 | { 123 | file2IsRpt = true; 124 | textFile2 = Path.ChangeExtension(rptFile2, "json"); 125 | textFile2Exists = File.Exists(textFile2); 126 | textFile2 = SerializeToFile(rptFile2, textFile2, reportOrder: 2); 127 | } 128 | else 129 | { 130 | textFile2 = rptFile2; 131 | } 132 | 133 | var diffParams = $"\"{textFile1}\" \"{textFile2}\""; 134 | Console.Write($"Starting \"{ textDiffTool}\" {diffParams}"); 135 | 136 | var diffProc = Process.Start( 137 | textDiffTool, 138 | diffParams 139 | ); 140 | 141 | diffProc.WaitForExit(); 142 | 143 | // Clean up (delete) temp files after text diff tool is finished with them (if they didn't exist already) 144 | if (file1IsRpt && !textFile1Exists) 145 | File.Delete(textFile1); 146 | 147 | if (file2IsRpt && !textFile2Exists) 148 | File.Delete(textFile2); 149 | } 150 | catch (Exception ex) 151 | { 152 | Console.WriteLine($"Error: {ex.Message} {ex.InnerException?.Message}"); 153 | ReadKey(); 154 | } 155 | break; 156 | } 157 | default: 158 | { 159 | Usage(); 160 | ReadKey(); 161 | break; 162 | } 163 | } 164 | } 165 | 166 | private static void Usage(string[] args = null) 167 | { 168 | Console.WriteLine("Usage:"); 169 | Console.WriteLine("CRDiff ReportFilename"); 170 | Console.WriteLine(" - Creates a serialized .json file of the report"); 171 | Console.WriteLine("CRDiff ReportFilename TempFilename"); 172 | Console.WriteLine(" - Writes json to TempFileName. Allows use as a .rpt converter in many text diff tools"); 173 | Console.WriteLine("CRDiff DiffAppPath ReportFilename1 ReportFilename2"); 174 | Console.WriteLine(" - Serializes 2 reports and passes serialized .json files to DiffApp for comparison"); 175 | Console.WriteLine(); 176 | Console.WriteLine($"CRDiff Version: {CRDiffProductVersion()}"); 177 | Console.WriteLine($"Path: {Environment.GetCommandLineArgs()[0]}"); 178 | Console.WriteLine($"Directory: {AppDomain.CurrentDomain.BaseDirectory}"); 179 | Console.WriteLine(); 180 | 181 | try 182 | { 183 | var version = (new CRSerialize()).CRVersion(); 184 | Console.WriteLine($"CR Version: {version}"); 185 | } 186 | catch (Exception e) 187 | { 188 | Console.WriteLine($"CR library is not available ({e.Message})"); 189 | } 190 | } 191 | 192 | private static void Instructions() 193 | { 194 | Console.WriteLine(@" 195 | 196 | "); 197 | } 198 | 199 | private static void ReadKey() 200 | { 201 | Console.WriteLine("Press any key to exit..."); 202 | Console.ReadKey(); 203 | } 204 | 205 | private static string SerializeToFile(string rptFile, string textFile = null, int reportOrder = 1) 206 | { 207 | var serializer = new CRSerialize(); 208 | 209 | textFile = textFile ?? Path.ChangeExtension(rptFile, "json"); 210 | var file = File.CreateText(textFile); 211 | Console.WriteLine($"Loading {rptFile}"); 212 | 213 | Console.WriteLine($"Saving {textFile}"); 214 | var serialized = serializer.Serialize(rptFile, textFile, reportOrder); 215 | file.Write(serialized); 216 | file.Flush(); 217 | file.Close(); 218 | return textFile; 219 | } 220 | 221 | private static void SerializeToStdOut(string rptFile) 222 | { 223 | var serializer = new CRSerialize(); 224 | Console.Write(serializer.Serialize(rptFile)); 225 | } 226 | 227 | private static string CRDiffProductVersion() 228 | { 229 | return FileVersionInfo 230 | .GetVersionInfo( 231 | Assembly 232 | .GetExecutingAssembly() 233 | .Location 234 | ) 235 | .ProductVersion; 236 | } 237 | } 238 | } -------------------------------------------------------------------------------- /CRDiff/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("CRDiff")] 8 | [assembly: AssemblyDescription("Crystal Report .rpt serialization tool")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("CRDiff")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("5b41a5f6-2f80-4bdc-a6a6-d0433fab2d89")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | // (Autoversioning provided by https://github.com/ygoe/NetRevisionTask) 35 | [assembly: AssemblyVersion("0.0.0.0")] 36 | [assembly: AssemblyFileVersion("0.0.0.0")] 37 | [assembly: AssemblyInformationalVersion("0.1.0.{revnum}_{b:ymd-}_{chash:7}{!:+}")] -------------------------------------------------------------------------------- /CRDiff/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CRSerializer/CRSerialize.cs: -------------------------------------------------------------------------------- 1 | using CrystalDecisions.CrystalReports.Engine; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | 10 | namespace CRSerializer 11 | { 12 | public class CRSerialize 13 | { 14 | public string Serialize(string rptPath, string filepath = null, int reportOrder = 1)//, OutputFormat fmt = OutputFormat.json) 15 | { 16 | var rpt = new ReportDocument(); 17 | rpt.Load(rptPath); 18 | var rptClient = rpt.ReportClientDocument; 19 | 20 | var jsonSerializerSettings = new JsonSerializerSettings 21 | { 22 | ContractResolver = new CustomJsonResolver(), 23 | Formatting = Formatting.Indented 24 | }; 25 | 26 | string rptName = string.IsNullOrEmpty(rpt.Name) 27 | ? Path.GetFileNameWithoutExtension(rptPath) 28 | : rpt.Name; 29 | 30 | var sb = new StringBuilder(); 31 | var sw = new StringWriter(sb); 32 | 33 | using (JsonWriter jw = new JsonTextWriter(sw)) 34 | { 35 | jw.Formatting = Formatting.Indented; 36 | 37 | jw.WriteStartObject(); 38 | 39 | jw.WriteObjectHierarchy("Entire Report Object", rpt); 40 | 41 | jw.WriteProperty("SerializeVersion", CRSerializeProductVersion()); 42 | 43 | jw.WriteProperty("ReportName", rptName); 44 | 45 | jw.WriteParameters("Parameters", rpt); 46 | 47 | jw.WriteObjectHierarchy("SummaryInfo", rpt.SummaryInfo); 48 | 49 | // experimental properties: 50 | jw.WriteObjectHierarchy("DataSourceConnections", rpt.DataSourceConnections); 51 | 52 | jw.WriteObjectHierarchy("ParameterFields", rpt.ParameterFields); 53 | 54 | jw.WriteObjectHierarchy("ReportOptions", rptClient.ReportOptions); 55 | 56 | jw.WriteDataSource("DataSource", rptClient.Database.Tables); 57 | 58 | jw.WriteObjectHierarchy("DatabaseTables", rpt.Database.Tables); 59 | 60 | jw.WriteObjectHierarchy("DataDefinition", rpt.DataDefinition); 61 | 62 | jw.WritePrintOptions(rpt.PrintOptions); 63 | 64 | jw.WriteObjectHierarchy("CustomFunctions", rptClient.CustomFunctionController.GetCustomFunctions()); 65 | 66 | jw.WriteObjectHierarchy("ReportDefinition", rpt.ReportDefinition); 67 | 68 | jw.WritePropertyName("Subreports"); 69 | var subReports = rpt.Subreports; 70 | if (subReports.Count > 0) 71 | { 72 | jw.WriteStartArray(); 73 | foreach (ReportDocument subReport in subReports) 74 | { 75 | var subReportClient = rptClient.SubreportController.GetSubreport(subReport.Name); 76 | 77 | jw.WriteStartObject(); 78 | jw.WriteProperty("SubreportName", subReport.Name); 79 | 80 | jw.WriteParameters("Parameters", subReport); 81 | 82 | jw.WriteDataSource("DataSource", subReportClient.DataDefController.Database.Tables); 83 | 84 | jw.WriteObjectHierarchy("DatabaseTables", subReport.Database.Tables); 85 | 86 | jw.WriteObjectHierarchy("DataDefinition", subReport.DataDefinition); 87 | 88 | jw.WriteObjectHierarchy("CustomFunctions", rptClient.CustomFunctionController.GetCustomFunctions()); 89 | 90 | jw.WriteObjectHierarchy("ReportDefinition", subReport.ReportDefinition); 91 | 92 | jw.WriteEndObject(); 93 | } 94 | } 95 | 96 | jw.WriteEndObject(); // final end 97 | rpt.Close(); 98 | 99 | return sb.ToString(); 100 | } 101 | } 102 | public static string CRSerializeProductVersion() 103 | { 104 | return FileVersionInfo 105 | .GetVersionInfo( 106 | Assembly 107 | .GetExecutingAssembly() 108 | .Location 109 | ) 110 | .ProductVersion; 111 | } 112 | 113 | public string CRVersion() 114 | { 115 | // ref: https://apps.support.sap.com/sap/support/knowledge/public/en/2003551 116 | var versions = new StringBuilder(); 117 | var rpt = new ReportDocument(); // pull in Crystal library 118 | 119 | foreach (var crAsm in AppDomain.CurrentDomain.GetAssemblies() 120 | .Where(a => a.FullName.StartsWith("CrystalDecisions.CrystalReports.Engine"))) 121 | { 122 | var fileVersionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(crAsm.Location); 123 | versions.Append(fileVersionInfo.FileVersion); 124 | //versions.Append("\n"); 125 | //versions.Append(fileVersionInfo.FileName); 126 | } 127 | if (versions.Length == 0) 128 | { 129 | versions.Append("Not Found"); 130 | } 131 | return versions.ToString(); 132 | } 133 | 134 | 135 | } 136 | } -------------------------------------------------------------------------------- /CRSerializer/CRSerializer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {612827CE-316D-40E7-9082-CB0A7B9BE047} 9 | Library 10 | Properties 11 | CRSerializer 12 | CRSerializer 13 | v4.6.1 14 | 512 15 | true 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | True 40 | 41 | 42 | True 43 | 44 | 45 | True 46 | 47 | 48 | True 49 | 50 | 51 | 52 | ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 76 | 77 | 78 | 79 | 80 | 81 | 82 | copy $(TargetPath) c:\Utils /y 83 | 84 | -------------------------------------------------------------------------------- /CRSerializer/CustomJsonResolver.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace CRSerializer 7 | { 8 | // Ref: https://stackoverflow.com/questions/20962316/ignoring-class-members-that-throw-exceptions-when-serializing-to-json 9 | internal class CustomJsonResolver : DefaultContractResolver 10 | { 11 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 12 | { 13 | JsonProperty property = base.CreateProperty(member, memberSerialization); 14 | 15 | property.ShouldSerialize = instance => 16 | { 17 | try 18 | { 19 | PropertyInfo prop = (PropertyInfo)member; 20 | if (prop.CanRead) 21 | { 22 | prop.GetValue(instance, null); 23 | return true; 24 | } 25 | } 26 | catch (Exception) 27 | { 28 | } 29 | return false; 30 | }; 31 | return property; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /CRSerializer/Extensions.cs: -------------------------------------------------------------------------------- 1 | using CrystalDecisions.CrystalReports.Engine; 2 | //using CrystalDecisions; 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | 6 | namespace CRSerializer 7 | { 8 | internal static class Extensions 9 | { 10 | public static void WriteProperty(this JsonWriter jw, string name, object value) 11 | { 12 | jw.WritePropertyName(name); 13 | jw.WriteValue(value); 14 | } 15 | 16 | public static void WriteArray(this JsonWriter jw, string name, object[] value) 17 | { 18 | 19 | } 20 | 21 | public static void WriteObjectHierarchy(this JsonWriter jw, object obj) 22 | { 23 | var jsonSerializerSettings = new JsonSerializerSettings 24 | { 25 | ContractResolver = new CustomJsonResolver(), 26 | Formatting = Formatting.Indented 27 | }; 28 | jw.WriteRawValue(JsonConvert.SerializeObject(obj, jsonSerializerSettings)); 29 | } 30 | 31 | public static void WriteObjectHierarchy(this JsonWriter jw, string name, object obj) 32 | { 33 | jw.WritePropertyName(name); 34 | jw.WriteObjectHierarchy(obj); 35 | } 36 | 37 | public static void WriteDataSource(this JsonWriter jw, string keyName, CrystalDecisions.ReportAppServer.DataDefModel.Tables tables) 38 | { 39 | //Doesn't deal with a mix of command statements and tables (but that scenario is rare) 40 | var isFirstTable = true; 41 | 42 | jw.WriteStartObject(); 43 | foreach (dynamic table in tables) 44 | { 45 | if (table.ClassName == "CrystalReports.CommandTable") 46 | { 47 | jw.WritePropertyName("Command"); 48 | MultiLinesToArray(jw, table.CommandText); 49 | } 50 | else 51 | { 52 | if (isFirstTable) 53 | { 54 | jw.WritePropertyName("Tables"); 55 | jw.WriteStartArray(); 56 | isFirstTable = false; 57 | } 58 | jw.WriteValue(table.Name); 59 | } 60 | } 61 | if (!isFirstTable) 62 | { 63 | jw.WriteEndArray(); 64 | } 65 | jw.WriteEndObject(); 66 | } 67 | 68 | public static void MultiLinesToArray(this JsonWriter jw, string str) 69 | { 70 | string[] vs = str.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.None); 71 | jw.WriteStartArray(); 72 | foreach (var s in vs) 73 | { 74 | jw.WriteValue(s); 75 | } 76 | jw.WriteEnd(); 77 | } 78 | 79 | public static void WriteParameters(this JsonWriter jw, string keyName, ReportDocument rpt) 80 | { 81 | //var parameterList = new List(); 82 | var parameterFields = rpt.DataDefinition.ParameterFields; 83 | jw.WritePropertyName(keyName); 84 | jw.WriteStartArray(); 85 | foreach (ParameterFieldDefinition parameterField in parameterFields) 86 | { 87 | jw.WriteStartObject(); 88 | jw.WriteProperty("Name", parameterField.Name); 89 | jw.WriteProperty("FormulaName", parameterField.FormulaName); 90 | jw.WriteProperty("ValueType", parameterField.ValueType.ToString()); 91 | jw.WriteProperty("EnableNullValue", parameterField.EnableNullValue.ToString()); 92 | jw.WriteEndObject(); 93 | } 94 | jw.WriteEnd(); 95 | } 96 | private static List Parameters(ReportDocument rpt) 97 | { 98 | var paramtrs = new List(); 99 | var parameters = rpt.DataDefinition.ParameterFields; 100 | foreach (ParameterFieldDefinition param in parameters) 101 | { 102 | paramtrs.Add($"Parameter {param.ReportName}.{param.Name}"); 103 | } 104 | return paramtrs; 105 | } 106 | 107 | public static void WritePrintOptions(this JsonWriter jw, PrintOptions printOptions) 108 | { 109 | jw.WritePropertyName("PrintOptions"); 110 | jw.WriteStartObject(); 111 | jw.WriteProperty("NoPrinter", printOptions.NoPrinter); 112 | jw.WriteProperty("PrinterName", printOptions.PrinterName); 113 | jw.WriteProperty("SavedPrinterName", printOptions.SavedPrinterName); 114 | jw.WriteProperty("PrinterDuplex", printOptions.PrinterDuplex.ToString()); 115 | jw.WriteProperty("PaperOrientation", printOptions.PaperOrientation.ToString()); 116 | jw.WriteProperty("PaperSize", printOptions.PaperSize.ToString()); 117 | jw.WriteProperty("PaperSource", printOptions.PaperSource.ToString()); 118 | jw.WriteProperty("DissociatePageSizeAndPrinterPaperSize", printOptions.DissociatePageSizeAndPrinterPaperSize); 119 | jw.WriteProperty("PageContentHeight", $"{printOptions.PageContentHeight} twips ({printOptions.PageContentHeight / 1440.0:N3} inches)"); 120 | jw.WriteProperty("PageContentWidth", $"{printOptions.PageContentWidth} twips ({printOptions.PageContentWidth / 1440.0:N3} inches)"); 121 | 122 | jw.WritePropertyName("PageMargins"); 123 | jw.WriteStartObject(); 124 | jw.WriteProperty("topMargin", $"{printOptions.PageMargins.topMargin} twips ({printOptions.PageMargins.topMargin / 1440.0:N3} inches)"); 125 | jw.WriteProperty("leftMargin", $"{printOptions.PageMargins.leftMargin} twips ({printOptions.PageMargins.leftMargin / 1440.0:N3} inches)"); 126 | jw.WriteProperty("rightMargin", $"{printOptions.PageMargins.rightMargin} twips ({printOptions.PageMargins.rightMargin / 1440.0:N3} inches)"); 127 | jw.WriteProperty("bottomMargin", $"{printOptions.PageMargins.bottomMargin} twips ({printOptions.PageMargins.bottomMargin / 1440.0:N3} inches)"); 128 | 129 | jw.WriteEndObject(); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /CRSerializer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("CRSerializer")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("CRSerializer")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("612827ce-316d-40e7-9082-cb0a7b9be047")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.0.0.0")] 35 | [assembly: AssemblyFileVersion("0.0.0.0")] 36 | [assembly: AssemblyInformationalVersion("0.1.0.{revnum}_{c:ymd-}_{chash:7}{!:+}")] -------------------------------------------------------------------------------- /CRSerializer/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

CRDiff

3 |

CRDiff is a program that serializes the binary Crystal reports .rpt files to human readable .json files, and works in conjunction with your favorite text diff tool. If your tool is configurable to use file transforms/converters, it works even better.

4 |

Installation

5 |

The published location is \\aus2-odx-dfs01.pdsenergy.local\devTools\CRDiff

6 |

Copy the contents to the local folder of your choice (referred to below as <CRDiff Path>) - Having that folder in your PATH environment variable can be helpful.

7 |

You may get a message "Are you sure you want to run this software?"

8 |
    9 |
  • Uncheck "Always ask before opening this file" and
  • 10 |
  • press "Run"
  • 11 |
12 |

If when CRDiff.exe is run with no parameters, you don't see a valid CR Version displayed, you may need to install the Crystal Reports Runtime, available in the Crystal Reports Runtime folder.

13 |

Usage:

14 |
    15 |
  • CRDiff.exe DiffAppPath.exe 16 |
      17 |
    • Save your diff tool's path in CRDiff's config file.
    • 18 |
    19 |
  • 20 |
  • CRDiff.exe ReportFilename.rpt 21 |
      22 |
    • Serialize the report to a json file of the same name and location but with .json extension.
    • 23 |
    24 |
  • 25 |
  • CRDiff.exe ReportFilename.rpt, ReportFilename2.rpt 26 |
      27 |
    • Serialize the two reports to json and pass the json files to your diff tool. Once you close your text diff tool, CRDiff will delete the json files (if they hadn't already existed). If you haven't set a text diff path above, you will be prompted for one.
    • 28 |
    29 |
  • 30 |
  • CRDiff.exe DiffAppPath.exe ReportFilename1.rpt ReportFilename2.rpt 31 |
      32 |
    • Same as above, but DiffAppPath.exe is used instead of what is saved in config.
    • 33 |
    34 |
  • 35 |
  • CRDiff.exe ReportFilename.rpt, TempFilename(.json) 36 |
      37 |
    • Serialize ReportFilename.rpt to TempFilename (provided by your configurable text diff tool, which is responsible for cleaning up after itself).
    • 38 |
    39 |
  • 40 |
  • Parameters: 41 |
      42 |
    • DiffAppPath - path to external differencing application .exe file that can compare two text files
    • 43 |
    • ReportFilename1 - path to first .rpt file to be serialized to json, or it can be an existing report.json file
    • 44 |
    • ReportFilename2 - path to second .rpt file to be serialized to json (or existing .json file) and compared with first file
    • 45 |
    • TempFilename - path to a temporary file that will likely be deleted after use.
    • 46 |
    47 |
  • 48 |
49 |

Configuration

50 |

CompareIt!

51 |

Open CompareIt!, open Tools/Options, and select Converters. Press "Add", and specify Name: "Crystal Reports", Mask: "*.rpt", Command: "<CRDiff path>CRDiff.exe", Arguments: "{$Source_File} {$Converted_File}"

52 |

Beyond Compare

53 |

In Tools, File Formats..., press "+" and select Text Format. In the General tab, specify Mask: "*.rpt". In the Conversion tab, specify "External program (Unicode filenames)", Loading: "<CRDiff path>CRDiff.exe %s %t", check Disable editing, then Save.

54 |

TortoiseGit

55 |

In Settings, Diff Viewer, click Advanced, Add..., and specify Extension: ".rpt", External Program: "<CRDiff path>CRDiff.exe <path to your text compare tool> %base %mine"

56 |

Others

57 |

TBA

58 |

Features

59 |
    60 |
  • Checks file extension, and if it is not .rpt, file passes directly to text diff tool.
  • 61 |
  • Serializes many of the report details we want to be able to compare into a text file, like 62 |
      63 |
    • Command query
    • 64 |
    • element dimensions and settings
    • 65 |
    • subreports
    • 66 |
    67 |
  • 68 |
  • Save serialized files and pass to a text diff tool
  • 69 |
  • Clean-up (delete) temp files after text diff tool is closed.
  • 70 |
71 |

Still to Implement

72 |
    73 |
  • Need to output 74 |
      75 |
    • element suppression formulas (and other formulas for elements)
    • 76 |
    • sub-textbox formatting (ie font changes, coloring, tab settings, etc)
    • 77 |
    • "Lock Position"
    • 78 |
    • "change number sign"
    • 79 |
    • Page Setup (ie Printer, margins)
    • 80 |
    • "Keep Data" setting
    • 81 |
    82 |
  • 83 |
84 | --------------------------------------------------------------------------------