Время прочтения: 5 мин.

Для выявления несоответствий в разных версиях файла формата .docx, в редакторе Microsoft Word есть стандартное средство сравнения. Оно показывает различия документов, так же в некоторых случаях, может показать, кем и когда было сделано изменение. Однако, обработка большого количества файлов, нуждающихся в сравнении, требует автоматизации процесса. В арсенале С# имеется библиотека “Microsoft.Office”, которую можно использовать для автоматизации обработки файлов. С её помощью можно выполнять множество операций, аналогичных использованию продуктов Microsoft, но масштабируя объёмы обработки.

Для того, чтобы аудитору было удобно просматривать разницу файлов и выбирать документы, в которых различия несут важную информацию, возьмём изменения файла, ближайший контекст и сложим результат в таблицу, сопроводив ссылкой на файл.

Необходимые зависимости для дальнейшей работы:

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

using System.Data;
using System.IO;
using OfficeWord = Microsoft.Office.Interop.Word;
using Paragraph = DocumentFormat.OpenXml.Wordprocessing.Paragraph;

Но прежде чем подключать к работе мощную библиотеку, можно простым побайтовым сравнением определить, различаются ли документы и есть ли смысл продолжать их обработку.

private bool CompareFile(string Path1, string Path2)
        {
            //compare files byte to byte

            int file1byte;
            int file2byte;

            FileStream fs1 = new FileStream(Path1, FileMode.Open);
            FileStream fs2 = new FileStream(Path2, FileMode.Open);

            do
            {
                file1byte = fs1.ReadByte();
                file2byte = fs2.ReadByte();
            }
            while ((file1byte == file2byte) && (file1byte != -1));

            fs1.Close();
            fs2.Close();

            return ((file1byte - file2byte) == 0);

        }

Если файлы различаются, с помощью библиотеки “Microsoft.Office” сравниваем их и создаём новый документ, демонстрирующий разницу.

private string CompareFileWord(string Path1, string Path2, string compareFileFolder)
        {
            //create Word application
            var app = new OfficeWord.Application();
            app.DisplayAlerts = OfficeWord.WdAlertLevel.wdAlertsNone;
            object missing = System.Reflection.Missing.Value;
            object readOnly = false;
            object AddToRecent = false;
            object Visible = true;

            try
            {
                //try open signed file 
                OfficeWord.Document docZero = app.Documents.Open(Path2, ref missing, ref readOnly, ref AddToRecent, Visible: ref Visible);

                docZero.Final = false;
                docZero.TrackRevisions = true;
                docZero.ShowRevisions = true;
                docZero.PrintRevisions = true;

                //compare file from card and signed file
                docZero.Compare(Path1, missing, OfficeWord.WdCompareTarget.wdCompareTargetCurrent, true, false, false, false, false);
                string name = System.IO.Path.GetFileName(Path1);
                string fileName = compareFileFolder + name;

                //save file of compare
                docZero.SaveAs2(fileName);
                docZero.Close();
                app.Quit();

                return fileName;
            }

            catch
            {
                app.Quit();
                return "";
            }


        }

Файл сравнения выглядит так:

Далее, получаем изменённый текст и его контекст из файла сравнения и добавляем его в результирующую таблицу. Так как файлы, формата .docx по факту представляют из себя xml файлы, мы производим поиск текста по тегам. Тег “paragraph”, имеющий локальное имя “del” содержит изменённый текст.

private string Take_Compare(string filePath)
        {
            string resultText = "";
            using (WordprocessingDocument wordprocDoc = WordprocessingDocument.Open(filePath, true))
            {
                Body body = wordprocDoc.MainDocumentPart.Document.Body;
                //take each paragraph which contain text
                IEnumerable<Paragraph> paragraphs = body.Elements<Paragraph>().Where(paragrahp => paragrahp.InnerText != "");
                List<Paragraph> paragraphsList = paragraphs.ToList();
                StringBuilder csvResult = new StringBuilder();
                string fileName = Path.GetFileName(filePath);

                int delFlag = 0;
                string text = "";
                for (var i = 0; i < paragraphsList.Count(); i++)
                {
                    //take paragraph which have local name "del"(this string contain change text)
                    if (paragraphsList[i].ChildElements.Where(child => child.LocalName == "del").Count() != 0)
                    {
                        // if paragraph before not "del" paragraph, add as context 
                        if (delFlag == 0)
                        {
                            if (i > 0)
                            {
                                text = paragraphsList[i - 1].InnerText;
                            }
                            
                            delFlag = 1;
                        }

                        //take text from "del" paragraph

                        foreach (OpenXmlElement child in paragraphsList[i].ChildElements)
                        {

                            if (child.LocalName == "del")
                            {
                                text = text + " <del> " + child.InnerText + " </del> ";
                            }
                            else
                            {
                                text = text + child.InnerText;
                            }
                        }
                    }
                    else
                    {
                        //if before usual paragraph was "del" paragraph, add this paragraph and finish process
                        if (delFlag == 1)
                        {
                            text = text + paragraphsList[i].InnerText;
                            string newLine = string.Format("{0};{1}", fileName, text).Replace("</del>  <del>", " ");
                            csvResult.AppendLine(newLine);
                            delFlag = 0;
                            text = "";
                        }
                    }
                }
                if (delFlag == 1)
                {
                    //if file end on usual paragraph, after paragraph with "del"
                    text = text + paragraphsList[paragraphsList.Count()-1].InnerText;
                    string newLine = string.Format("{0};{1}", fileName, text).Replace("</del>  <del>", " ");
                    csvResult.AppendLine(newLine);
                    delFlag = 0;
                    text = "";
                }
                wordprocDoc.Close();

                //delete carriage return symbol
                if (csvResult.ToString().Length > 2)
                {
                    resultText = csvResult.ToString().Substring(0, csvResult.ToString().Length - 2);
                }
                else
                {
                    resultText = csvResult.ToString();
                }
                

            }
            return resultText;
        }

Приведённые методы класса вызываются в цикле, для каждой пары файлов, нуждающихся в сравнении, и в конце выполняется сохранение различий в файл формата .csv:

StringBuilder csvResult = new StringBuilder();
string newLine = string.Format("{0};{1}", "Имя файла", "Текст");
csvResult.AppendLine(newLine);
string ResultFile = @"Результаты.csv";
string text = "";
//Здесь вызов методов
text = text + csvResult.ToString();

File.WriteAllText(ResultFile, text, System.Text.Encoding.UTF8);

Результирующий файл выглядит следующим образом:

Таким образом, мы получаем для каждого случая, файл сравнения и запись в результирующей таблице. Изучив результирующую таблицу, аудитор может посмотреть файл сравнения для более детального изучения заинтересовавших его случаев.