مطالب
نگاهی به Latent Semantic Indexing
مقدمه ای بر Latent Semantic Indexing

هنگامیکه برای اولین بار، جستجو بر مبنای کلمات کلیدی (keyword search) بر روی مجموعه‌ای از متون، به دنیای بازیابی اطلاعات معرفی شد شاید فقط یک ذهنیت مطرح می‌شد و آن یافتن لغت در متن بود. به بیان دیگر در آن زمان تنها بدنبال متونی می‌گشتیم که دقیقا شامل کلمه کلیدی مورد جستجوی کاربر باشند. روال کار نیز بدین صورت بود که از دل پرس و جوی کاربر، کلماتی بعنوان کلمات کلیدی استخراج می‌شد. سپس الگوریتم جستجو در میان متون موجود بدنبال متونی می‌گشت که دقیقا یک یا تمامی کلمات کلیدی در آن آمده باشند. اگر متنی شامل این کلمات بود به مجموعه جواب‌ها اضافه می‌گردید و در غیر این صورت حذف می‌گشت. در پایان جستجو با استفاده از الگوریتمی، نتایج حاصل رتبه بندی می‌گشت و به ترتیب رتبه با کاربر نمایش داده می‌شد.
نکته مهمی که در این روش دیده می‌شود اینست که متون به تنهایی و بدون در نظر گرفتن کل مجموعه پردازش می‌شدند و اگر تصمیمی مبنی بر جواب بودن یک متن گرفته می‌شد، آن تصمیم کاملا متکی به همان متن و مستقل از متون دیگر گرفته می‌شد. در آن سال‌ها هیچ توجهی به وابستگی موجود بین متون مختلف و ارتباط بین آنها  نمی‌شد که این مسئله یکی از عوامل پایین بودن دقت جستجو‌ها بشمار می‌رفت.
در ابتدا بر اساس همین دیدگاه  الگوریتم‌ها و روش‌های اندیس گذاری (indexing) پیاده سازی می‌شدند که تنها مشخص می‌کردند یک لغت در یک سند (document) وجود دارد یا خیر. اما با گذشت زمان محققان متوجه ناکارآمدی این دیدگاه در استخراج اطلاعات شدند. به همین دلیل روشی بنام Latent Semantic Indexing که بر پایه Latent Semantic Analysis بنا شده بود به دنیای بازیابی و استخراج اطلاعات معرف شد. کاری که این روش انجام می‌داد این بود که گامی را به مجموعه مراحل موجود در پروسه اندیس گذاری اضافه می‌کرد. این روش بجای آنکه در اندیس گذاری تنها یک متن را در نظر بگیرد و ببیند چه لغاتی در آن آورده شده است، کل مجموعه اسناد را با هم و در کنار یکدیگر در نظر می‌گرفت تا ببیند که چه اسنادی لغات مشابه با لغات موجود در سند مورد بررسی را دارند. به بیان دیگر اسناد مشابه با سند فعلی را به نوعی مشخص می‌نمود.
بر اساس دیدگاه LSI اسناد مشابه با هم، اسنادی هستند که لغات مشابه یا مشترک بیشتری داشته باشند. توجه داشته باشید تنها نمی‌گوییم لغات مشترک بیشتری بلکه از  واژه لغات مشابه نیز استفاده می‌کنیم. چرا که بر اساس LSI دو سند ممکن است هیچ لغت مشترکی نداشته باشند (یعنی لغات یکسان نداشته باشند) اما لغاتی در آنها وجود داشته باشد که به لحاظی معنایی و مفهومی هم معنا و یا مرتبط به هم باشند. بعنوان مثال لغات شش و ریه دو لغت متفاوت اما مرتبط با یکدیگر هستند و اگر دو لغات در دوسند آورده شوند می‌توان حدس زد که ارتباط و شباهتی معنایی بین آنها وجود دارد. به روش هایی که بر اساس این دیدگاه ارائه می‌شوند روش‌های جستجوی معنایی نیز گفته می‌شود. این دیدگاه مشابه دیدگاه انسانی در مواجهه با متون نیز است. انسان هنگامی که دو متن را با یکدیگر مقایسه می‌کند تنها بدنبال لغات یکسان در آن‌ها نمی‌گردد بلکه شباهت‌های معنایی بین لغات را نیز در نظر می‌گیرد این اصل و نگرش پایه و اساس الگوریتم  LSI و همچنین حوزه ای از علم بازیابی اطلاعات بنام مدل سازی موضوعی (Topic Modeling) می‌باشد.
هنگامیکه شما پرس و جویی را بر روی مجموعه ای از اسناد (که بر اساس LSI اندیس گذاری شده‌اند) اجرا می‌کنید، موتور جستجو ابتدا بدنبال لغاتی می‌گردد که بیشترین شباهت را به کلمات موجود در پرس و جوی شما دارند. بعبارتی پرس و جوی شما را بسط می‌دهد (query expansion)، یعنی علاوه بر لغات موجود در پرس و جو، لغات مشابه آنها را نیز به پرس و جوی شما می‌افزاید. پس از بسط دادن پرس و جو، موتور جستجو مطابق روال معمول در سایر روش‌های جستجو، اسنادی که این لغات (پرس و جوی بسط داده شده) در آنها وجود دارند را بعنوان نتیجه به شما باز می‌گرداند. به این ترتیب ممکن است اسنادی به شما بازگردانده شوند که لغات پرس و جوی شما در آنها وجود نداشته باشد اما LSI بدلیل وجود ارتباطات معنایی، آنها را مشابه و مرتبط با جستجو تشخیص داده باشد.  توجه داشته باشید که الگوریتم‌های جستجوی معمولی و ساده، بخشی از اسناد را که مرتبط با پرس و جو هستند، اما شامل لغات مورد نظر شما نمی‌شوند، از دست می‌دهد (یعنی کاهش recall).

برای آنکه با دیدگاه LSI بیشتر آشنا شوید در اینجا مثالی از نحوه عملکرد آن می‌زنیم. فرض کنید می‌خواهیم بر روی مجموعه ای از اسناد در حوزه زیست شناسی اندیس گذاری کنیم. بر مبنای روش LSI چنانچه لغاتی مانند کروموزم، ژن و DNA در اسناد زیادی در کنار یکدیگر آورده شوند (یا بعبارتی اسناد مشترک باهم زیادی داشته باشند)، الگوریتم جستجو چنین برداشت می‌کند که به احتمال زیاد نوعی رابطه معنایی بین آنها وجود دارد. به همین دلیل اگر شما پرس و جویی را با کلمه کلیدی "کروموزوم" اجرا نمایید، الگوریتم علاوه بر مقالاتی که مستقیما واژه کروموزوم در آنها وجود دارد، اسنادی که شامل لغات "DNA" و  "ژن" نیز باشند را بعنوان نتیجه به شما باز خواهد گرداند. در واقع می‌توان گفت الگوریتم جستجو به پرس و جوی شما این دو واژه را نیز اضافه می‌کند که همان بسط دادن پرس و جوی شما است. دقت داشته باشید که الگوریتم جستجو هیچ اطلاع و دانشی از معنای لغات مذکور ندارد و تنها بر اساس تحلیل‌های ریاضی به این نتیجه می‌رسد که در بخش‌های بعدی چگونگی آن را برای شما بازگو خواهیم نمود. یکی از برتری‌های مهم LSI نسبت به روش‌های مبتنی بر کلمات کلیدی (keyword based) این است که در LSI، ما به recall بالاتری دست پیدا می‌کنیم، بدین معنی که از کل جواب‌های موجود برای پرس و جوی شما، جواب‌های بیشتری به کاربر نمایش داده خواهند شد.
یکی از مهمترین نقاط قوت LSI اینست که این روش تنها متکی بر ریاضیات است و هیچ نیازی به دانستن معنای لغات یا پردازش کلمات در متون ندارد. این مسئله باعث می‌شود بتوان این روش را بر روی هر مجموعه متنی و با هر زبانی بکار گرفت. علاوه بر آن می‌توان LSI را بصورت ترکیبی با الگوریتم‌های جستجوی دیگر استفاده نمود و یا تنها متکی بر آن موتور جستجویی را پیاده سازی کرد.
 

نحوه عملکرد Latent Semantic Indexing
در روش LSI مبنا وقوع همزمان لغات در اسناد می‌باشد. در اصطلاح علمی به این مسئله word co-occurrence گفته می‌شود. به بیان دیگر LSI بدنبال لغاتی می‌گردد که در اسناد بیشتری در با هم آورده می‌شوند. پیش از آنکه وارد مباحث ریاضی و محاسباتی LSI شویم بهتر است کمی بیشتر در مورد این مسوله به لحاظ نظری بحث کنیم.
 
لغات زائد
به نحوه صحبت کردن روز مره انسان‌ها دقت کنید. بسیاری از واژگانی که در طول روز و در محاوره‌ها از انها استفاده می‌کنیم، تاثیری در معنای سخن ما ندارند. این مسئله در نحوه نگارش ما نیز صادق است. خیلی از لغات از جمله حروف اضافه، حروف ربط، برخی از افعال پر استفاده و غیره در جملات دیده می‌شوند اما معنای سخن ما در آنها نهفته نمی‌باشد. بعنوان مثال به جمله "جهش در ژن‌ها می‌تواند منجر به بیماری سرطان شود" درقت کنید. در این جمله لغاتی که از اهمیت بالایی بر خوردار هستند و به نوعی بار معنایی جمله بر دوش آنهاست عبارتند از "جهش"، "ژن"، بیماری" و "سرطان". بنابراین می‌توان سایر لغات مانند "در"، "می تواند" و "به" را حذف نمود. به این لغات در اصطلاح علم بازیابی اطلاعات (Information Retrieval) لغات زائد (redundant) گفته می‌شود که در اکثر الگوریتم‌های جستجو یا پردازش زبان طبیعی (natural language processing) برای رسیدن به نتایج قابل قبول باید حذف می‌شوند.روش LSI نیز از این قاعده مستثنی نیست. پیش از اجرای آن بهتر است این لغات زائد حذف گردند. این مسئله علاوه بر آنکه بر روی کیفیت نتایج خروجی تاثیر مثبت دارد، تا حد قابل ملاحظه ای کار پردازش و محاسبات را نیز تسهیل می‌نماید.
 
 
مدل کردن لغات و اسناد
پس از آنکه لغات اضافی از مجموعه متون حذف شد باید بدنبال روشی برای مدل کردن داده‌های موجود در مجموعه اسناد بگردیم تا بتوان کاربر پردازش را با توجه به آن مدل انجام داد. روشی که در LSI برای مدلسازی بکار گرفته می‌شود استفاده از ماتریس لغت – سند (term-document matrix) است. این ماتریس یک گرید بسیار بزرگ است که هر سطر از آن نماینده یک سند و هر ستون از ان نماینده یک لغت در مجموعه متنی ما می‌باشد(البته این امکان وجود دارد که جای سطر و ستون‌ها عوض شود). هر سلول از این ماتریس بزرگ نیز به نوعی نشان دهنده ارتباط بین سند و لغت متناظر با آن سلول خواهد بود. بعنوان مثال در ساده‌ترین حات می‌توان گفت که اگر لغتی در سند یافت نشد خانه متناظر با انها در ماتریس لغت – سند خالی خواهد ماند و در غیر این صورت مقدار یک را خواهد گرفت. در برخی از روش‌ها سلول‌ها را با تعداد دفعات تکرار لغات در اسناد متناظر پر می‌کنند و در برخی دیگر از معیار‌های پیچیده‌تری مانند tf*idf استفاده می‌نمایند. شکل زیر نمونه از این ماتریس‌ها را نشان می‌دهد : 

برای ایجاد چنین ماتریسی باید تک تک اسناد و لغات موجود در مجموعه متنی را پردازش نمود و خانه‌های متناظر را در ماتریس لغت – سند مقدار دهی نمود.خروجی این کار ماتریسی مانند ماتریس شکل بالا خواهد شد (البته در مقیاسی بسیار بزرگتر) که بسیاری از خانه‌های ان صفر خواهند بود (مانند آنچه در شکل نیز مشاهده می‌کنید). به این مسئله تنک بودن (sparseness) ماتریس گفته می‌شود که یکی از مشکلات استفاده از مدل ماتریس لغت – سند محسوب می‌شود. 
این ماتریس، بازتابی از کل مجموعه متنی را به ما می‌دهد. بعنوان مثال اگر بخواهیم ببینیم در سند i چه لغاتی وجود دارد، تنها کافی است به سراغ سطر iام از ماتریس برویم (البته در صورتی که ماتریس ما سند – لغت باشد) وآن را بیرون بکشیم. به این سطر در اصطلاح بردار سند (document vector) گفته می‌شود. همین کار را در مورد لغات نیز می‌توان انجام داد. بعنوان مثال با رفتن به سراغ ستون j ام می‌توان دریافت که لغت j ام  در چه اسنادی آورده شده است. به ستون j ام نیز در ماتریس سند – لغت، بردار لغت (term vector) گفته می‌شود. توجه داشته باشید که این بردار‌ها در مباحث و الگوریتم‌های مربوط به بازیابی اطلاعات و پردازش زبان طبیعی بسیار پر کاربرد می‌باشند.
با داشتن ماتریس لغت – سند می‌توان یک الگوریتم جستجو را پیاده سازی نمود. بسیاری از روش‌های جستجویی که تا کنون پیشنهاد شده اند نیز بر پایه چنین ماتریس هایی بنا شده اند. فرض کنید می‌خواهیم پرس و جویی با کلمات کلیدی "کروموزوم‌های انسان" اجرا کنیم. برای این منظور کافیست ابتدا کلمات کلیدی موجود در پرس و جو را استخراج کرده (در این مثال کروموزوم و انسان دو کلمه کلیدی ما هستند) و سپس به سراغ بردار‌های هر یک برویم. همانطور که گفته شد با مراجعه به سطر یا ستون مربوط به لغات می‌توان بردار لغت مورد نظر را یافت. پس از یافتن بردار مربوط به کروموزوم و انسان می‌توان مشخص کرد که این لغات در چه اسناد و متونی اورده شده اند و آنها را استخراج و به کاربر نشان داد. این ساده‌ترین روش جستجو بر مبنای کلمات کلیدی می‌باشد. اما دقت داشته باشید که هدف نهایی در LSI چیزی فراتر از این است. بنابراین نیاز به انجام عملیاتی دیگر بر روی این ماتریس می‌باشد که بتوانیم بر اساس آن ارتباطات معنایی بین لغات و متون را تشخیص دهیم. برای این منظور LSI ماتری لغت – سند را تجزیه (decompose) می‌کند. برای این منظور نیز از تکنیک Singular Value Decomposition استفاده می‌نماید. پیش از پرداختن به این تکنیک ابتدا بهتر است کمی با فضای برداری چند بعدی (multi-dimensional vector space) آشنا شویم. برای این منظور به مثال زیر توجه کنید.
 
مثالی از فضای چند بعدی
فرض کنید قصد دارید تحقیقی در مورد اینکه مردم چه چیز هایی را معمولا برای صبحانه خود سفارش می‌دهند انجام دهید. برای این منظور در یک روز شلوغ به رستورانی در اطراف محل زندگی خود می‌روید و لیست سفارشات صبحانه را می‌گیرید. فرض کنید از بین اقلام متعدد، تمرکز شما تنها بر روی تخم مرغ (egg)، قهوه (coffee) و بیکن (bacon) است. در واقع قصد دارید ببینید چند نفر در سفارش خود این سه قلم را باهم درخواست کرده اند. برای این منظور سفارشات را تک تک بررسی می‌کنید و تعداد دفعات را ثبت می‌کنید.
پس از آنکه کار ثبت و جمع آوری داده‌ها به پایان رسید می‌توانید نتایج را در قالب نموداری نمایش دهید. یک روش برای اینکار رسم نموداری سه بعدی است که هر بعد آن مربوط به یکی از اقلام مذکور می‌باشد. بعنوان مثال در شکل زیر نموداری سه بعدی را که برای این منظور رسم شده است مشاهده می‌کنید. همانطور که در شکل نشان داده شده است محود x مربوط به "bacon"، محور y مربوط به "egg" و محور z نیز مربوط به "coffee" می‌باشد. از آنجایی که این نمودار سه بعدی است برای مشخص کردن نقاط بر روی آن به سه عدد (x ,y ,z)  نیاز مندیم. حال اطلاعات جمع اوری شده از صورت سفارشات را یکی یکی بررسی می‌کنیم و بر اساس تعداد دفعات سفارش داده شدن این سه قلم نقطه ای  را در این فضای سه بعدی رسم می‌کنیم. بعنوان مثال اگر در سفارشی 2 عدد تخم مرغ و یک قهوه سفارش داده شد بود، این سفارش با (0, 2, 1) در نمودار ما نمایش داده خواهد شد. به این ترتیب می‌توان محل قرار گرفتن این سفارش در فضای سه بعدی سفارشات صبحانه را یافت. این کار را برای تمامی سفارشات انجام می‌دهیم تا سر انجام نموداری مانند نمودار زیر بدست آید. 

دقت داشته باشید که اگر از هریک از نقطه آغازین نمودار (0, 0, 1) خطی را به هر یک از نقاط رسم شده بکشید، بردار هایی در فضای “bacon-eggs-coffee”بدست خواهد آمد. هر کدام از این بردار‌ها به ما نشان می‌دهند که در یک صبحانه خاص بیشتر از کدام یک از این سه قلم درخواست شده است. مجموع بردار‌ها در کنار یکدیگر نیز می‌توانند اطلاعات خوبی راجع به گرایش و علاقه مردم به اقلام مذکور در صبحانه‌های خود به ما دهد. به این نمودار نمودار فضای بردار (vector – space) می‌گویند.
حالا وقت آن است که مجددا به بحث مربوط به بازیابی اطلاعات (information retrieval) باز گردیم. همانطور که گفتیم اسناد در یک مجموعه را می‌توان در قالب بردار هایی بنام Term – vector نمایش داد. این بردار‌ها مشابه بردار مثال قبل ما هستند. با این تفاوت که به جای تعداد دفعات تکرار اقلام موجود در صبحانه افراد، تعداد دفعات تکرار لغات را در یک سند در خود دارند. از نظر اندازه نیز بسیار بزرگتر از مثال ما هستند. در یک مجموعه از اسناد ما هزاران هزار لغت داریم که باید بردار‌های ما به اندازه تعداد کل لغات منحصر به فرد ما باشند. بعنوان مثال اگر در یک مجموعه ما هزار لغات غیر تکراری داریم بردار‌های ما باید هزار بعد داشته باشند. نموداری که اطلاعات را در ان نمایش خواهیم داد نیز بجای سه بعد (در مثال قبل) می‌بایست هزار بعد (یا محور) داشته باشد که البته چنین فضایی قابل نمایش نمی‌باشد.

به مثال صبحانه توجه کنید. همانطور که می‌بینید برخی از نقاط بر روی نمودار نسبت به بقیه به یکدیگر نز دیکتر هستند و ابری از نقاط را در قسمتی از نمودار ایجاد کردند. این نقاط نزدیک به هم باعث می‌شوند که بردار‌های آنها نیز با فاصله نزدیک به هم در فضای برداری مثال ما قرار گیرند. علت نزدیک بودن این بردار‌ها اینست که تعداد دفعات تکرار bacon، eggs و coffee در انها مشابه به هم بوده است. بنابراین می‌توان گفت که این نقاط (یا سفارشات مربوط به انها) به یکدیگر شبیه می‌باشند. در مورد فضای برداری مجموعه از اسناد نیز وضع به همین ترتیب است. اسنادی که لغات مشترک بیشتری با یک دیگر دارند بردار‌های مربوط به انها در فضای برداری در کنار یکدیگر قرار خواهند گرفت. هر چه این مشترکات کمتر باشد منجر به فاصله گرفتن بردار‌ها از یکدیگر می‌گردد. بنابراین می‌بینید که با داشتن فضای برداری و مقایسه بردار‌ها با یکدیگر می‌توان نتیجه گرفت که دو سند چقدر به یکدیگر شباهت دارند.
در بسیاری از روش‌های جستجو از چنین بردار هایی برای یافتن اسناد مرتبط به پرس و جوی کاربران استفاده می‌کنند. برای ان منظور تنها کافی اس پرس و جوی کاربر را بصورت برداری در فضای برداری مورد نظر نگاشت دهیم و سپس بردار حاصل را با بردار‌های مربوط به اسناد مقایسه کنیم و در نهایت آنهایی که بیشترین شباهت را دارند باز به کاربر بازگردانیم. این روش یکی از ساده‌ترین روش‌های مطرح شده در بازیابی اطلاعات است.
خوب حالا بیایید به Latent Semantic Indexing باز گردیم. روش LSI برمبنای همین فضای برداری عمل می‌کند با این تفاوت که فضای برداری را که دارای هزاران هزار بعد می‌باشد به فضای کوچکتری با ابعاد کمتر (مثلا 300 بعد) تبدیل می‌کند. به این کار در اصطلاح عملی کاهش ابعاد (dimensionality reduction) گفته می‌شود. دقت داشته باشید که هنگامیکه این عمل انجام می‌گیرد لغاتی که شباهت و یا ارتباط زیادی به لحاظ معنایی با یکدیگر دارند بجای اینکه هریک در قالب یک بعد نمایش داده شوند، همگی بصورت یک بعد در می‌آیند. بعنوان مثال لغات کروموزم و ژن از نظر معنایی با یکدیگر در ارتباط هستند. در فضای برداری اصلی این دو لغت در قالب دو بعد مجزا نمایش داده می‌شوند اما با اعمال کاهش ابعاد به ازای هر دوی آنها تنها یک بعد خواهیم داشت. مزیت این کار اینست که اسنادی که لغات مشترکی ندارند اما به لحاظ معنایی با یکدیگر ارتباط دارند در فاضی برداری کاهش یافته نزدیکی بیشتری به یکدیگر خواهند داشت.
 
روش‌های مختلفی برای اعمال کاهش ابعاد وجود دارد. در LSI از روش Singular Value Decompistion استفاده می‌شود که در بحث بعدی در مورد آن صحبت خواهیم نمود.
 
 
Singular Value Decomposition
پیشتر گفتیم که در LSI برای مدل کردن مجموعه اسناد موجود از ماتریس بزرگی بنام ماتریس لغت – سند استفاده می‌شود. این ماتریس در واقع نمایشی از مدل فضای برداری است که در بخش قبلی به آن اشاره شد. دقت داشته باشید که ما در دنیای واقعی در یک سیستم بزرگ تقریبا چیزی در حدود یک ملیون سند داریم که در مجموع این اسناد تقریبا صد هزار لغت غیر تکراری و منحصر به فرد یافت می‌شود. بنابراین می‌توان گفت میزان تنک بودن ماتریس ما تقریبا برابر با 0.1 درصد خواهد بود. یعنی از کل ماتریس تنها 0.1 درصد آن دارای اطلاعات است و اکثر سلول‌های ماتریس ما خالی می‌باشد. این مسئله را در شکل زیر می‌توانید مشاهده کنید. 

در Latent Semantic Indexing با استفاده از روش Singular Value Decomposition این ماتریس را کوچک می‌کنند. به بیان بهتر تقریبی از ماتریس اصلی را ایجاد می‌کنند که ابعاد کوچکتری خواهد داشت. این کار مزایایی را بدنبال دارد. اول آنکه سطر‌ها و ستون هایی (لغات و اسناد) که اهمیت کمی در مجموعه اسناد ما دارند را حذف می‌کند. علاوه بر آن این کار باعث می‌شود که ارتباطات معنایی بین لغات هم معنی یا مرتبط کشف شود. یافتن این ارتباطات معنایی بسیار در پاسخ به پرس و جو‌ها مفید خواهد بود. چرا که مردم معمولا در پرس و جو‌های خود از دایره لغات متفاوتی استفاده می‌کنند. بعنوان مثال برای جستجو در مورد مطالب مربوط به ژن‌های انسان برخی از واژه کروموزوم و برخی دیگر از واژه ژنوم و دیگران ممکن است از واژگان دیگری استفاده نمایند. این مسئله مشکلی را در جستجو بنام عدم تطبیق کلمات کلیدی (mismatch problem) بوجود می‌اورده که با اعمال SVD بر روی ماتریس سند – لغت این مشکل برطرف خواهد شد.
توجه داشته باشید که SVD ابعاد بردار‌های لغات و سند را کاهش می‌دهد. بعنوان مثال بجای آنکه یک سند در قالب صد هزار بعد (که هر بعد مربوط به یک لغت می‌باشد) نمایش داده شود، بصورت یک بردار مثلا 150 بعدی نمایش داده خواهد شد. طبیعی است که این کاهش ابعاد منجر به از بین رفتن برخی از اطلاعات خواهد شد چرا که ما بسیاری از ابعاد را با یکدیگر ادغام کرده ایم. این مسئله شاید در ابتدا مسئله ای نا مطلوب به نظر آید اما در اینجا نکته ای در آن نهفته است. دقت داشته باشید که آنچه از دست می‌رود اطلاعات زائد (noise) می‌باشد. از بین رفتن این اطلاعات زائد منجر می‌شود تا ارتباطات پنهان موجود در مجموعه اسناد ما نمایان گردند. با اجرای SVD بر روی ماتریس، اسناد و لغات مشابه، مشابه باقی می‌مانند و انهایی که غیر مشابه هستند نیز غیر مشابه باقی خواهد ماند. پس ما از نظر ارتباطات بین اسناد و لغات چیزی را از دست نخواهیم داد.
 
در مباحث بعدی در مورد چگونگی اعمال SVD و همچنین نحوه پاسخگویی به پرس و جو‌ها مطالب بیشتری را برای شما عزیزان خواهیم نوشت.
 
موفق و پیروز باشید. 
مطالب دوره‌ها
معرفی پروژه NotifyPropertyWeaver
پس از معرفی مباحث IL Code Weaving و همچنین ارائه راه حلی در مورد «استفاده از AOP Interceptors برای حذف کدهای تکراری INotifyPropertyChanged در WPF» راه حل مشابهی به نام NotifyPropertyWeaver ارائه شده است که همان کار AOP Interceptors را انجام می‌دهد؛ اما بدون نیاز به تشکیل پروکسی و سربار اضافی. کار نهایی را توسط ویرایش اسمبلی و افزودن کدهای IL لازم انجام می‌دهد؛ البته بدون استفاده از PostSharp. این پروژه از کتابخانه سورس باز پایه‌ای به نام Fody استفاده می‌کند که جهت IL Code weaving طراحی شده است.
اگر به Wiki آن مراجعه نمائید، لیست افزونه‌های قابل توجهی را در مورد آن خواهید یافت که PropertyChanged تنها یکی از آن‌ها است.


پیشنیازها
الف) صفحه پروژه در GitHub
ب) دریافت از طریق نوگت


روش استفاده

پس از نصب بسته نوگت پروژه PropertyChanged.Fody
 PM> Install-Package PropertyChanged.Fody
کلاسی را که باید پس از کامپایل، پیاده سازی‌های خودکار OnPropertyChanged را شامل شود، با ویژگی ImplementPropertyChanged مزین کنید.
using PropertyChanged;

namespace AOP02
{
    [ImplementPropertyChanged]
    public class Person
    {
        public string Id { set; get; }
        public string Name { set; get; }
    }
}
و سپس پروژه را کامپایل نمائید. خروجی کنسول Build در VS.NET :
------ Build started: Project: AOP02, Configuration: Debug x86 ------
  Fody (version 1.13.6.1) Executing
  Finished Fody 287ms.
  AOP02 -> D:\Prog\AOP02\bin\Debug\AOP02.exe
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
اکنون اگر فایل اسمبلی نهایی پروژه را در برنامه ILSpy باز کنیم، چنین پیاده سازی را می‌توان شاهد بود:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace AOP02
{
    public class Person : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Id
        {
            [System.Runtime.CompilerServices.CompilerGenerated]
            get
            {
                return this.<Id>k__BackingField;
            }
            [System.Runtime.CompilerServices.CompilerGenerated]
            set
            {
              if (string.Equals(this.<Id>k__BackingField, value, System.StringComparison.Ordinal))
              {
                  return;
              }
              this.<Id>k__BackingField = value;
              this.OnPropertyChanged("Id");
            }
        }
        public string Name
        {
            [System.Runtime.CompilerServices.CompilerGenerated]
            get
            {
               return this.<Name>k__BackingField;
            }
            [System.Runtime.CompilerServices.CompilerGenerated]
            set
           {
             if (string.Equals(this.<Name>k__BackingField, value, System.StringComparison.Ordinal))
             {
                return;
             }
             this.<Name>k__BackingField = value;
             this.OnPropertyChanged("Name");
            }
        }
        public virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
مطالب
خواندنی‌های 12 تیر

اس کیوال سرور

امنیت

توسعه وب

دات نت فریم ورک

دبلیو سی اف

دبلیو پی اف و سیلور لایت

سایت‌های ایرانی

شیرپوینت

لینوکس

متفرقه

محیط‌های مجتمع توسعه

مرورگرها

مسایل انسانی، اجتماعی و مدیریتی برنامه نویسی

پی اچ پی

نظرات مطالب
مبانی TypeScript؛ ماژول‌ها
درود جناب نصیری ، من یک مشکلی که دارم و الان یک ساعته که دارم R&D میکنم و موفق به حلش نشدم متاسفانه این هست که یک فایل جدید ts درست در روت پروژه درست کردم ( کلا پروزه شامل دو فایل app.ts و testmd.ts هست ) فایل testmd  یک کلاس رو تعریف کرده و اون رو export کرده و در  app اون رو import کردم و سعی دارم از اون کلاس استفاده کنم و درواقع خواستم این مطلب رو تست کنم و یک ماژول آزمایشی درست کنم و ازش استفاده کنم . هر کاری کردم پروژه بیلد نمیشه و خطای  Error TS2307 Cannot find module 'testmd'  در تب Error نمایش داده میشه . هر چی هم جستجو کردم به نتیجه ای نرسیدم واقعا. 
فایل پروژه هم اتچ کردم ، اگر ممکنه راهنمایی کنید خیلی ممنون میشم . 
محیط توسعه :
+ Visual Studio 2015 Update 2
+ آخرین نگارش Resharper که متناسب با نسخه TypeScript که 1.8 هست تنظیمش کردم ( چک کردم و نسخه‌های دیگه TypeScript  رو نصب ندارم)
+ Web Essentials 2015.3 v3.0.230 ، Web Extension Pack v1.4.44 ، Web Analyzer v1.7.77 
با تشکر 
مطالب
معرفی برنامه‌ی Subtitle Tools

این روزها زیرنویس‌های فارسی فیلم‌های روز دنیا را راحت می‌شود در اینترنت یافت، اما مشکلات زیادی هم به همراه این نوع فایل‌ها وجود دارند:
- گاها با فیلم دریافت شده هماهنگ نیستند.
- عموما با فرمت windows-1256 تهیه می‌شوند که برای استفاده از آن‌ها در سیستم‌های مختلف بهتر است به UTF8 تبدیل شوند.
- اکثر برنامه‌های موجود برای کار با زیر نویس‌ها و ویرایش آن‌ها، درکی از یونیکد ندارند.
- عموما نیاز است جهت استفاده از آن‌ها در یک جمع، تعدادی از سطور آن‌ها را با حفظ شماره بندی فایل، حذف کرد!
و ...

به همین جهت نیاز به یک برنامه‌ی جمع و جور جهت کار با زیر نویس‌ها داشتم که نتیجه‌ی آن تهیه‌ی برنامه‌ی زیر شد:

الف) تغییر encoding فایل دریافتی به UTF-8
هنگام گشودن اکثر فایل‌های زیر نویس فارسی با تصویر زیر روبرو خواهید شد:



برای تبدیل آن به فرمت یونیکد تنها کافی است بر روی دکمه‌ی To UTF-8 کلیک کنید. در این حالت نتیجه به صورت زیر خواهد بود:



ب) هماهنگ سازی زیر نویس با فیلم دریافتی
ممکن است دیالوگ‌های زیر نویس دریافتی شما به اندازه‌ی مدت زمان خاصی، از دیالوگ‌های فیلم در حال پخش، عقب‌تر یا جلوتر باشند. در این حالت ابتدا باید این مدت زمان را با گوش فرا دادن به ابتدای فیلم و مقایسه‌ی آن با اولین شماره‌ی زیرنویس، پیدا کنید. مثلا پس از بررسی متوجه شده‌اید که به کلیه زمان‌های زیرنویس جاری باید 7 ثانیه اضافه گردد. اینکار را مطابق تصویر ذیل می‌توان انجام داد:



ج) یکی کردن زمان‌های دو زیر نویس با هم
گاهی از اوقات مشکلات زمانی یک زیرنویس وخیم‌تر از مورد ب است. به این معنا که شماره‌های مختلف آن، زمان‌های کاملا بی‌ربطی داشته و به صورت هماهنگ، قابل افزایش یا کاهش نیستند. در این حالت ابتدا زیر نویس مورد نظر را در برنامه باز کنید. سپس یک زیر نویس خوب انگلیسی هماهنگ با فیلم را نیز پیدا کنید و از طریق دکمه‌ی Merge times به برنامه معرفی نمائید. در این حالت به صورت خودکار زمان‌های صحیح از زیر نویس دوم به زیر نویس اول منتقل خواهند شد.

د) یکی کردن دو فایل زیر نویس با هم
ممکن است فیلم دریافتی تنها از یک فایل تشکیل شده باشد، اما شما فقط توانسته‌اید زیر نویس مرتبط با نگارش دو سی دی این فیلم را پیدا کنید. بنابراین نیاز است تا این دو فایل زیر نویس یکی شوند با این شرط که زمان‌های سی دی دوم از انتهای سی دی اول در فایل نهایی یکی شده‌ی تولیدی شروع گردد.
برای این منظور ابتدا فایل زیرنویس سی دی اول را در برنامه باز کرده و سپس به گزینه‌ی join files در برنامه مراجعه کنید. در اینجا ابتدا مسیر فایل زیرنویس سی دی 2 را مشخص نمائید. برنامه سعی خواهد کرد تا زمان آغاز قسمت دوم را بر اساس آخرین زمان سی دی اول و اولین زمان سی دی دوم حدس بزند و نمایش دهد. یا می‌توانید این مقدار پیش فرض را پذیرفته و بر روی دکمه‌ی join کلیک نمائید و یا امکان تغییر دستی آن هم میسر است.



ه) حذف و یا ویرایش ردیف‌ها
به دلایل نامشخصی اشخاصی که سعی در ترجمه‌ی زیرنویس‌ها می‌کنند عموما علاقمندند که به متن اصلی وفادار بمانند. به همین دلیل نیاز به جرح و تعدیل زیرنویس‌های تهیه شده برای نمایش در یک جمع خانوادگی وجود دارد. گزینه‌ی جستجوی موجود در بالای ردیف‌های زیرنویس باز شده در برنامه، امکان نمایش ردیف‌هایی را که حاوی متن وارد شده است، به صورت خودکار دارد.



در این حالت اگر نیاز است سطری حذف شود، آن‌را انتخاب نموده و بر روی دکمه‌ی Delete row کلیک نمائید. در این حالت علاوه بر حذف ردیف، کلیه شماره‌های موجود در زیر نویس نیز به صورت خودکار مجددا تولید و مرتب خواهند شد. (کلیک راست بر روی هر ردیف نیز این گزینه‌ را نمایش می‌دهد)
یا اگر مشغول به ویرایش متنی شدید، پس از ویرایش، کلیک کردن بر روی دکمه‌ی Save را فراموش نکنید (در حالت حذف نیازی به اینکار نیست).

ز) پیدا کردن زیرنویس یک فایل بر اساس امضای دیجیتال آن
سایت opensubtitles.org یک API را جهت پیدا کردن زیرنویسی مطابق با هش یک فایل ویدیویی ارائه داده است، که در برنامه‌ی جاری، کلاینتی برای آن تهیه شده است:



فقط کافی است فایل ویدیویی خود را در این قسمت انتخاب نمائید. برنامه هش فایل را محاسبه کرده و سپس با کمک سرویس XML-RPC سایت opensubtitles.org سعی در یافتن زیرنویس هماهنگ با آن خواهد کرد. در اینجا دیگر مهم نیست نام فایل انتخابی چیست؛ امضای دیجیتال آن مهم است. برای دریافت موارد مورد نظر، ابتدا آن‌ها را تیک زده و سپس بر روی دکمه‌ی دریافت کلیک کنید. کلیک راست بر روی ردیف مورد نظر نیز این امکان دریافت را لحاظ کرده است.
همچنین قسمتی هم برای آپلود زیرنویس به این سایت پیش بینی شده است (لطفا مصرف کننده‌ی صرف یا به قولی لیچر نباشید!)



در اینجا انتخاب فایل ویدیویی، فایل زیرنویس هماهنگ با آن و همچنین زبان زیر نویس الزامی است. از فایل ویدیویی جهت محاسبه‌ی هش آن برای یافتن ساده‌تر زیرنویس‌ها در دفعات آتی استفاده می‌گردد.

پیشنیاز استفاده از این برنامه، نصب دات نت فریم ورک 4 است که اگر از ویندوز 7 استفاده می‌کنید، جزو به روز رسانی‌های اختیاری آن است و در حالت کلی نسخه‌ی کامل 32 بیتی و 64 بیتی آن از این آدرس قابل دریافت است.


نظرات مطالب
پَرباد - راهنمای اتصال و پیاده‌سازی درگاه‌های پرداخت اینترنتی (شبکه شتاب)
نکته : در زمانی که ذخیره و بازیابی اطلاعات توسط روش مورد نظر  انجام می‌شود پس از Verify کردن متد Update فراخوای می‌شود.
به نظرم باید متد Insert فراخوانی شود که لاگ روند تغییرات رو داشته باشیم که بتونیم به کاربر نمایش دهیم در این حالت اطلاعاتی مثل Status بروزرسانی می‌شود و دیگر نمی‌توان اطلاعات روند قبلی رو نمایش داد.
به عنوان مثال :
1. ارسال درخواست
2. انصراف از خرید
3. تایید درخواست
باتوجه به یکتا بودن شماره درخواست باید در زمان ارسال مجدد درخواست ، هم فیلد Id و هم فیلد orderNumber همانند به صورت درخواست اولیه باشد یعنی فیلد Id جدید و OrderNumber هم براساس شماره درخواست جدید باشد.
نظر شما در این مورد چیست؟
نکته دیگر اینکه با توجه به یکتا بودن شماره سفارش تابع SelectByOrderNumberAsync  عملا همیشه مقدار null برمیگرداند
فیلد Message که در فایل XML موجود هست در مدل موجود نمی‌باشد.
نظرات مطالب
تولید SiteMap استاندارد و ایجاد یک ActionResult اختصاصی برای Return کردن SiteMap تولید شده
سلام؛ عالی بود. من برای خواندن از بانک اطلاعاتی این کد رو نوشتم
 public virtual ActionResult Sitemap()
        {
            var data = new sunn.Models.ApplicationDbContext().Posts.ToList();
            SiteMap sm = new SiteMap();
            foreach (var siteno in data)
            {
                sm.Add(new Location()
                   {
                       Url = string.Format("http://www.MySite.ir/Develop/Home/Post/{0}", siteno.Id),
                       LastModified = siteno.InsertDate,
                       Priority = 0.5D
                   });
            }

            return new XmlResult(sm);
        }
زمانیکه در آدرس بار نام این اکشن رو میزنم، تمامی اطلاعات رو نشون میده؛ اما بدون هیچ فرمتی و پشت سر هم. اما ViewSource رو که میزنم توی source کاملا نقشه سایت رو نشون میده .

دو تا سوال : اول اینکه به چه نحوی میتونم آدرس فایل xml  را بدست بیارم که مثلاً به گوگل معرفی کنم و دوم اینکه به جای نمایش این صفحه ناخوانا بتوانم یک صفحه خوانا‌تر با فرمت نمایش بدم
مطالب
Portable Class Library چیست و چگونه از آن استفاده کنیم؟
Portable Class Library چیست؟
Portable Class Library برای تولید Assembly مدیریت شده که قابیلت استفاده در اکثر تکنولوژی‌ها نظیر (WP7(Windows Phone 7 و WPF و Silverlight و Windows Store App و حتی XBox را داراست استفاده می‌شود.  این نوع پروژه‌ها میزان استفاده مجدد از کد‌ها رو به حداکثر ممکن می‌رسونه و در عوض تعداد پروژه‌های مورد نیاز رو به حداقل. مورد اصلی استفاده از اون‌ها برای تولید Multi-Targeted Application هاست. برای مثال در تولید پروژه‌های تحت Windows که با تکنولوژی WPF پیاده سازی می‌شوند پروژه ViewModel و  Model رو با این تکنولوژی پیاده سازی می‌کنند تا در آینده اگر نیاز بود قسمتی از پروژه با استفاده از Silverlight یا Windows Store App انجام بشه بتونیم Model , ViewModel نوشته شده رو به این پروژه‌ها Reference بدیم. تا قبل از برای انجام این کار باید این دو بخش رو دوباره برای هر تکنولوژی بازنویسی می‌کردیم.
روش استفاده
زمانی که یک پروژه از نوع Portable Class Library رو ایجاد می‌کنیم باید حداقل 2 یا چند تا از Platform‌های مورد نظر رو انتخاب کنیم. به صورت زیر :


بعد از انتخاب گزینه Portable Class Library و زدن کلید Enter  فرم زیر ظاهر خواهد شد که در این قسمت باید Platform‌های مورد نظر رو انتخاب کنیم.


در اینجا من 2 Platform رو انتخاب کردم. یکی Silverlight و Net 4.5. یعنی این Portable Class Library هم می‌تونه به پروژه Silverlight و هم به Class library .Net 4.5 رفرنس داده بشه.
حتما می‌پرسید چه طوری؟
در واقع Microsoft برای انجام این کار فقط یک سری از Reference‌های مشترک بین این 2 Platform رو به پروژه اضافه میکنه و همین موضوع باعث شده کار با این پروژه‌ها نیاز به یکم مهارت داشته باشه.
برای مثال اگر قصد استفاده از تکنولوژی Async&Await  رو دارید باید یکم به خودتون زحمت بدید و CTP مورد نظر رو نصب کنید.(حالا اگر از Net4 استفاده می‌کنید که باید از یک روش دیگه استفاده کنید که در یک مقاله جداگانه براتون توضیح خواهم داد).
به جدول زیر دقت کنید.
 Xbox 360  Windows Phone Silverlight 
Windows Store 
.NET Framework 
 Feature 
 
 
 √  √  
 Core 
  
 √  √  √  LINQ 
 Only 7.5 
 √  √  
 IQueryable 
   
 
 Only 4.5  Dynamic keyword 
   √  
 √  Managed Extensibility Framework (MEF) 
  √  √  
 √  Network Class Library (NCL) 
  
 √  
 √  Serialization 
  
 √  
 √  Windows Communication Foundation (WCF) 
  √  √  
  Only 4.5 
 
 Model-View-View Model (MVVM) 
   
 
 Only 4.0.3 and 4.5  Data annotations 
 √  √  
 
 Only 4.0.3 and 4.5  XLINQ 

در جدول بالا به صورت کامل مشخص شده که در هر پلاتفرم چه چیزی Support میشه. برای مثال Data Annotation فقط در Net4.3 , 4.5  قابل استفاده است.
اسمبلی‌های زیر در یک Portable Class Library  قابل استفاده هستند.
  • mscorlib.dll

  • System.dll

  • System.Core.dll

  • System.Xml.dll

  • System.ComponentModel.Composition.dll

  • System.Net.dll

  • System.Runtime.Serialization.dll

  • System.ServiceModel.dll

  • System.Xml.Serialization.dll

  • System.Windows.dll  - From Silverlight

در صورتی که از VS2010 استفاده می‌کنید می‌تونید از این جا Portable Library Tools رو دانلود کنید. بعد از نصب دقیقا همین مراحل رو برای ایجاد پروژه انجام بدید.
پیاده سازی یک مثال عملی
در این مثال قصد دارم یک کلاس برای مدیریت تاریخ شمسی درست کنم. مراحل زیر رو دنبال کنید
 یک Portable Class Library  به نام Common به پروژه اضافه کنید.
یک کلاس به نام PersianDate به برنامه اضافه کرده به صورت زیر
 public class PersianDate<TCalendar> where TCalendar : System.Globalization.Calendar
    {
        public PersianDate()
        {
            this.CurentCalendar = System.Activator.CreateInstance<TCalendar>();
        }

        public TCalendar CurentCalendar 
        {
            get;
            private set;
        }

        public string GetDate()
        {
            return string.Format( "{0}/{1}/{2}", this.CurentCalendar.GetYear( DateTime.Today ).ToString( "####0000" )
                , this.CurentCalendar.GetMonth( DateTime.Today ).ToString( "##00" )
                , this.CurentCalendar.GetDayOfMonth( DateTime.Today ).ToString( "##00" ) );
        }
    }
کلاس بالا به صورت Generic تعریف شده که شما باید هنگام استفاده حتما یک نوع Calendar رو بهش بدید. دلیل این کار اینه که کلاس PersianCalendar در Portable Class Library وجود ندارد و فقط یک کلاس پایه Calendar در فضای نام System.Globalization وجود داره.

حالا یک پروژه از نوع Console Application به برنامه اضافه کنید و یک Reference  به پروژه Common بدید و بعد کلاس زیر رو بنویسید.
public class CustomPersianDate
    {
        public CustomPersianDate()
        {

        }

        public static Common.PersianDate<System.Globalization.PersianCalendar> PersianCalendar
        {
            get
            {
                return _persianCalendar ?? ( _persianCalendar = new Common.PersianDate<System.Globalization.PersianCalendar>() );
            }
        }
        private static Common.PersianDate<System.Globalization.PersianCalendar> _persianCalendar;
    }
در این قسمت ما نوع TCalendar رو از نوع PersianCalendar تعیین کردیم.

حالا یک پروژه از نوع Silverlight به برنامه اضافه کنید و یک Reference  به پروژه Common بدید و بعد کلاس بالا بدون تغییر بنویسید.
نمای کلی پروژه باید به صورت زیر باشد.

بعد پروژه ConsoleApplication  رو به عنوان پروژه StartUp انتخاب کنید و فایل Program  رو به صورت زیر تغییر بدید.
class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine( "Today Is ?{0}", CustomPersianDate.PersianCalendar.GetDate() );

            Console.ReadLine();
        }
    }

خوب نتیجه به صورت زیر خواهد بود:

برای پروژه Silverlight هم نتیجه قطعا به همین صورت است.

همان طور که دید انتخاب نوع Calendar به خود Application‌ها واگذار شد و شما می‌تونید هر نوع تقویم رو به عنوان TCalendar تعیین کنید.

امیدوارم شما هم مثل من به این تکنولوژی دلچسب علاقه پیدا کرده باشید.





مطالب
نمایش، ذخیره و چاپ فایل‌های PDF در برنامه‌های Angular
با توجه به اینکه فایل‌های PDF نیز فایل باینری هستند، کلیات نکات مطلب «دریافت و نمایش تصاویر از سرور در برنامه‌های Angular» در مورد آن‌ها هم صادق است. در اینجا به تکمیل این نکات پرداخته و مواردی را مانند ذخیره، چاپ و استفاده از اشیاء نمایشی <object>، <embed> و <iframe> نیز بررسی می‌کنیم. نمایش PDF در اینجا بر اساس امکانات توکار مرورگرها صورت می‌گیرد و نیاز به افزونه‌ی اضافه‌تری ندارد.


کدهای سمت سرور دریافت فایل PDF

در اینجا کدهای سمت سرور برنامه، نکته‌ی خاصی را به همراه نداشته و صرفا یک فایل PDF ساده (محتوای باینری) را بازگشت می‌دهد:
using Microsoft.AspNetCore.Mvc;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class ReportsController : Controller
    {
        [HttpGet("[action]")]
        public IActionResult GetPdfReport()
        {
            return File(virtualPath: "~/assets/sample.pdf",
                        contentType: "application/pdf",
                        fileDownloadName: "sample.pdf");
        }
    }
}
که در نهایت با آدرس api/Reports/GetPdfReport در سمت کلاینت قابل دسترسی خواهد بود.


سرویس دریافت محتوای باینری در برنامه‌های Angular

برای اینکه HttpClient برنامه‌های Angular بتواند محتوای باینری را بجای محتوای JSON پیش‌فرض آن دریافت کند، نیاز است نوع خروجی سمت سرور آن‌را به blob تنظیم کرد:
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { HttpClient } from "@angular/common/http";

@Injectable()
export class DownloadPdfDataService {

  constructor(private httpClient: HttpClient) { }

  public getReport(): Observable<Blob> {
    return this.httpClient.get("/api/Reports/GetPdfReport", { responseType: "blob" });
  }
}
به این ترتیب پس از اشتراک به متد getReport این سرویس، اطلاعات باینری این فایل PDF را دریافت خواهیم کرد.


اصلاح Content Security Policy سمت سرور جهت ارائه‌ی محتوای blob

پس از دریافت فایل PDF به صورت یک blob، با استفاده از متد URL.createObjectURL می‌توان آدرس موقت محلی را برای دسترسی به آن تولید کرد و یک چنین آدرس‌هایی به صورت blob:http تولید می‌شوند. در این حالت در Content Security Policy سمت سرور، نیاز است امکان دسترسی به تصاویر و همچنین اشیاء از نوع blob را نیز آزاد معرفی کنید:
img-src 'self' data: blob:
default-src 'self' blob:
object-src 'self' blob:
در غیراینصورت مرورگر نمایش یک چنین تصاویر و یا اشیایی را سد خواهد کرد.


دریافت فایل‌های PDF از سرور و نمایش آن‌ها در یک برنامه‌ی Angular

پس از این مقدمات، کامپوننتی که یک فایل PDF را از سمت سرور دریافت کرده و نمایش می‌دهد، چنین کدی را خواهد داشت:
import { DownloadPdfDataService } from "./../download-pdf-data.service";
import { WindowRefService } from "./../../core/window.service";
import { Component, OnInit } from "@angular/core";
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";

@Component({
  templateUrl: "./view-pdf.component.html",
  styleUrls: ["./view-pdf.component.css"]
})
export class ViewPdfComponent implements OnInit {
  private nativeWindow: Window;
  private pdfBlobUrl: string;
  sanitizedPdfBlobResourceUrl: SafeResourceUrl;

  constructor(private downloadService: DownloadPdfDataService,
    private windowRefService: WindowRefService, private sanitizer: DomSanitizer) { }

  ngOnInit() {
    this.nativeWindow = this.windowRefService.nativeWindow;
    this.downloadService.getReport().subscribe(pdfDataBlob => {
      console.log("pdfDataBlob", pdfDataBlob);
      const urlCreator = this.nativeWindow.URL;
      this.pdfBlobUrl = urlCreator.createObjectURL(pdfDataBlob);
      console.log("pdfBlobUrl", this.pdfBlobUrl);
      this.sanitizedPdfBlobResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.pdfBlobUrl);
    });
  }
}
با این قالب:
<h1>Display PDF Files</h1>

<div *ngIf="sanitizedPdfBlobResourceUrl">
  <h4>using iframe</h4>
  <iframe width="100%" height="600" [attr.src]="sanitizedPdfBlobResourceUrl" type="application/pdf"></iframe>
  <h4>using object</h4>
  <object [attr.data]="sanitizedPdfBlobResourceUrl" type="application/pdf" width="100%"
    height="100%"></object>
  <h4>usin embed</h4>
  <embed [attr.src]="sanitizedPdfBlobResourceUrl" type="application/pdf" width="100%"
    height="100%">
</div>
- در اینجا در ngOnInit، به سرویس پنجره دسترسی یافته و وهله‌ای از آن‌را جهت کار با متد createObjectURL شیء URL آن دریافت می‌کنیم.
- سپس مشترک متد getReport دریافت فایل PDF شده و اطلاعات نهایی آن‌را به صورت pdfDataBlob دریافت می‌کنیم.
- این اطلاعات باینری را به متد createObjectURL ارسال کرده و آدرس موقتی این تصویر را در مرورگر بدست می‌آوریم.
- چون در این حالت Angular این URL را امن سازی می‌کند، یک چنین خروجی unsafe:blob بجای blob تولید خواهد شد که نمایش این مورد نیز توسط مرورگر سد می‌شود. برای رفع این مشکل، می‌توان از سرویس DomSanitizer آن که به سازنده‌ی کلاس تزریق شده‌است استفاده کرد:
this.sanitizedPdfBlobResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.pdfBlobUrl);
تفاوت این مورد با حالت نمایش تصویر، استفاده از متد bypassSecurityTrustResourceUrl بجای متد bypassSecurityTrustUrl است. از این جهت که اشیاء یاد شده نیاز به SafeResourceUrl دارند و نه SafeUrl.
اینبار یک چنین انتسابی به صورت مستقیم کار می‌کند که سه نمونه‌ی این انتساب را به اشیاء iframe ،object و embed، در قالب فوق مشاهده می‌کنید.



افزودن دکمه‌ی چاپ PDF به برنامه

پس از اینکه به this.pdfBlobUrl دسترسی یافتیم، اکنون می‌توان یک iframe مخفی را ایجاد کرد، سپس src آن‌را به این آدرس ویژه تنظیم نمود و در آخر متد print آن‌را فراخوانی کرد که سبب نمایش خودکار دیالوگ چاپ مرورگر می‌شود:
  printPdf() {
    const iframe = document.createElement("iframe");
    iframe.style.display = "none";
    iframe.src = this.pdfBlobUrl;
    document.body.appendChild(iframe);
    iframe.contentWindow.print();
  }


نمایش فایل PDF در یک برگه‌ی جدید

اگر علاقمند بودید تا این فایل PDF را به صورت تمام صفحه و در برگه‌ای جدید نمایش دهید، می‌توان از متد window.open استفاده کرد:
  showPdf() {
    this.nativeWindow.open(this.pdfBlobUrl);
  }


دریافت فایل PDF

بجای نمایش فایل PDF می‌توان دکمه‌ای را بر روی صفحه قرار داد که با کلیک بر روی آن، این فایل توسط مرورگر به صورت متداولی جهت دریافت به کاربر ارائه شود:
  downloadPdf() {
    const fileName = "test.pdf";
    const anchor = document.createElement("a");
    anchor.style.display = "none";
    anchor.href = this.pdfBlobUrl;
    anchor.download = fileName;
    document.body.appendChild(anchor);
    anchor.click();
  }
در اینجا یک anchor جدید به صورت مخفی به صفحه اضافه می‌شود که href آن به this.pdfBlobUrl تنظیم شده‌است. سپس متد click آن فراخوانی خواهد شد. نام این فایل را هم توسط ویژگی download این شیء می‌توان تنظیم نمود.
این روش در مورد تدارک دکمه‌ی دریافت تمام blobهای دریافتی از سرور کاربرد دارد و منحصر به فایل‌های PDF نیست.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات اشتراک‌ها
آیا WPF مرده است؟!
بسته به اینکه شما مرده رو چی در نظر بگیرید مسئله فرق دارد. 
1- اینکه احتما نسخه بعدی وجود ندارد.(احتمالا)
2- جایگزینی بهتری برای آن وجود دارد(100% خیر){ولی احتمالا تا چند وقت دیگر می‌آید زیاد است. چون مایکروسافت قصد دارد تغییراتی را در برنامه‌های توسعه ای خود ایجاد کند که با یک کد برای همه پلت فرم‌ها کد نویسی شود. به نظر من برنامه ای شبیه ویندوز استو با امکانات کامل بزودی جایگزین خواهد شد که با توجه به تکنلوژی Xaml  برای wpf کار‌ها کار سویچ سخت نخواهد شد}