/* ======================================================================== * Copyright 2018 - Volian Enterprises, Inc. All rights reserved. * Volian Enterprises - Proprietary Information - DO NOT COPY OR DISTRIBUTE * ------------------------------------------------------------------------ * This program is to automate the Comparison of PDF output using the debug * files created during automated testing. The SQL script is used to create * the debug files; DebugPagination.txt and DebugMeta.txt. This comparison * code then compares these files run with two versions of the PROMS code * and finds pages that have been impacted by the changes to the PROMS code. * * By Identifying Procedures and Pages that have been changed the amount of * time required to perform comparisons is greatly reduced. * * The Pagination Comparison simply looks for things that have impacted page * breaks. This is a quick summary of the page breaks for all of the * procedures in a folder. Thus, the folders are listed which contain * differences. When a folder is selected, the pagination information is * compared for all of the procedures in each folder. If no changes exits, * then the folder is not contained in the list. after selecting a folder, * a comparison is shown of the pagination lines that are different. * Pressing the UC button allows you to see ultracompare with the two * pagination files. If you select a line that is different, two pdf * windows are opened to the relative pages. * * The Meta Comparison looks at more detail including text and formatting * differences. This information is organized by procedure and page. It * uses the procedure/page/line classes. The structure of this file is * described in * V:\Proms Versions\Automated Testing\Baseline Metafile Key.docx * By Clicking on a folder a list of impacted procedures is provided. * By clicking on a Procedure a list of pages and lines are provided. * By clicking on a line the PDF is displayed. * * The Search feature performs a text search on the Meta File. Looking * for RCP will tell you where RCP can be found in the PDFs. This may be * of use if you want to see where certain text is contained within all of * the PDFs. Looking for [[ will find all of the special characters which * have not yet been handled in the PROMS meta code. See FixText in * DisplayText.cs in the PROMS code. * ======================================================================*/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Collections.Specialized; using System.IO; using System.Text.RegularExpressions; using System.Xml.Serialization; using System.Xml.Schema; using System.Xml; using System.Runtime.InteropServices; namespace Baseline { enum LastWas { Pagination, Baseline, Search }; public enum Relation { Contains, StartsWith, EndsWith, Regex } public partial class frmBaseline : Form { private IgnoreLines _MyIgnore = new IgnoreLines(); public IgnoreLines MyIgnore { get { return _MyIgnore; } set { _MyIgnore = value; } } private LastWas myLast = LastWas.Search; private Settings MySettings; public string MyStatus { get { return tsslStatus.Text; } set { tsslStatus.Text = value; Application.DoEvents(); } } public frmBaseline() { InitializeComponent(); LoadSettings(); SetupEventHandlers(); MyStatus = "Ready"; } #region Settings /// /// Setup event handlers for changes to Settings /// private void SetupEventHandlers() { this.Resize += frmBaseline_Resize; this.Move += frmBaseline_Move; this.splitContainer1.SplitterMoved += splitContainer1_SplitterMoved; this.splitContainer2.SplitterMoved += splitContainer2_SplitterMoved; this.splitContainer3.SplitterMoved += splitContainer3_SplitterMoved; cbFile1.TextChanged += cbFile1_TextChanged; cbFile2.TextChanged += cbFile2_TextChanged; } // Remember the location of splitters void splitContainer3_SplitterMoved(object sender, SplitterEventArgs e) { Properties.Settings.Default.Split3 = splitContainer3.SplitterDistance; Properties.Settings.Default.Save(); } void splitContainer2_SplitterMoved(object sender, SplitterEventArgs e) { Properties.Settings.Default.Split2 = splitContainer2.SplitterDistance; Properties.Settings.Default.Save(); } void splitContainer1_SplitterMoved(object sender, SplitterEventArgs e) { Properties.Settings.Default.Split1 = splitContainer1.SplitterDistance; Properties.Settings.Default.Save(); } // Remember Form Location void frmBaseline_Move(object sender, EventArgs e) { Properties.Settings.Default.Location = this.Location; Properties.Settings.Default.Save(); } // Remember Form Size void frmBaseline_Resize(object sender, EventArgs e) { if (this.WindowState == FormWindowState.Normal) { Properties.Settings.Default.Size = this.Size; } Properties.Settings.Default.WidnowState = this.WindowState; Properties.Settings.Default.Save(); } // Load Settings private void LoadSettings() { this.Location = Properties.Settings.Default.Location; this.Size = Properties.Settings.Default.Size; this.WindowState = Properties.Settings.Default.WidnowState; if(Properties.Settings.Default.Ignore != null && Properties.Settings.Default.Ignore != "") MyIgnore = IgnoreLines.Get(Properties.Settings.Default.Ignore); MySettings= new Settings(); MySettings.IgnoreLines = new BindingList(); splitContainer1.SplitterDistance = Properties.Settings.Default.Split1; splitContainer2.SplitterDistance = Properties.Settings.Default.Split2; splitContainer3.SplitterDistance = Properties.Settings.Default.Split3; if (Properties.Settings.Default.MRU1 != null && Properties.Settings.Default.MRU1.Count > 0) { cbFile1.Items.Clear(); foreach (string str in Properties.Settings.Default.MRU1) cbFile1.Items.Add(str); cbFile1.SelectedIndex = 0; } if (Properties.Settings.Default.MRU2 != null && Properties.Settings.Default.MRU2.Count > 0) { cbFile2.Items.Clear(); foreach (string str in Properties.Settings.Default.MRU2) cbFile2.Items.Add(str); cbFile2.SelectedIndex = 0; } } #endregion /// /// Use FolderBrowserDialog to find folder /// /// /// private void btnBrowse1_Click(object sender, EventArgs e) { fbd.SelectedPath = cbFile1.Text; if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK) cbFile1.Text = fbd.SelectedPath; } private void btnBrowse2_Click(object sender, EventArgs e) { fbd.SelectedPath = cbFile2.Text; if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK) cbFile2.Text = fbd.SelectedPath; } /// /// Open frmSettings to edit ignore list /// /// /// private void btnSettings_Click(object sender, EventArgs e) { string saveOriginal = MyIgnore.ToString(); frmSettings mySettings = new frmSettings(MyIgnore); if (mySettings.ShowDialog() == System.Windows.Forms.DialogResult.OK) { Properties.Settings.Default.Ignore = MyIgnore.ToString(); Properties.Settings.Default.Save(); } else { MyIgnore = IgnoreLines.Get(saveOriginal); } } /// /// Process DebugPagination.txt in all sub-folders /// /// /// private void btnPagination_Click(object sender, EventArgs e) { myLast = LastWas.Pagination; // Initialize lbDifferent DocVersion comparison list lbDifferent.DataSource = null; lbDifferent.Items.Clear(); // Initialize line differences for DebugPagination lbResults1.Items.Clear(); lbResults2.Items.Clear(); MyStatus = "Searching..."; // Perform DebugPagination Comparison FindFiles fnd = new FindFiles(cbFile1.Text, cbFile2.Text, "DebugPagination.txt",MyIgnore); lbDifferent.DataSource = fnd; lbDifferent.DisplayMember = "File1"; MyStatus = string.Format("{0} Differences Found", fnd.Count); } /// /// Fill Appropriate List Boxes when an entry is selected in lbDifferent /// /// /// private void lbDifferent_SelectedIndexChanged(object sender, EventArgs e) { //Initialize ListBoxes lbProcedures.Items.Clear(); lbResults1.Items.Clear(); lbResults2.Items.Clear(); FindFile ff = lbDifferent.SelectedItem as FindFile; if (ff != null) { // Fill Procedure or Result ListBoxes switch (myLast) { case LastWas.Pagination: CompareContent(ff.File1, ff.File2);// Compare DebugPagination break; case LastWas.Baseline: CompareContent3(ff.File1, ff.File2);// Compare DebugMeta break; case LastWas.Search: ShowSearchResults(ff.File1, ff.File2);// Perform search on DebugMeta break; default: CompareContent(ff.File1, ff.File2);//Default DebugPagination break; } //CompareOneFile(ff.File1, ff.File2); } } Procedures MyProcs1; Procedures MyProcs2; /// /// Use LINQ to find a search string in the DebugMeta Files /// /// /// private void ShowSearchResults(string file1, string file2) { IEnumerable lines1 = File.ReadLines(file1);// LINQ Read Lines IEnumerable lines2 = File.ReadLines(file2);// LINQ Read Lines lines1 = AddItemIDs(lines1);// LINQ Add ItemIDs to each line to make them unique lines2 = AddItemIDs(lines2);// LINQ Add ItemIDs to each line to make them unique // LINQ Lambda Expression - Only includes lines that contain the search text // Each line x such that local function Contains(tbsearch.Text, x) is true lines1 = lines1.Where(x => Contains(tbSearch.Text, x));// LINQ Search lines2 = lines2.Where(x => Contains(tbSearch.Text, x));// LINQ Search // Initialize Results lbResults1.Items.Clear(); lbResults2.Items.Clear(); // Initialize Procedure List lbProcedures.Items.Clear(); // TODO: Are the following lines necessary string line1 = lines1.ElementAt(0); string line2 = lines2.ElementAt(0); // Load a List of Procedures MyProcs1 = FillProcedures(lines1); MyProcs2 = FillProcedures(lines2); // Populate the Procedure ListBox foreach (Procedure proc in MyProcs1) { // TODO: Is the following local variable necessary int i = lbProcedures.Items.Add(proc); } MyStatus = string.Format("{0} Procedures contain {1}",MyProcs1.Count(),tbSearch.Text); } /// /// Fill the Procedure ListBox /// /// /// private Procedures FillProcedures(IEnumerable lines) { Procedures myProcs = new Procedures(); string lastProc = null; int pageNumber = 0; foreach (string line in lines) { if (line.StartsWith("!!")) // !! Lines contain Procedure Info { lastProc = line; //Remember last Procedure line pageNumber = 1; } else if (line.StartsWith("Page Change from ")) // These lines contain Page Number { pageNumber = int.Parse(line.Substring(line.LastIndexOf(' '))); // Remember last page number } else { // use remembered procedure and page number to save line of text myProcs.Add(lastProc, pageNumber, line);// Otherwise add Procedure, Page or Line } } return myProcs; } /// /// LINQ add ItemID Prefix to each line /// /// /// private static IEnumerable AddItemIDs(IEnumerable lines1) { List list1 = new List(); string prefix = ""; foreach (string line in lines1) { if (line.StartsWith("TX ")) //Get the ItemID { if (line.Contains("ItmID=")) { prefix = line.Substring(line.IndexOf("ItmID=") + 6); } else prefix = ""; list1.Add(line); } else if (line.Contains(" Atrbs: "))// Add the prefix to make the line unique list1.Add(prefix + line); else { prefix = ""; list1.Add(line); } } lines1 = list1.AsEnumerable();// Convert back to Enumerable to work with LINQ return lines1; } private string GetProcNum(string line) { string retval = line.Substring(3, line.IndexOf(" | ") - 3); if (retval.Contains("_")) retval = retval.Substring(0, retval.IndexOf("_") - 1); return retval; } /// /// Include lines for Procedure or Page or Search is true /// Account for Case Insensitive CheckBox /// /// /// /// private bool Contains(string searchText, string x) { bool result; if (cbCaseSensitive.Checked) { result = x.Contains(searchText) || x.StartsWith("Page Change from ") || Regex.IsMatch(x, @"^!![^|]+\|[^|]+?$", RegexOptions.Compiled); } else { result = x.ToUpper().Contains(searchText.ToUpper()) || x.StartsWith("Page Change from ") || Regex.IsMatch(x, @"^!![^|]+\|[^|]+?$", RegexOptions.Compiled); } return result; } private void CompareContent(string file1, string file2) { // Enable the UltraCompare button // Collapse the Procedure Panel btnUC.Enabled = splitContainer3.Panel2Collapsed = true; IEnumerable lines1 = FindFiles.ReadFilteredLines(file1, true, MyIgnore); IEnumerable lines2 = FindFiles.ReadFilteredLines(file2, true, MyIgnore); IEnumerable missing1 = lines1.Except(lines2); IEnumerable missing2 = lines2.Except(lines1); lbResults1.Items.Clear(); lbResults1.Items.AddRange(missing1.ToArray()); lbResults2.Items.Clear(); lbResults2.Items.AddRange(missing2.ToArray()); } /// /// LINQ Perform DebugMeta Comparison /// /// /// private void CompareContent3(string file1, string file2) { // Disble the UltraCompare button // Expand the Procedure Panel btnUC.Enabled = splitContainer3.Panel2Collapsed = false; IEnumerable lines1 = FindFiles.ReadFilteredLines(file1, true,MyIgnore);// LINQ Read lines excluding lines to be ignored IEnumerable lines2 = FindFiles.ReadFilteredLines(file2, true, MyIgnore);// LINQ Read lines excluding lines to be ignored lines1 = AddItemIDs(lines1);// LINQ Add ItemID prefix to make lines unique lines2 = AddItemIDs(lines2);// LINQ Add ItemID prefix to make lines unique // The following lines create a list of lines without Page Numbers or procedures // They are removed from this list so that they will not be impacted by the intersect below IEnumerable noPageNumbers1 = lines1.Where(x => x.StartsWith("Page Change from ") == false);// LINQ get lines without Page Numbers IEnumerable noPageNumbers2 = lines2.Where(x => x.StartsWith("Page Change from ") == false);// LINQ get lines without Page Numbers noPageNumbers1 = noPageNumbers1.Where(x => x.StartsWith("!! ") == false);//LINQ remove lines with !! noPageNumbers2 = noPageNumbers2.Where(x => x.StartsWith("!! ") == false);//LINQ remove lines with !! IEnumerable intersect = noPageNumbers1.Intersect(noPageNumbers2);//LINQ find Matchhing lines List lIntersect = intersect.ToList();//LINQ Create a list of matching lines HashSet hs = new HashSet(intersect);// LINQ Create a HashSet of matching lines IEnumerable missing1 = lines1.Where(x => !hs.Contains(x));// LINQ Remove matching lines IEnumerable missing2 = lines2.Where(x => !hs.Contains(x));// LINQ Remove matching lines MyProcs1 = FillProcedures(missing1);// Fill Procedures from the lines that don't match (including Procedure and Page Number lines) MyProcs2 = FillProcedures(missing2);// Fill Procedures from the lines that don't match (including Procedure and Page Number lines) if (MyProcs1 != null && MyProcs1.Count > 0)// Fill Procedure ListBox with MyProcs1 { foreach (Procedure proc in MyProcs1) lbProcedures.Items.Add(proc); MyStatus = string.Format("{0} Procedures found with differences", MyProcs1.Count()); } else if (MyProcs2 != null && MyProcs2.Count > 0)// Fill Procedure ListBox with MyProcs1 { foreach (Procedure proc in MyProcs2) lbProcedures.Items.Add(proc); MyStatus = string.Format("{0} Procedures found with differences", MyProcs2.Count()); } } /// /// Open One or more PDF's /// /// /// private void lbResults1_SelectedIndexChanged(object sender, EventArgs e) { string line=null; if (lbResults1.SelectedItem is string) line = (string)lbResults1.SelectedItem; Line myLine = lbResults1.SelectedItem as Line; switch (myLast) { case LastWas.Pagination: line = OpenPDF(line); break; case LastWas.Baseline: // TODO: Need to add code here to open matching file OpenOnePDF(myLine,1); break; case LastWas.Search: // TODO: Need to add code here to open matching file OpenOnePDF(myLine,1); break; default: break; } } private void lbResults2_SelectedIndexChanged(object sender, EventArgs e) { string line=null; if(lbResults2.SelectedItem is string) line = (string)lbResults2.SelectedItem; Line myLine = lbResults2.SelectedItem as Line; switch (myLast) { case LastWas.Pagination: line = OpenPDF(line); break; case LastWas.Baseline: // TODO: Need to add code here to open matching file OpenOnePDF(myLine,2); break; case LastWas.Search: // TODO: Need to add code here to open matching file OpenOnePDF(myLine,2); break; default: break; } } string exePath; private string OpenPDF(string line) { int page = int.Parse(line.Substring(0, 6)); // B2018-113 - Replace slashes and backslashes with underscores just as PROMS does when creating a PDF file. line = line.Substring(8, line.IndexOf(".S") - 8).Replace("/", "_").Replace("\\", "_"); FindFile ff = lbDifferent.SelectedItem as FindFile; FileInfo fi1 = new FileInfo(ff.File1); FileInfo fi2 = new FileInfo(ff.File2); // If you don't know where the Reader executable is for PDFs Open a PDF and Check to see where the path points if (exePath == null) { System.Diagnostics.Process p = System.Diagnostics.Process.Start(fi1.DirectoryName + "\\" + line + ".pdf"); exePath = TryToGetPath(p); p.Kill(); // No need to keep it open } // Open the first PDF on a Specific Page System.Diagnostics.ProcessStartInfo psi1 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", page) + fi1.DirectoryName + "\\" + line + ".pdf "); System.Diagnostics.Process p1 = System.Diagnostics.Process.Start(psi1); // Move the PDF Reader window to 0,0 MoveProcess(p1, 0, 0); // Open the first PDF on a Specific Page System.Diagnostics.ProcessStartInfo psi2 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", page) + fi2.DirectoryName + "\\" + line + ".pdf "); System.Diagnostics.Process p2 = System.Diagnostics.Process.Start(psi2); // Move the PDF Reader window to 960,0 // TODO: This Offset could be a Setting MoveProcess(p2, 960, 0); return line; } /// /// Try to get the location of the PDF Reader executable /// /// Process of PDF Reader /// private string TryToGetPath(System.Diagnostics.Process p) { p.WaitForInputIdle(); while (p.MainModule == null) { Console.WriteLine("{0} - {1}", p.MainWindowTitle,p.ProcessName); p.WaitForInputIdle(); Application.DoEvents(); } return p.MainModule.FileName; } /// /// Start UltraCompare of two files /// /// /// private void CompareOneFile(string compareFile, string baseFile) { //Console.WriteLine("Compare {0} and {1}", compareFile, baseFile); string progname = string.Empty; if (System.IO.File.Exists(@"C:\Program Files\IDM Computer Solutions\UltraCompare\UC.exe")) progname = @"C:\Program Files\IDM Computer Solutions\UltraCompare\UC.exe"; if (System.IO.File.Exists(@"C:\Program Files (x86)\IDM Computer Solutions\UltraCompare\UC.exe")) progname = @"C:\Program Files (x86)\IDM Computer Solutions\UltraCompare\UC.exe"; System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(progname, string.Format(@" -t ""{0}"" ""{1}""", compareFile, baseFile)); System.Diagnostics.Process prc = System.Diagnostics.Process.Start(psi); } private ProcessLocationQueue myQueue= new ProcessLocationQueue(); private Timer queueTimer = null; /// /// Move a Process to a specific screen location - This is done with a timer so /// that it waits until windows are finished being initialized. /// /// /// /// private void MoveProcess(System.Diagnostics.Process proc, int x, int y) { if (queueTimer == null) { queueTimer = new Timer(); queueTimer.Enabled = false; queueTimer.Tick += queueTimer_Tick; queueTimer.Interval = 1000; } myQueue.Add(proc, x, y); if (!queueTimer.Enabled) queueTimer.Enabled = true; } /// /// timer tick for moving a process window /// /// /// void queueTimer_Tick(object sender, EventArgs e) { while (myQueue.Count > 0) myQueue.ProcessNext(); queueTimer.Enabled = false; } /// /// Open PDF associated with the selected line /// /// /// private void OpenOnePDF(Line myLine, int list) { // B2018-113 - Replace slashes and backslashes with underscores just as PROMS does when creating a PDF file. string proc = myLine.MyProc.Number.Replace("/","_").Replace("\\","_"); int pagenum = myLine.MyPage.Number; FindFile ff = lbDifferent.SelectedItem as FindFile; FileInfo fi1 = new FileInfo(ff.File1); FileInfo fi2 = new FileInfo(ff.File2); if (exePath == null) { System.Diagnostics.Process p = System.Diagnostics.Process.Start(fi1.DirectoryName + "\\" + proc + ".pdf"); while (exePath == null) { try { exePath = p.MainModule.FileName; } catch (Exception ex) { Application.DoEvents(); Console.WriteLine("{0} - {1}", ex.GetType().Name, ex.Message); } } p.Kill(); } if (list == 1) { System.Diagnostics.ProcessStartInfo psi1 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", pagenum) + fi1.DirectoryName + "\\" + proc + ".pdf "); System.Diagnostics.Process p1 = System.Diagnostics.Process.Start(psi1); } else { System.Diagnostics.ProcessStartInfo psi2 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", pagenum) + fi2.DirectoryName + "\\" + proc + ".pdf "); System.Diagnostics.Process p1 = System.Diagnostics.Process.Start(psi2); } } /// /// Perform Debug Meta file comparison for all of the folders within the automated testing folders /// /// /// private void btnBaseline_Click(object sender, EventArgs e) { myLast = LastWas.Baseline; lbDifferent.DataSource = null; // Initialize Differnce and Results ListBoxes lbDifferent.Items.Clear(); lbResults1.Items.Clear(); lbResults2.Items.Clear(); MyStatus = "Searching..."; // Perform code to find DebugMeta files with diffences FindFiles fnd = new FindFiles(cbFile1.Text, cbFile2.Text, "DebugMeta.txt",MyIgnore); lbDifferent.DataSource = fnd; lbDifferent.DisplayMember = "File1"; MyStatus = string.Format("{0} Differences Found", fnd.Count); } /// /// Perform search of all the DocVersions Debug.Meta Files /// /// /// private void btnSearch_Click(object sender, EventArgs e) { // Disble the UltraCompare button // Expand the Procedure Panel btnUC.Enabled= splitContainer3.Panel2Collapsed = false; myLast = LastWas.Search; // Initialize ListBoxes lbDifferent.DataSource = null; lbDifferent.Items.Clear(); lbResults1.Items.Clear(); lbResults2.Items.Clear(); MyStatus = "Searching..."; //Perform Search FindFiles fnd = new FindFiles(cbFile1.Text, cbFile2.Text, "DebugMeta.txt", tbSearch.Text, SearchType.Contains, cbCaseSensitive.Checked); lbDifferent.DataSource = fnd; lbDifferent.DisplayMember = "File1"; MyStatus = string.Format("{0} Differences Found", fnd.Count); } bool InHandler1 = false; // Track changes to the File Path 1 including Most Recent Used private void cbFile1_TextChanged(object sender, EventArgs e) { if (InHandler1) return; InHandler1 = true; string str = cbFile1.Text; while (cbFile1.Items.Contains(str)) cbFile1.Items.Remove(str); cbFile1.Items.Insert(0, str); cbFile1.SelectedIndex = 0; Properties.Settings.Default.MRU1 = new System.Collections.Specialized.StringCollection(); foreach (string str1 in cbFile1.Items) Properties.Settings.Default.MRU1.Add(str1); Properties.Settings.Default.Save(); InHandler1 = false; } bool InHandler2 = false; // Track changes to the File Path 2 including Most Recent Used private void cbFile2_TextChanged(object sender, EventArgs e) { if (InHandler2) return; InHandler2 = true; string str = cbFile2.Text; while (cbFile2.Items.Contains(str)) cbFile2.Items.Remove(str); cbFile2.Items.Insert(0, str); cbFile2.SelectedIndex = 0; Properties.Settings.Default.MRU2 = new System.Collections.Specialized.StringCollection(); foreach (string str2 in cbFile2.Items) Properties.Settings.Default.MRU2.Add(str2); Properties.Settings.Default.Save(); InHandler2 = false; } private void lbProcedures_SelectedIndexChanged(object sender, EventArgs e) { //Initialize Results List Box lbResults1.Items.Clear(); Procedure myProc = lbProcedures.SelectedItem as Procedure; //TODO: May need to consider if there are duplicate procedure numers and titles Procedure myProc1 = MyProcs1.Find(x => x.Number == myProc.Number && x.Title == myProc.Title); // Build the results ListBox for the left window if (myProc1 != null) { foreach (Page myPage in myProc1.MyPages) { lbResults1.Items.Add(myPage); foreach (Line myLine in myPage.MyLines) lbResults1.Items.Add(myLine); } } lbResults2.Items.Clear(); // Build the results ListBox for the right window Procedure myProc2 = MyProcs2.Find(x => x.Number == myProc.Number && x.Title == myProc.Title); if (myProc2 != null) { foreach (Page myPage in myProc2.MyPages) { lbResults2.Items.Add(myPage); foreach (Line myLine in myPage.MyLines) lbResults2.Items.Add(myLine); } } } /// /// Open Ultra Compare for the currently selected DocVersion /// /// /// private void btnUC_Click(object sender, EventArgs e) { if (myLast == LastWas.Pagination) { FindFile ff = lbDifferent.SelectedItem as FindFile; CompareOneFile(ff.File1, ff.File2); } } } public enum SearchType { Contains, StartsWith, EndsWith }; public class Settings { private BindingList _IgnoreLines; public BindingList IgnoreLines { get { return _IgnoreLines; } set { _IgnoreLines = value; } } } public partial class FindFile { private string _File1; public string File1 { get { return _File1; } set { _File1 = value; } } private string _File2; public string File2 { get { return _File2; } set { _File2 = value; } } public FindFile(string file1, string file2) { File1 = file1; File2 = file2; } } public partial class FindFiles : List { private string _FileName; public string FileName { get { return _FileName; } } /// /// Build list of DocVersion Folders with differences /// /// Base path /// Compare path /// filename /// Ignore list public FindFiles(string path1, string path2, string fileName,IgnoreLines myIgnore) { DirectoryInfo di1 = new DirectoryInfo(path1); DirectoryInfo di2 = new DirectoryInfo(path2); _FileName = fileName; FillByCompare(di1, di2, fileName, myIgnore); } /// /// Fill for a search /// /// /// /// /// /// /// public FindFiles(string path1, string path2, string fileName, string searchText, SearchType searchType, bool caseSensitive) { DirectoryInfo di1 = new DirectoryInfo(path1); DirectoryInfo di2 = new DirectoryInfo(path2); _FileName = fileName; FillByCompare(di1, di2, fileName,searchText,searchType, caseSensitive); } /// /// Fill results by search /// /// /// /// /// /// /// private void FillByCompare(DirectoryInfo di1, DirectoryInfo di2, string fileName, string searchText, SearchType searchType, bool caseSensitive) { foreach (DirectoryInfo diChild1 in di1.GetDirectories()) { DirectoryInfo diChild2 = new DirectoryInfo(di2.FullName + "\\" + diChild1.Name); if (diChild2.Exists) FillByCompare(diChild1, diChild2, fileName,searchText,searchType,caseSensitive); } foreach (FileInfo fiChild1 in di1.GetFiles(fileName)) { FileInfo fiChild2 = new FileInfo(di2.FullName + "\\" + fiChild1.Name); if (fiChild2.Exists) { if (CompareFile(fiChild1, fiChild2,searchText,searchType, caseSensitive)) Add(new FindFile(fiChild1.FullName, fiChild2.FullName)); } } } /// /// LINQ Peform Searc on DebugMeta files in all DocVersion folders /// /// /// /// /// /// /// private bool CompareFile(FileInfo fiChild1, FileInfo fiChild2, string searchText, SearchType searchType,bool caseSensitive) { IEnumerable lines1=null; IEnumerable lines2=null; switch (searchType) { case SearchType.Contains: if (caseSensitive) { lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.Contains(searchText)); lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.Contains(searchText)); } else { lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.ToUpper().Contains(searchText.ToUpper())); lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.ToUpper().Contains(searchText.ToUpper())); } break; case SearchType.StartsWith: if (caseSensitive) { lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.StartsWith(searchText)); lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.StartsWith(searchText)); } else { lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.ToUpper().StartsWith(searchText.ToUpper())); lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.ToUpper().StartsWith(searchText.ToUpper())); } break; case SearchType.EndsWith: if (caseSensitive) { lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.EndsWith(searchText)); lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.EndsWith(searchText)); } else { lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.ToUpper().EndsWith(searchText.ToUpper())); lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.ToUpper().EndsWith(searchText.ToUpper())); } break; } if (lines1 != null && lines2 != null) { if ((lines1.Count() > 0) || (lines2.Count() > 0)) { return true; } return false; } return false; } private void FillByCompare(DirectoryInfo di1, DirectoryInfo di2, string fileName, IgnoreLines myIgnore) { foreach (DirectoryInfo diChild1 in di1.GetDirectories()) { DirectoryInfo diChild2 = new DirectoryInfo(di2.FullName + "\\" + diChild1.Name); if (diChild2.Exists) FillByCompare(diChild1, diChild2, fileName,myIgnore);// Recursively work on Sub-Folders } foreach (FileInfo fiChild1 in di1.GetFiles(fileName)) { FileInfo fiChild2 = new FileInfo(di2.FullName + "\\" + fiChild1.Name); if (fiChild2.Exists) { if (CompareFile(fiChild1, fiChild2,myIgnore)) Add(new FindFile(fiChild1.FullName, fiChild2.FullName));// Process Each File } } } private bool CompareFile(FileInfo fiChild1, FileInfo fiChild2,IgnoreLines myIgnore) { if (fiChild1.Name == "DebugMeta.txt") { IEnumerable lines1 = ReadFilteredLines(fiChild1.FullName, false, myIgnore);//LINQ Read lines without ignored lines IEnumerable lines2 = ReadFilteredLines(fiChild2.FullName, false, myIgnore);//LINQ Read lines without ignored lines if (lines1.Except(lines2).Count() == 0 && lines2.Except(lines1).Count() == 0) //Look for lines in one file that are not in the other return false; return true; } else { string file1Contents = ReadContents(fiChild1);// This is just a simple text comparison string file2Contents = ReadContents(fiChild2); return !file1Contents.Equals(file2Contents); } } /// /// LINQ - Readlines and remove items based upon the ignore list /// /// /// /// /// public static IEnumerable ReadFilteredLines(string child1, bool includeProcedures, IgnoreLines myIgnore) { IEnumerable lines = File.ReadLines(child1); lines = lines.Where(x => FindProcedure(x,includeProcedures)); foreach (IgnoreLine ignore in myIgnore) { if (ignore.Active) { switch (ignore.SearchType) { case Relation.Contains: lines = lines.Where(x => x.Contains(ignore.Text) == false); break; case Relation.StartsWith: lines = lines.Where(x => x.StartsWith(ignore.Text) == false); break; case Relation.EndsWith: lines = lines.Where(x => x.EndsWith(ignore.Text) == false); break; case Relation.Regex: Regex myreg = new Regex(ignore.Text, RegexOptions.Compiled); lines = lines.Where(x => myreg.IsMatch(x) == false); break; default: break; } } } return lines; } /// /// Find a procedure line in a meta file /// /// /// /// private static bool FindProcedure(string x, bool includeProcedures) { bool result = x.StartsWith("!!") == false; if(includeProcedures) result |= Regex.IsMatch(x, @"^!![^|]+\|[^|]+?$", RegexOptions.Compiled); return result; } /// /// basic text read /// /// /// private string ReadContents(FileInfo fiChild1) { StreamReader sr = fiChild1.OpenText(); string buff = sr.ReadToEnd(); return buff; } } // Classes for structured data storage used for meta file comparisons // // Procedure contains: // Number - Procedure Number // Title - Procedure Title // MyPages - The Page objects associated with this procedure // // Page contains: // Number - PageNumber // MyLines - The Line Objects associated with this Page // // Line contains: // MyProc - The containing procedure // MyPage - the containing page // Text - the line of text public partial class Procedure { private string _Number; public string Number { get { return _Number; } set { _Number = value; } } private string _Title; public string Title { get { return _Title; } set { _Title = value; } } private Pages _MyPages = new Pages(); public Pages MyPages { get { return _MyPages; } set { _MyPages = value; } } public Procedure(string number, string title) { _Number = number; _Title = title; } public override string ToString() { return string.Format("{0} - {1}", Number, Title); } } public partial class Procedures : List { // Sample data for a Procedure Number line // !! E-0 | Reactor Trip Or Safety Injection // The First Group is the Procedure Number // The Second Group is the Procedure Title public Regex parseLine = new Regex("^!! (.*) \\| (.*)$", RegexOptions.Compiled); /// /// This adds a procedure, page and line class as needed, If the procedure /// already exists it is used. If the page already exists it is used. /// /// Procedure Line matches Regular Expression above /// Page Number /// Text from the meta File public void Add(string lastProc, int pageNumber, string text) { if (lastProc == null) return; Match m = parseLine.Match(lastProc); if (m.Success) { Procedure myProc = this.Find(x => x.Number.Equals(m.Groups[1].ToString()) && x.Title.Equals(m.Groups[2].ToString())); if (myProc == null) this.Add(myProc = new Procedure(m.Groups[1].ToString(), m.Groups[2].ToString())); Page myPage = myProc.MyPages.Find(x => x.Number == pageNumber); if (myPage == null) myProc.MyPages.Add(myPage = new Page(pageNumber)); myPage.MyLines.Add(new Line(text, myProc, myPage)); } } } public partial class Page { private int _Number; public int Number { get { return _Number; } set { _Number = value; } } private Lines _MyLines = new Lines(); public Lines MyLines { get { return _MyLines; } set { _MyLines = value; } } public Page(int number) { _Number = number; } public override string ToString() { return string.Format("Page {0}", Number); } } public partial class Pages : List { public void Add(int number) { Add(new Page(number)); } } public partial class Line { private Procedure _MyProc; public Procedure MyProc { get { return _MyProc; } set { _MyProc = value; } } private Page _MyPage; public Page MyPage { get { return _MyPage; } set { _MyPage = value; } } private string _Text; public string Text { get { return _Text; } set { _Text = value; } } public Line(string text) { _Text = text; } public Line(string text, Procedure myProc, Page myPage) { _Text = text; _MyProc = myProc; _MyPage = myPage; } public override string ToString() { return Text; } } public partial class Lines : List { public void Add(string text) { Add(new Line(text)); } } [Serializable] public partial class IgnoreLine { private bool _Active = true; public bool Active { get { return _Active; } set { _Active = value; } } private Relation _SearchType = Relation.Contains; public Relation SearchType { get { return _SearchType; } set { _SearchType = value; } } private string _Text; public string Text { get { return _Text; } set { _Text = value; } } public IgnoreLine(string text, Relation searchType, bool active) { Text = text; SearchType = searchType; Active = active; } public IgnoreLine() { } } [Serializable] public partial class IgnoreLines : BindingList { public IgnoreLines() { } public void Add(string text, Relation searchType, bool active) { Add(new IgnoreLine(text, searchType, active)); } // Convert IgnoreLines to string (XML) public override string ToString() { return GenericSerializer.StringSerialize(this); } // Convert string to IgnoreLines public static IgnoreLines Get(string xml) { return GenericSerializer.StringDeserialize(xml); } } /// /// This is a simple serializer that takes a class and converts it to and from string (XML) /// /// public static class GenericSerializer where T : class { public static string StringSerialize(T t) { string strOutput = string.Empty; XmlSerializer xs = new XmlSerializer(typeof(T)); using (MemoryStream ms = new MemoryStream()) { xs.Serialize(new NonXsiTextWriter(ms, Encoding.Unicode), t); //xs.Serialize(ms, t); ms.Position = 0; StreamReader sr = new StreamReader(ms); strOutput = sr.ReadToEnd(); ms.Close(); } return strOutput; } public static T StringDeserialize(string s) { T t; string ss = s.Replace("encoding=\"utf-16\"", ""); XmlSerializer xs = new XmlSerializer(typeof(T)); UTF8Encoding enc = new UTF8Encoding(); Byte[] arrBytData = enc.GetBytes(ss); using (MemoryStream ms = new MemoryStream(arrBytData)) { t = (T)xs.Deserialize(ms); } return t; } } /// /// This XML Writer makes the XML more simple by excluding the XSI attributes from the serializer /// public class NonXsiTextWriter : XmlTextWriter { public NonXsiTextWriter(TextWriter w) : base(w) { } public NonXsiTextWriter(Stream w, Encoding encoding) : base(w, encoding) { this.Formatting = Formatting.Indented; } public NonXsiTextWriter(string filename, Encoding encoding) : base(filename, encoding) { } bool _skip = false; public override void WriteStartAttribute(string prefix, string localName, string ns) { if ((prefix == "xmlns" && (localName == "xsd" || localName == "xsi")) || // Omits XSD and XSI declarations. ns == XmlSchema.InstanceNamespace) // Omits all XSI attributes. { _skip = true; return; } if (localName == "xlink_href") base.WriteStartAttribute(prefix, "xlink:href", ns); else base.WriteStartAttribute(prefix, localName, ns); } public override void WriteString(string text) { if (_skip) return; base.WriteString(text); } public override void WriteEndAttribute() { if (_skip) { // Reset the flag, so we keep writing. _skip = false; return; } base.WriteEndAttribute(); } } /// /// Class to support moving a process window /// public class ProcessLocation { [DllImport("user32.dll", EntryPoint = "SetWindowPos")] public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags); public const short SWP_NOMOVE = 0X2; public const short SWP_NOSIZE = 1; public const short SWP_NOZORDER = 0X4; public const int SWP_SHOWWINDOW = 0x0040; private System.Diagnostics.Process _Process; public System.Diagnostics.Process Process { get { return _Process; } set { _Process = value; } } private int _X; public int X { get { return _X; } set { _X = value; } } private int _Y; public int Y { get { return _Y; } set { _Y = value; } } public ProcessLocation(System.Diagnostics.Process process, int x, int y) { Process = process; X = x; Y = y; } private static Boolean FoxitSettingInfo = true; /// /// MoveIt() moves the window containing the PDF viewer to the right so the two pdf viewer windows will not overlap. /// public void MoveIt() { try { SetWindowPos(Process.MainWindowHandle, 0, X, Y, 200, 400, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOZORDER); } catch // B2018-147 bring up message to inform user of the setting that will create separate windows of the pdf viewer { if (FoxitSettingInfo) MessageBox.Show("If you want to see the documents in separate windows,\nyou need to set Foxit to allow multiple instances. \nSelect File | preferences | Documents\n Check the Allow Multiple Instances", "Foxit Settings"); FoxitSettingInfo = false; } } } public class ProcessLocationQueue: Queue { public void Add(System.Diagnostics.Process process, int x, int y) { Enqueue(new ProcessLocation(process,x,y)); } public void ProcessNext() { ProcessLocation pl = Dequeue(); pl.MoveIt(); } } }