۱۳۸۷/۰۸/۱۷

جزئيات برنامه نويسي افزونه فارسي به پارسي


اين افزونه با استفاده از ابزار Visual Studio Tools for Office كه به VSTO مشهور شده است، تهيه شد. در بسته به روز رساني سيستم كه در ذيل (معرفي افزونه) نيز معرفي شد نگارش sp1 vsto3.0 آن به صورت خودكار نصب خواهد شد.
براي ايجاد اين پروژه در VS.Net 2008 ، تنها كافي است يك پروژه جديد Word add-in را آغاز نمائيم. (شكل زير)





قبل از ادامه بحث، بهتر است در مورد بانك اطلاعاتي مورد استفاده نيز توضيح داده شود. در اينجا از SQLite استفاده شد. (بسيار سبك، كم حجم و سريع است و اساسا يك كاربر نهايي براي تنظيمات آن نيازي نيست اطلاعاتي داشته باشد). بسته به روز رساني سيستم (در مطلب قبلي)، اين مورد را نيز به صورت خودكار نصب خواهد كرد (در GAC بايد نصب شود وگرنه افزونه قادر به يافتن آن نخواهد شد).
براي ايجاد اين بانك اطلاعاتي، از افزونه SQLite manager براي فايرفاكس استفاده شد. (اين افزونه رايگان شما را از هر ابزار جانبي براي مديريت يك بانك اطلاعاتي SQLite بي‌نياز مي‌كند)
براي مثال فايل ErrorsBank.sqlite برنامه افزونه فارسي به پارسي را توسط افزونه SQLite manager فايرفاكس باز كنيد (اين فايل را در محل نصب افزونه مي‌توانيد پيدا كنيد). در اينجا مي‌توان جداول جديد را ايجاد كرد، كوئري‌هاي دلخواه را اجرا نمود و يا اطلاعات را مرور كرده، حذف يا ويرايش كرد (شكل زير).




و خوشبختانه اين بانك اطلاعاتي و محصور كننده‌هاي آن با اطلاعات يونيكد فارسي هيچ مشكلي ندارند و براي كارهايي با وسعت كم و تعداد ركورد پائين يكي از بهترين انتخاب‌ها به‌شمار مي‌روند.
نحوه استفاده از SQLite نيز در دات نت بسيار ساده است. اگر با ADO.Net كار كرده باشيد، پس از افزودن ارجاعي از اسمبلي System.Data.SQLite.DLL به پروژه و معرفي فضاي نام آن به پروژه، تنها كافي است در كدهاي قبلي خود براي مثال SqlConnection را به SQLiteConnectionتغيير دهيد و امثال آن. يعني دانش ADO.Net شما در اينجا نيز كاملا قابل استفاده خواهد بود و نيازي نيست مدتي را صرف آشنا شدن با كلاس‌ها و مفاهيم جديد نمائيد (البته اين تنها زماني معنا خواهد داشت كه به ويزاردها عادت نكرده باشيد و كارهاي خود را با كد نويسي انجام داده باشيد).
تنها يك نكته را بايد به‌خاطر داشت و آن هم مربوط است به ساز و كار دروني SQLite . هنگام انجام عمليات update يا insert حتما از transaction استفاده كنيد تا سرعت كوئري‌هاي شما در SQLite به نحو شگفت انگيزي افزايش يابد. مثالي در اين مورد را در فايل chm راهنماي SQLite.NET مي‌توانيد پيدا كنيد.

مطلب ديگري كه پيش از پرداختن به كد نويسي افزونه بايد با آن آشنا شويم، مفهوم smart tags در مجموعه آفيس است كه در اين پروژه از آن استفاده گرديد.
smart tags در مجموعه آفيس برچسب‌هايي هستند كه به صورت خودكار توسط يكي از محصولات آفيس مثلا ورد يا اكسل و امثال آن، پس از تشخيص يك كلمه خاص ايجاد مي‌شوند و مي‌توان اعمالي را به اين برچسب ايجاد شده انتساب داد. براي مثال در اينجا امكان جايگزين كردن كلمه فارسي با معادل پارسي در نظر گرفته شد.
ويديويي در مورد نحوه ايجاد اسمارت تگ‌ها در VS.Net و يا مثالي پيشرفته‌تر در مورد تشخيص دماي فارنهايت در يك متن و ايجاد smart tag مخصوص به آن براي تبديل به سلسيوس. (از regular expressions جهت يافتن يك الگو در متن استفاده شده است)

در اين پروژه، حدود 3800 واژه فارسي به‌ يك smart tag انتساب داده مي‌شود (در روال استاندارد ThisAddIn_Startup). سپس در هنگام نمايش آن، معادل پارسي كلمه نيز به منوي باز شده افزوده گشته و در روال رخداد كليك آن، تعويض كلمه تشخيص داده شده با واژه پيدا شده صورت خواهد گرفت.

در ادامه فرض بر اين است كه يك پروژه جديد word add-in را در VS.Net ايجاد كرده‌ايد و همچنين ارجاعي را به فايل System.Data.SQLite.DLL افزوده‌ايد.

using System;
using System.Diagnostics;
using Microsoft.Office.Tools.Word;
using Action = Microsoft.Office.Tools.Word.Action;

private SmartTag _st;
private void init()
{
try
{
//Enable Smart Tags in Word
if (!Application.Options.LabelSmartTags)
{
//ممكن است اسمارت تگ‌ها در ورد غيرفعال باشند. به اين صورت مي‌شود آنها را فعال كرد
Application.Options.LabelSmartTags = true;
}

_st = new SmartTag(@"www.microsoft.com/Demo#FarsiSmartTag", @"فارسى به پارسى");

//دريافت واژه‌هاي فارسي از ديتابيس و افزودن خودكار آنها به اسمارت تگ‌ها
if (!DBhelper.AddSmartTagItems(_st, "select distinct farsi from tblFarsiToParsi")) return;

Action stActions = new Action("تبديل");//تعريف يك اكشن جديد
stActions.Click += stActions_Click;//انتساب روال‌هاي رخداد گردان
stActions.BeforeCaptionShow += stActions_BeforeCaptionShow;
_st.Actions = new[] { stActions };
VstoSmartTags.Add(_st);//افزودن اسمارت تگ به مجموعه
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}

private void ThisAddIn_Startup(object sender, EventArgs e)
{
init();
}

دو روال رخداد گردان زير نيز جهت تغيير عنوان پيش فرض به واژه يافته شده در لحظه نمايش منو و روال كليك نيز ايجاد خواهد شد:

static void stActions_BeforeCaptionShow(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
string parsi = DBhelper.FindParsi(e.Text);//معادل پارسي از ديتابيس دريافت مي‌شود
clickedAction.Caption = (parsi == string.Empty ? e.Text : parsi);
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}

static void stActions_Click(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
e.Range.Text = clickedAction.Caption;//جايگزيني متن موجود با عنواني كه پيشتر پارسي شده است
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}

نكته‌اي را كه در اينجا بايد حتما رعايت كرد بحث exception handling‌ است. خصوصا در روال استاندارد ThisAddIn_Startup . اگر در اين روال خطايي مديريت نشده رخ دهد، word افزودني شما را به صورت غيرفعال به مجموعه اضافه خواهد كرد و فعال سازي بعدي آن پس از اصلاح كد واقعا مشكل خواهد بود. همانطور كه ملاحظه مي‌كنيد تمامي خطاها در event log‌ ويندوز نوشته مي‌شوند.
همچنين بايد دقت داشت كه اگر متغيري در سطح كلاس تعريف نشود به احتمال زياد تا دقايقي بعد توسط garbage collector به ديار باقي خواهد شتافت (تعريف st_ در اينجا). اينجاست كه شايد ساعت‌ها وقت صرف كنيد كه چرا روال‌هاي رخ‌داد گردان ديگر اجرا نمي‌شوند. چرا افزونه ديگر كار نمي‌كند.

همين! كل سورس اين add-in منهاي بحث دريافت اطلاعات از ديتابيس همين بود! وظيفه‌ي تشخيص كلمات معرفي شده به ms-word به‌عهده‌ي خود آن است و اين‌كار را نيز به‌خوبي انجام مي‌دهد. در گذشته‌هاي نچندان دور ايجاد يك افزونه براي word واقعا مشكل بود كه با اين روش بسياري از موانع برطرف شده است.

كلاس DBHelper كه كار دريافت اطلاعات واژه‌ها را از ديتابيس SQLite انجام مي‌دهد به شرح زير است:

using System;
using System.Data.SQLite;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Office.Tools.Word;

namespace Farsi2Parsi
{
class DBhelper
{
#region Methods (2)

// Public Methods (2)

public static bool AddSmartTagItems(SmartTag st, string strSQL)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
bool ret = false;
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();

if (myReader != null)
while (myReader.Read())
{
if (myReader.GetValue(0) != DBNull.Value)
st.Terms.Add(myReader.GetValue(0).ToString());
}

ret = true;
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 7);
}
finally
{
if (myReader != null)
myReader.Close();

if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}

public static string FindParsi(string farsi)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
string ret = string.Empty;
string strSQL = "select parsi from tblFarsiToParsi where farsi='" + farsi.Replace("'", "''") + "'";
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();

if (myReader != null)
{
myReader.Read(); //اولين مورد كافي است
if (myReader.GetValue(0) != DBNull.Value)
ret = myReader.GetValue(0).ToString();
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 8);
}
finally
{
if (myReader != null)
myReader.Close();

if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}
#endregion Methods
}
}

همانطور كه پيشتر نيز عنوان شد اگر با ADO.net آشنايي داشته باشيد، هيچ نكته‌ي خاص جديدي را در اينجا مشاهده نخواهيد كرد و تنها يك سري امور روزمره كاري با ADO.net مطرح شده است، باز كردن كانكشن، اجراي كوئري، دريافت اطلاعات و پاكسازي نهايي. (قسمت finally را با استفاده از عبارت using مي‌شود حذف كرد)

هنگام نصب برنامه، مسير پوشه نصب در رجيستري ويندوز توسط نصاب نوشته خواهد شد. از همين مورد براي ايجاد رشته اتصالي به ديتابيس استفاده گرديد.

class ConStr
{
public static string ConnectionString
{
get
{
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\FarsiToParsi").GetValue("folder") + "\\ErrorsBank.sqlite";
}
}
}

سورس كامل اين افزونه را به صورت يك پروژه VS.Net 2008 SP1 از اينجا مي‌توانيد دريافت كنيد.
نصاب برنامه با استفاده از NSIS ايجاد شده كه در روزي ديگر درباره‌ي آن توضيح خواهم داد.
اگر قصد داشته باشيد از روش‌هاي متداول استفاده كنيد، مشاهده ويديوي زير توصيه مي‌شود:
http://msdn.microsoft.com/en-us/office/bb851702.aspx

براي توزيع اين نوع افزونه‌ها علاوه بر دات نت فريم ورك، به چهار به روز رساني ديگر نيز نياز خواهد بود:
به روز رساني نصاب ويندوز (كه احتمالا نصب هست)
WindowsInstaller-KB893803-v2-x86.exe
Microsoft Office System Update: Redistributable Primary Interop Assemblies :
o2007pia.msi
نصب vsto و همچنين sp1 آن
vstor30.exe
vstor30sp1-KB949258-x86.exe

اين موارد را من در بسته به روز رساني سيستم قرار داده‌ام كه به صورت خودكار و يكي پس از ديگري اجرا و نصب خواهند شد.
پس از آن با كليك بر روي فايلي با پسوند vsto كه در پوشه build برنامه موجود است، مي‌توان افزونه را نصب كرد (click once installation).




ساير اطلاعات در مورد پروژه‌هاي VSTO را مي‌توان از طريق وبلاگ رسمي آنها دنبال كرد:
http://blogs.msdn.com/vsto/

ايده‌هاي ديگري را هم در همين رابطه مي‌توان پياده سازي كرد. براي مثال درست كردن يك افزونه براي بررسي آئين نگارش فارسي در متون word. دقيقا با همين روش قابل پياده سازي است و يا ايجاد غلط ياب بهتري نسبت به آن‌چه كه هم اكنون براي آفيس 2003 توسط مايكروسافت ارائه شده است (اين غلط ياب با صفحه كليد استاندارد تايپ ايران همخواني ندارد، به همين جهت با استقبال نيز مواجه نشد).