Makale Özeti

Yazdığımız uygulamalarda bazı verilerde yapılan değişikliklerin geçmişinin tutulması gerekmektedir. Bu durumda kullanıcılar farklı iki versiyonu karşılaştırmada 'Track Changes' benzeri bir yöntemle farklılıkları kolay ayırt etmeyi isteyebileceklerdir veya bu özelliği siz projenizde bir artı değer olarak sunmak isteyebilirsiniz. Bir başka senaryo ise bazı bilgilerin değiştiğinde değişikliğin kabul edilebilmesi için yönetici onayına düşmesidir. Bu değişiklikleri onaylayacak yönetici yine değişiklikleri kolay ayırt edebilmek isteyecektir. Bu gibi durumlarda kullanıcılara Word'de bulunan 'Track Changes' benzeri bir gösterimi nasıl yapabileceğinizi bu makalede anlatacağım.

Makale

Merhabalar,

         Yazdığımız uygulamalarda bazı verilerde yapılan değişikliklerin geçmişinin tutulması gerekmektedir. Bu durumda kullanıcılar farklı iki versiyonu karşılaştırmada 'Track Changes' benzeri bir yöntemle farklılıkları kolay ayırt etmeyi isteyebileceklerdir veya bu özelliği siz projenizde bir artı değer olarak sunmak isteyebilirsiniz. Bir başka senaryo ise bazı bilgilerin değiştiğinde değişikliğin kabul edilebilmesi için yönetici onayına düşmesidir. Bu değişiklikleri onaylayacak yönetici yine değişiklikleri kolay ayırt edebilmek isteyecektir. Bu gibi durumlarda kullanıcılara Word'de bulunan 'Track Changes' benzeri bir gösterimi nasıl yapabileceğinizi bu makalede anlatacağım.

         Öncelikle bu işlemi yapabilmek için bize gerekli altyapıyı Menees Software (http://www.menees.com/) tarafından geliştirilmiş Diff.Net library'si sağlayacaktır. İndirmek için http://www.menees.com/Files/MeneesDiffUtils.zip adresini ziyaret edebilir veya bu makalenin Download'lar kısmına bakabilirsiniz. Şimdi isterseniz BinaryDiff, Copy, DiractoryDiff, TextDiff gibi gelişmiş karşılaştırmalar yapabilen bu kütüphanenin içindeki TextDiff özelliğinin kullanımını inceleyelim sonrasında ise bu kütüphanedeki metodları kullanarak 'Track Changes' işlemini yapacak class'ımızı yazalım;

Öncelikle TextDiff nesnesinin bir örneğini alabilmek için karşılaştırmanın hangi hash'e göre yapılacağını, boşluk karakterlerinin göz ardı edilip edilmeyeceğini ve büyük küçük harf duyarlı karşılaştırma yapılıp yapılmayacağını nesnenin bir instance'ını alırken constructor'da belirlememiz gerekiyor. Sonrasında ise yarattığımız bu nesnenin Execute metodunu kullanarak karşılaştırma işlemimizi yapabiliyoruz. Burada dikkat etmemiz gereken bir nokta Execute metodunun bizden parametre olarak iki tane string list alması. Bundan dolayı biz de iki metni karşılaştırıken öncelikle metinleri string list'e çevireceğiz. İsterseniz şimdide bu metodun döndürdüğü sonuçtan bahsedelim. Bu sonuçları yorumlayabilirsek metodumuz başarıyla çalışacaktır. Bu metod bize EditScript nesnesi döndürmektedir. Bu nesne Edit nesnelerini taşıyan bir collection'dır. Edit nesnesinin özelliklerini inceleyelim;

StartA Belirtilen değişikliğin Execute metoduna ilk parametre olarak verdiğimiz list'te hangi item'dan başladığını belirtir.
StartB Belirtilen değişikliğin Execute metoduna ikinci parametre olarak verdiğimiz list'te hangi item'dan başladığını belirtir.
Length Belirtilen değişikliğin kaç Item boyunca devam ettiğini belirtir.
Type Belirtilen değişikliğin tipinin ne olduğunu belirtir. Olası tipler;Change, Delete, Insert, None değerlerinin taşıyan EditType enum'undan gelmektedir.


Öncelikle bir tane Web Site projesi oluşturuyoruz. Sonraki adımda ise bu projenin referanslarına MeneesDiffUtils kütüphanesini ekliyoruz.



Sonraki adımda ise ismi StringDiff olan class'ımızı projeye ekliyoruz ve bu class'ın içine kodumuzu yazmaya başlıyoruz.

using System;
using System.Collections.Generic;
using System.Web;
using Menees.DiffUtils;
 
namespace StringDiffSample
{
    public class StringDiff
    {
Yukarıda gördüğünüz gibi öncelikle class'ımızı oluşturuyoruz. Bu işlemden sonra artık asıl işlemi yapacağımız metod olan GetDiffString metodunu yazmaya geçebiliriz.

        public string GetDiffString(string Astr, string Bstr)
        {
            List<string> A = new List<string>();
            List<string> B = new List<string>();
                foreach (string s in Astr.Split(" ".ToCharArray()))
                {
                    A.Add(s);
                }
                foreach (string s in Bstr.Split(" ".ToCharArray()))
                {
                    B.Add(s);
                }
Biz metodumuzda varsayılan olarak metinleri kelime kelime ayıracağız. Bunun için iki tane string değişken açık public bir metod ve bu metodun içinden çağıracağımız private metodumuzu yazıyoruz. Private metodumuzun ilk satırlarında gelen metni belirtilen split type'a göre split edip A ve B adındaki iki farklı string list'e atıyoruz.

            Menees.DiffUtils.TextDiff diff = new Menees.DiffUtils.TextDiff(Menees.DiffUtils.HashType.HashCode, false, false);
            EditScript es = diff.Execute(A, B);
Daha sonra oluşturduğumuz bu iki string list'i makalenin başında anlattığım MeneesDiff kütüphanesindeki TextDiff class'ının Execute metoduna geçiriyoruz ve EditScript tipinden sonucumuzu elde ediyoruz.

            int j = 0;
            foreach (Edit edit in es)
            {
Kuracağımız algoritmanın içinde işimize yarayacak bazı değişkenleri tanımlıyoruz ve EditScript nesnesinin içinde yer alan Edit nesneleri için bir for each iteration'ı yazıyoruz.

                for (int i = edit.StartA; i < edit.StartA + edit.Length; i++)
                {
                    if (i < A.Count)
                    {
                        if (edit.Type == EditType.Delete)
                        {
                            A[i] = "<font color=red><s>" + A[i] + "</s></font>";
                            B.Insert(i, A[i]);
                            j++;
                        }
                    }
                }
Tüm değişiklikleri B string listimizde yapacağımızı düşünerek kodumuzu yazmaya başlıyoruz. Öncelikle döngümüzdeki ilk değişiklik kaydı için ilk geçirdiğimiz parametrede bulunan ve ikinci parametrede bulunmayan yani silinmiş kayıtları buluyoruz. Bu silinmiş kayıtları üstü çizgili ve kırmızı şekilde göstermek için metni gerekli html kodları arasına alıyoruz ve yine aynı listedeki değişkene atıyoruz. Bundan sonra aynı metni B değişkenindeki yerine yani eskiden olduğu ancak silindiği yere ekliyoruz. ve B'ye kaç ekleme yaptığımız daha ileride işimize yarayacağından j değişkenini bir arttırıyoruz.

                for (int i = edit.StartB; i < edit.StartB + edit.Length; i++)
                {
                    if (i < B.Count)
                    {
                        if (edit.Type == EditType.Change)
                        {
                            string s = "";
                            s = "<font color=red><s>" + A[edit.StartA + i - edit.StartB] + "</s></font><font color=green><u>" + B[i + j] + "</u></font>"; 
                             B[i + j] = s;
                        }
                        else if (edit.Type == EditType.Insert)
                        {
                            B[i + j] = "<font color=green><u>" + B[i + j] + "</u></font>";
                        }
                    }
                }
Bir sonraki adımda ise aynı değişikliğin ikinci parametre geçirdiğimiz metni nasıl etkilediğini inceliyoruz. İkinci parametre metnin yeni hali olduğu için bu kısımda bize silinme kaydı gelmeyecektir. Sadece ekleme ve değişiklikler gelecektir. Öncelikle değişiklikleri buluyoruz. Değişiklikler için öncelikle aynı metni ilk parametre geçirdiğimiz A listesinden silinmiş ikinci parametre geçirdiğimiz B dizisine eklenmiş gibi davranacağız. Bunun için aynı metnin A string listindeki değerini bulup font rengini kırmızı yapıyor, B string listindeki karşılığını bulup altı çizgili ve yeşil yapıyoruz. Bulduğumuz bu değeri B list stringindeki ilgili sıraya ekliyoruz. B list stringinde hangi sıraya ekleyeceğimize A metninden silinenlerden kaç tane araya eklediğimiz ile B metninden değişikliğin kaçıncı karakterden başladığını toplayarak yapıyoruz. Yeni eklenmiş metinler için ise metni direkt altı çizgili ve yeşil yapıyoruz.

                }
            }
            string retval = "";
            foreach (string s in B)
            {
 
                 retval += s + " ";
 
             }
            return retval;
        }
    }
}
Son işlem olarak B dizisindeki kelimeleri birleştirerek track changes yapılmış metnimizi ortaya çıkarıyoruz ve bu metni metodumuzdan geri döndürüyoruz. Şimdi isterseniz bunun ufak bir demosunu yapalım. Bunun için bir web sayfasına iki textbox ve bir button koyacağız ve button'un click olayına aşağıdaki kodu yazacağız.

            lblResult.Text = new StringDiff().GetDiffString(txtOld.Text, txtNew.Text);
Sonuç aşağıdaki gibi oluyor.



Umarım işinize yaramıştır. 

         tamer.oz@yazgelistir.com
         oztamer@hotmail.com
ORnek Kodlar