- معماری لایه ای
- استفاده از جدیدترین نسخه ASP.NET Identity
- به روز رسانی لایه IocConfig برای یک برنامه ASP.NET Core با توجه به بخش روش تعویض IoC Container توکار ASP.NET Core با StructureMap و https://github.com/structuremap/StructureMap.Microsoft.DependencyInjection جهت استفاده از تنظیمات StructureMap در یک لایه مجزا (IocConfig) با کانفیگهای مختص برای آخرین نگارش MVC و Web API و SignalR و Entity Framework .
هدف بررسی کامل مباحث Streaming در دات نت فریمورک میباشد.
Stream چیست؟
دنبالهای از بایتها که میتوان آنها را از یک backing store (انبار پشتیبان) خواند یا در آن نوشت.
Backing Store
یک رسانه ذخیره سازی از جمله Disk-Drive، Memory و Network Location میباشد که به عنوان منبع یا مقصدی برای خواندن و نوشتن بایتها به صورت دنبالهای، میتوان از آن استفاده کرد.
زمانی که قرار است داده ذخیره شده به صورت Stream مصرف شود، مزیت مقیاس پذیری را نیز خواهید داشت. لذا لازم نیست با مشکل محدودیت حافظه نیز درگیر شوید.
آشنایی با معماری Streaming در دات نت
Streaming در دات نت، توسط سه مفهوم: backing store، decorators و adapters در برگرفته شده است.
کلاسی به نام Stream در دات نت، برای ارائه یکسری متد مشترک برای Reading، Writing و Positioning در نظر گرفته شده است که همچنین کلاس پایه Backing Store Streams و Decorator Streams نیز میباشد.
اعضای کلاس Stream را میتوان به شکل زیر گروه بندی کرد:
در نظر داشته باشید که Stream ها، دارای اشاره گری به مکان جاری تحت عنوان Pointer نیز میباشند. مقدار پیش فرض آن «صفر» میباشد و زمانی که شروع به خواندن از Stream کنید، این خواندن از مکانی شروع میشود که Pointer به آنجا اشاره میکند. به شکل زیر توجه کنید:
اگر قرار باشد 3 بایت اول خوانده شود، لذا حالت زیر را خواهیم داشت:
همانطور که مشخص است، Pointer مربوط به Stream به اولین خانهای اشاره میکند که در Readهای بعدی قرار است خوانده شود. در نهایت با خواندن دو بایت دیگر، حالت زیر را خواهیم داشت:
برای Reading و Writing متدهای زیر در کلاس System.IO.Stream در نظر گرفته شدهاند:
(Read(byte[] buffer,int offset,int count
buffer: آرایهای از بایتها برای نگهداری دادهی خوانده شده از Stream
offset: برخلاف تصور، اندیسی است که مکان شروع ذخیره سازی در buffer را مشخص میکند و نه مکان شروع خواندن از Stream
count: بیشترین تعداد بایت برای خواندن از Stream میباشد. با توجه به اینکه ممکن است به انتهای Stream رسیده باشیم یا اینکه در شرایطی مثلا در Network Streamها چه بسا خود Stream تصمیم بگیرد تعداد بایت کمتری از این مقدار Count را برای ما ارائه دهد. از این رو همیشه مقداری که برای Count مشخص میکنید همان مقداری نیست که متد Read برای شما برگشت خواهد داد.
return: تعداد بایتهایی که خوانده شده است یا اگر به انتهای Stream رسیده باشیم «0» برگشت خواهد داد. از این رو تکه کد زیر برای خواندن کل داده به یکباره، قابل اطمینان نخواهد بود.
byte[] dataToRead=new byte[stream.Length]; int bytesRead=stream.Read(dataToRead,0,dataToRead.Length);
راه حل جایگزین میتواند به شکل زیر باشد:
static byte[] ReadBytes(Stream stream) { // dataToRead will hold the data read from the stream byte[] dataToRead = new byte[stream.Length]; //this is the total number of bytes read. this will be incremented //and eventually will equal the bytes size held by the stream int totalBytesRead = 0; //this is the number of bytes read in each iteration (i.e. chunk size) int chunkBytesRead = 1; while (totalBytesRead < dataToRead.Length && chunkBytesRead > 0) { chunkBytesRead = stream.Read(dataToRead, totalBytesRead, dataToRead.Length - totalBytesRead); totalBytesRead = totalBytesRead + chunkBytesRead; } return dataToRead; }
byte[] data = new BinaryReader (s).ReadBytes (1000);
return: یک بایت را از مکان فعلی که Pointer به آن اشاره میکند، میخواند. اگر خروجی «-1» باشد، به انتهای Stream رسیده اید.
برخلاف انتظار، خروجی این متد از نوع int میباشد؛ چرا که لازم است «-1» را نیز در برگیرد.
با توجه به حالت FileStream که فقط برای Append کردن وهله سازی شده است، امکان خواندن را نخواهید داشت. بنابراین زمانیکه از کلاس شخص ثالثی برای خواندن از Stream استفاده میکنید، بهصلاح است (به منظور Defensive Programming) که از متد CanRead قبل خواندن بهره ببرید.
(Write(byte[] array,int offset,int count
array: آرایه ای از بایتها که قرار است در Stream درج شوند.
offset: اندیس شروع array برای درج کردن در Stream را مشخص میکند.
count: بیشترین تعداد بایتی که از array در Stream درج خواهد شد.
برای درج یک بایت در Stream استفاده میشود.
برای تشخص پشتیبانی کردن Stream از عملیات درج کردن مورد استفاده قرار خواهد گرفت.
با انجام هر یک از عملیات Read و Write برروی Stream، باعث تغییر مکان Pointer مربوط به آن خواهید شد. در صورتیکه نیاز است به صورت انتخابی مکان خاصی از Stream را برای شروع درج کردن یا خواندن انتخاب کنید، Seeking کمک کننده خواهد بود.
باید توجه داشت که پشتیبانی از این عملیات به backing store مورد استفاده وابسته میباشد. از این رو باید دانست که MemoryStream و FileStream از Seeking پشتیبانی کرده ولی در مقابل NetworkStream، PipeStream و همچنین Decorator Streams به غیر از BufferedStream قابلیت Seeking را ندارند. BufferedStream با ایجاد پوششی برروی یک Stream به اصطلاح non-seekable، امکان Seeking درون Buffer داخلی خود را مهیا خواهد کرد.
برای عملیات Seeking نیز اعضایی در کلاس پایه System.IO.Stream در نظر گرفته شده است:
برای تنظیم مکان Pointer در Stream استفاده خواهد شد.
متدی برای تنظیم طول Stream، که اگر value ارسال شده کوچکتر از طول فعلی Stream باشد، آن را کوتاه کرده و در غیر این صورت، Stream موردنظر گسترش خواهد یافت. برای استفاده از این متد، Stream مورد نظر باید قابلیت Writing و Seeking را داشته باشد.
پراپرتی فقط خواندنی که طول Stream را مشخص میکند. در صورتیکه Stream مورد نظر Seekable باشد، میتوان از این پراپرتی بهر برد؛ این بدین معنی است که اگر با یک Stream از نوع non-seekable کار میکنید، در صورت استفاده از این خصوصیت، تمام بایتهای Stream خوانده شده و بعد از قرار گرفتن در یک buffer (به عنوان مثال در memory)، محاسبه خواهد شد.
Position
پراپرتی برای خواندن یا تنظیم مکان فعلی Pointer مربوط به Stream، میباشد. برای استفاده از آن لازم است Stream مورد استفاده Seekable باشد.
مشخص میکند که Stream مورد استفاده Seekable می باشد یا خیر.
به طور خلاصه با استفاده از متد Seek انعطاف پذیری بالایی خواهید داشت. با مقدار دهی پراپرتی Position، این مقدار همیشه نسبت به ابتدای Stream در نظر گرفته خواهد شد (شکل زیر)؛ این در حالی است که با استفاده از متد Seek میتوان مشخص کرد که مقدار Offset تنظیم شده نسبت به ابتدا، مکان جاری و یا انتهای Stream میباشد.
مثال:
using (FileStream fs = File.Create(@"C:\files\testfile3.txt")) { // position is 0 long pos = fs.Position; // sets the position to 1 fs.Position = 1; byte[] arrbytes = { 100, 101 }; //writes the content of arrbytes into current position - which is 1 fs.Write(arrbytes, 0, arrbytes.Length); //position is now 3 as its advanced by write pos = fs.Position; fs.Position = 0; byte[] readdata1 = ReadBytes(fs); }
Closing and Flushing
کلاس پایه System.IO.Stream اینترفیس IDisposable را پیاده سازی کرده است؛ لذا بهتر است برای آزاد سازی منابع از جمله: file handle در FileStream یا socket handle در NetworkStream، بعد از استفاده، متد Dispose آنها را فراخوانی کنید یا با وهله سازی آنها در بدنه using، این فراخوانی به صورت ضمنی انجام شود.
نکته: باید توجه کنید که با Close (معادل Dispose) شدن decorator streamها ، backing store stream داخلی آنها نیز Close خواهد شد.
با توجه به اینکه I/O عملیات پرهزینهای میباشد، برخی از انواع Streamها به منظور بهبود کارآیی از یک مکانیزم بافر داخلی استفاده میکنند. به این شکل که عملیات Write، داده را به جای آنکه درون backing store ذخیره سازی کند، درون این بافر ذخیره سازی خواهد کرد. زمانیکه این بافر پر شود یا به صورت صریح متدهای Flush یا Close فراخوانی شده باشند، داده موجود در بافر درون backing store ذخیره خواهد شد. در نتیجه عملیات Read هم میتواند به بخشی از داده اصلی که هم اکنون درون بافر میباشد، دسترسی سریعتری داشته باشد. به عنوان مثال FileStream از این مکانیزم داخلی برخوردار است. سایز پیش فرض این بافر 4KB (قابل تنظیم است) میباشد. برای سایر مواردی که این امکان برایشان وجود ندارد، میتوان از BufferedStream برای Decorate کردن Stream مورد نظر خود استفاده کرد.
نکته: به صورت پیش فرض، Streamها thread-safe نیستند و امکان خواندن و نوشتن همزمان توسط چند thread برروی یک stream مشترک را نخواهید داشت. برای حل این موضوع، متد استاتیکی در کلاس Stream تحت عنوان Synchronized در نظر گرفته شده است که یک thread-safe wrapper را به برروی stream ورودی در نظر گرفته و آن را به عنوان خروجی برگشت خواهد داد.
[HostProtection(SecurityAction.LinkDemand, Synchronization = true)] public static Stream Synchronized(Stream stream) { if (stream == null) throw new ArgumentNullException("stream"); if (stream is Stream.SyncStream) return stream; return (Stream) new Stream.SyncStream(stream); }
WhatsApp for Windows Phone is one of the few apps on Windows 10 Mobile today that continues to receive frequent updates from its developer. Unfortunately, the app itself is one based on Silverlight, which is what apps built for Windows Phone 8.1 used back in 2014. This means the app isn't a Universal Windows Platform app (UWP,) and as such doesn't run across all the different Windows 10 platforms and devices available today.
بررسی OLAP
واژه OLAP در اوایل سالهای 1990 شکل گرفت. E.F.Codd بنیانگذار مدل دادهی رابطهای، این واژه را در فرهنگ نامه کاربران بانکهای اطلاعاتی توصیف نمود.مشابه یک بانک اطلاعاتی رابطهای که شامل تعدادی جدول میباشد، یک بانک اطلاعاتی OLAP شامل تعدادی Cube است. هر Cube مجموعه ای از Dimensionها و Measure هاست. Dimension یک شیء تحلیلی است که محورهای مختصات را برای پرسشهای تحلیلی تعریف میکند و از Member هایی تشکیل شده است که Member هر Dimension در قالب سلسله مراتب میتواند تعریف شود؛ در حالیکه Measure یک مقدار عددی است که در مختصات Cube تعریف میشود که این مقادیر از جداول تراکنشی بدست میآید (جدول Fact) که جزئیات هر رکورد تراکنشی در آنها ذخیره میشود. Measureها حاوی اطلاعاتی هستند که از پیش، محاسبات تجمیعی بر روی آنها براساس سلسله مراتب تعریف شده در Dimension انجام شده است.
ساختار OLAP شبیه به یک مکعب روبیک از دادهها است که میتوان آنرا در جهات مختلف چرخانید تا بتوان سناریوهای «قبلا چه شده» و «چه میشد اگر ...» را بررسی نمود. مدل چند بعدی OLAP طریقه نمایش دادن دادهها را در مقایسه با بانکهای اطلاعاتی رابطهای تسهیل میکند. غالبا OLAP دادهها را از یک انباره داده استخراج میکند.
ابزارهای OLAP را به چند دسته تقسیم میکنند:
OLAP رو میزی:
ابزارهای ساده و مستقل که روی کامپیوترهای شخصی نصب شده و مکعبهای کوچکی میسازند و آنها را نیز بر روی سیستم به شکل فایل ذخیره میکنند. بیشتر این ابزارها با صفحات گسترده ای نظیر Excel کار میکنند. به این ترتیب کسانی که در سفر هستند قادر به استفاده از این دسته از محصولات هستند. (در حال حاضر Web OLAP در حال جایگزین کردن این محصولات است)MOLAP:
بجای ذخیره کردن اطلاعات در رکوردهای کلید دار، این دسته از ابزارها، بانکهای اطلاعاتی خاصی را برای خود طراحی کردهاند؛ بطوری که دادهها را به شکل آرایههای مرتب شده بر اساس ابعاد داده ذخیره میکنند. در حال حاضر نیز دو استاندارد برای این نوع ابزار وجود دارد. سرعت این ابزار بالا و سایز بانک اطلاعاتی آن نسبتا کوچک است.ROLAP:
این ابزارها با ایجاد یک بستر روی بانکهای رابطهای اطلاعات را ذخیره و بازیابی میکنند. بطوری که اساس بهینه سازی برخی بانکهای مانند Red Brick ،MicreoStrategy و ... بر همین اساس استوار است. اندازه بانک اطلاعاتی این ابزار قابل توجه میباشد.HOLAP:
در اینجا منظور از hybrid ترکیبی از MOLAP و ROLAP است. ابزار دارای بانک اطلاعاتی بزرگ و راندمان بالاتر نسبت به ROLAP میباشد.مقایسه گزینههای ذخیره سازی در OLAP:
MOLAP:
این نوع ذخیرهسازی بیشترین کاربرد در ذخیره اطلاعات را دارد. همچنین به صورت پیش فرض جهت ذخیرهسازی اطلاعات انتخاب شده است. در این نوع تنها زمانی دادههای منتقل شده به Cube به روز میشوند که Cube پردازش شود و این امر باعث تاخیر بالا در پردازش و انتقال دادهها میشود.ROLAP:
در ذخیرهسازی ROLAP زمان انتقال بالا نیست که از مزایای این نوع ذخیرهسازی نسبت به MOLAP است. در ROLAP اطلاعات و پیشمحاسبهها در یک حالت رابطهای ذخیره میشوند و این به معنای زمان انتقال نزدیک به صفر میان منبع داده (بانک اطلاعاتی رابطهای) و Cube میباشد. از معایب این روش میتوان به کارایی پایین آن اشاره کرد زیرا زمان پاسخ برای پرسوجوهای اجرا شده توسط کاربران طولانی است. دلیل این کارایی پایین بکار نبردن تکنیکهای ذخیرهسازی چند بعدی است.HOLAP:
این نوع ذخیرهسازی چیزی مابین دو حالت قبلی است. ذخیره اطلاعات با روش ROLAP انجام میشود، بنابراین زمان انتقال تقزیبا صفر است. از طرفی برای بالابردن کارایی، پیشمحاسبهها به صورت MOLAP انجام میگیرد در این حالت SSAS آماده است تا تغییری در اطلاعات مبداء رخ دهد و زمانی که تغییرات را ثبت کرد نوبت به پردازش مجدد پیشمحاسبهها میشود. با این نوع ذخیرهسازی زمان انتقال دادهها به Cube را نزدیک به صفر و زمان پاسخ برای اجرای کوئریهای کاربر را زمانی بین نوع ROLAP و MOLAP میرسانیم.این سه روش ذخیرهسازی انعطافپذیری مورد نیاز را برای اجرای پروژه فراهم میکند. انتخاب هر یک از این روشها به نوع پروژه، حجم دادهها و ... بستگی دارد. در پایان میتوان نتیجه گرفت که بهتر است زمان پردازش طولانیتری داشته باشیم تا اینکه کاربر نهایی در هنگام ایجاد گزارشات زمان زیادی را منتظر بماند.
بررسی داده کاوی
حجم زیاد اطلاعات، مدیران مجموعهها را در تحلیل و یافتن اطلاعات مفید دچار چالش کرده است. داده کاوی، ابزار مناسب برای تجزیه و تحلیل اطلاعات و کشف و استخراج روابط پنهان در مجموعههای دادهای سنگین را فراهم میکند. گروه مشاورهای گارتنر داده کاوی را استخراج نیمه اتوماتیک الگوها، تغییرات، وابستگیها، نابهنجاریها و دیگر ساختارهای معنی دار آماری از پایگاههای بزرگ داده تعریف میکند. داده کاوی، تلاشی برای یافتن قوانین، الگوها و یا میل احتمالی داده به مُدلی، در بین انبوهی از دادهها است.
داده کاوی فرآیندی پیچیده جهت شناسایی الگوها و مدلهای صحیح، جدید و به صورت بالقوه مفید، در حجم وسیعی از داده میباشد؛ به طریقی که این الگوها و مدلها برای انسانها قابل درک باشند. داده کاوی به صورت یک محصول قابل خریداری نمیباشد، بلکه یک رشته علمی و فرآیندی است که بایستی به صورت یک پروژه پیاده سازی شود.
به بیانی دیگر داده کاوی، فرآیند کشف الگوهای پنهان، جالب توجه، غیر منتظره و با ارزش از داخل مجموعه وسیعی از دادههاست و فعالیتی در ارتباط با تحلیل دقیق دادههای سنگین بی ساختار است که علم آمار ناتوان از تحلیل آنهاست. بعضی مواقع دانش کشف شده توسط داده کاوی عجیب به نظر میرسد؛ مثلا ارتباط افراد دارای کارت اعتباری و جنسیت با داشتن دفترچه تامین اجتماعی یا سن، جنسیت و درآمد اشخاص با پیش بینی خوش حسابی او در بازپرداخت اقساط وام. داده کاوی در حوزههای تصمیم گیری، پیش بینی، و تخمین مورد استفاده قرار میگیرد.
پایه و اساس این تکنیک، ریشه در علوم زیر دارد:
- علم آمار و احتمال
- کامپیوتر (تکنولوژی اطلاعات)
- هوش مصنوعی (تکنیکهای یادگیری ماشین)
ارتباط داده کاوی و OLAP
OLAP و داده کاوی فن آوریهای تحلیلی در خانواده BI به شمار میآیند. OLAP در زمینه تجمیع مقادیر عظیم دادههای تراکنشی بر پایه تعاریف ابعادی مناسب است.
سوالات موضوعی که در ادامه به آن اشاره میشود توسط OLAP پاسخ داده میشوند:
-
مقدار فروش کل تولیدات در سه ماهه گذشته در یک منطقه بخصوص چقدر بوده است؟
-
کدامیک از محصولات جزء ده محصول پر فروش تمامی فروشگاهها در ماه گذشته بودند؟
-
کدامیک از محصولات برای مشتریان زن و مشتریان مرد فروش قابل توجهی داشته است؟
-
تفاوت میزان فروش روزانه در هنگام تبلیغات در مقایسه با دوره زمانی عادی چیست؟
فن آوری OLAP بر پایه محاسبات تجمیعی است. سرویس دهنده OLAP نوع خاصی از سرویس دهندهی بانک اطلاعاتی محسوب میگردد که با دادههای چند بعدی سروکار دارد. بسیاری از مشکلات و مخاطرات نظیر ایندکس گذاری، ذخیره سازی دادهها و ... که در RDBMSها وجود دارد در سرویس دهندهی OLAP نیز وجود دارد.
داده کاوی در یافتن الگوهای پنهان از یک مجموعه داده توسط تحلیل همبستگی میان مقادیر مشخصهها مناسب است.
تکنیکهای داده کاوی دو گونه هستند: نظارت شده و نظارت نشده. در داده کاوی نظارت شده کاربر میبایست مشخصهی هدف و مجموعه دادهی ورودی را تعیین نماید. الگوریتمهای داده کاوی نظارت شده شامل درخت تصمیم، نیو بیز و شبکههای عصبی هستند. تکنیکهای داده کاوی نظارت نشده نیازی به تعیین مشخصهی قابل پیش بینی ندارد. خوشه بندی مثال خوبی از داده کاوی نظارت نشده میباشد و به گروه بندی نقاط داده ای ناهمگن به زیر گروه هایی میپردازد که در آنها نقاط داده ای کم و بیش مشابه و همگن هستند.
در زیر نمونه ای از سوالات پاسخ داده شده توسط داده کاوی ارائه شده است:
-
مشخصات مشتریانی که تمایل به خرید جدیدترین مدل را دارند، چیست؟
-
چه کالاهایی باید به این دسته از مشتریان خاص توصیه و پیشنهاد گردد؟
-
برآورد میزان فروش مدلی خاص در سه ماهه آینده چیست؟
-
چگونه باید مشتریان را تقسیم بندی کرد؟
یکی از فرآیندهای اصلی داده کاوی، تحلیل همبستگی میان مشخصهها و مقادیر آنها است. محققین آمار در این موارد قرنها مطالعه داشتهاند. OLAP و داده کاوی دو فن آوری مختلف هستند اما فعالیتهای یکدیگر را تکمیل میکنند. OLAP فعالیت هایی نظیر خلاصه سازی، تحلیل تغییرات در طول زمان و تحلیلهای What If را پشتیبانی مینماید. همچنین میتوان آنرا برای تحلیل نتایج داده کاوی در سطوح مختلف و مجزا استفاده کرد. داده کاوی نیز میتواند در ساخت Cubeهای مفیدتر سودمند باشد.
تفاوت میان OLAP و داده کاوی ارتباطی به تفاوت میان دادههای تلخیص شده و دادههای تشریحی ندارد. در واقع تمایز قابل توجهی میان مدل سازی توصیفی و تشریحی وجود دارد. توابع و الگوریتم هایی که معمولاً در ابزارهای OLAP یافت میشود، توابع مدل سازی توصیفی به شمار میآیند. در حالیکه توابعی که در آنچه که اصطلاحاً بسته داده کاوی نامیده میشود، یافت میشود توابع یا الگوهای مدل سازی تشریحی هستند.
الگوریتمهای داده کاوی موجود در SSAS و زمینه کاری متناظر
این الگوریتمها را به 5 دسته تقسیم میتوان نمود:
پیش بینی توالی وقایع
برای مثال جهت تجزیه و تحلیل مجموعه ای از شرایط آب و هوایی که منجر به وقوع پدیده خاصی میشود. از الگوریتم زیر استفاده میشود:
Microsoft Sequence Clustering Algorithm
یافتن گروهی از موارد مشترک در تراکنش ها
معروفترین مثال در خصوص تجزیه و تحلیل سبد بازار است. از الگوریتمهای زیر استفاده میشود:Microsoft Association Algorithm
Microsoft Decision Trees Algorithm
یافتن گروهی از موارد مشابه
معمولترین کاربرد زمینه بخش بندی دادههای مشتریان به منظور یافتن گروههای مجزا از مشتریان است. از الگوریتمهای زیر استفاده میشود:Microsoft Clustering Algorithm
Microsoft Sequence Clustering Algorithm
پیش بینی صفات گسسته
به عنوان مثال، پیش بینی اینکه یک مشتری خاص، تمایلی به خرید محصول جدید دارد یا خیر. از الگوریتمهای زیر استفاده میشود:Microsoft Decision Trees Algorithm
Microsoft Naive Bayes Algorithm
Microsoft Clustering Algorithm
Microsoft Neural Network Algorithm
پیش بینی صفات پیوسته
پیش بینی درآمد در ماه آینده مثالی از آن میباشد. از الگوریتمهای زیر استفاده میشود:Microsoft Decision Trees Algorithm
Microsoft Time Series Algorithm
Roslyn #5
همانطور که از قسمت قبل بهخاطر دارید، برای دسترسی به اطلاعات semantics، نیاز به یک context مناسب که همان Compilation API است، میباشد. این context دارای اطلاعاتی مانند دسترسی به تمام نوعهای تعریف شدهی توسط کاربر و متادیتاهای ارجاعی، مانند کلاسهای پایهی دات نت فریمورک است. بنابراین پس از ایجاد وهلهای از Compilation API، کار با فراخوانی متد GetSemanticModel آن ادامه مییابد. در ادامه با مثالهایی، کاربرد این متد را بررسی خواهیم کرد.
ساختار جدید Optional
خروجیهای تعدادی از متدهای Roslyn با ساختار جدیدی به نام Optional ارائه میشوند:
public struct Optional<T> { public bool HasValue { get; } public T Value { get; } }
دریافت مقادیر ثابت Literals
فرض کنید میخواهیم مقدار ثابت ; int x = 42 را دریافت کنیم. برای اینکار ابتدا باید syntax tree آن تشکیل شود و سپس نیاز به یک سری حلقه و if و else و همچنین بررسی نال بودن بسیاری از موارد است تا به نود مقدار ثابت 42 برسیم. سپس متد GetConstantValue مربوط به GetSemanticModel را بر روی آن فراخوانی میکنیم تا به مقدار واقعی آن که ممکن است در اثر محاسبات جاری تغییر کرده باشد، برسیم.
اما روش بهتر و توصیه شده، استفاده از CSharpSyntaxWalker است که در انتهای قسمت سوم معرفی شد:
class ConsoleWriteLineWalker : CSharpSyntaxWalker { public ConsoleWriteLineWalker() { Arguments = new List<ExpressionSyntax>(); } public List<ExpressionSyntax> Arguments { get; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { var member = node.Expression as MemberAccessExpressionSyntax; var type = member?.Expression as IdentifierNameSyntax; if (type != null && type.Identifier.Text == "Console" && member.Name.Identifier.Text == "WriteLine") { if (node.ArgumentList.Arguments.Count == 1) { var arg = node.ArgumentList.Arguments.Single().Expression; Arguments.Add(arg); return; } } base.VisitInvocationExpression(node); } }
در ادامه نحوهی استفادهی از این SyntaxWalker را ملاحظه میکنید. در اینجا ابتدا سورس کدی حاوی یک سری Console.WriteLine که دارای تک آرگومانهای ثابتی هستند، تبدیل به syntax tree میشود. سپس از روی آن CSharpCompilation تولید میگردد تا بتوان به اطلاعات semantics دسترسی یافت:
static void getConstantValue() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { Console.WriteLine(3.14); Console.WriteLine(""qux""); Console.WriteLine('c'); Console.WriteLine(null); Console.WriteLine(x * 2 + 1); } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Analyze the constant argument (if any). foreach (var arg in walker.Arguments) { var val = model.GetConstantValue(arg); if (val.HasValue) { Console.WriteLine(arg + " has constant value " + (val.Value ?? "null") + " of type " + (val.Value?.GetType() ?? typeof(object))); } else { Console.WriteLine(arg + " has no constant value"); } } }
خروجی نمایش داده شدهی توسط برنامه به صورت ذیل است:
3.14 has constant value 3.14 of type System.Double "qux" has constant value qux of type System.String 'c' has constant value c of type System.Char null has constant value null of type System.Object x * 2 + 1 has no constant value
درک مفهوم Symbols
اینترفیس ISymbol در Roslyn، ریشهی تمام Symbolهای مختلف مدل سازی شدهی در آن است که تعدادی از آنها را در تصویر ذیل مشاهده میکنید:
API کار با Symbols بسیار شبیه به API کار با Reflection است با این تفاوت که در زمان آنالیز کدها رخ میدهد و نه در زمان اجرای برنامه. همچنین در Symbols API امکان دسترسی به اطلاعاتی مانند locals, labels و امثال آن نیز وجود دارد که با استفاده از Reflection زمان اجرای برنامه قابل دسترسی نیستند. برای مثال فضاهای نام در Reflection صرفا به صورت رشتهای، با دات جدا شده از نوعهای آنالیز شدهی توسط آن است؛ اما در اینجا مطابق تصویر فوق، یک اینترفیس مجزای خاص خود را دارد. جهت سهولت کار کردن با Symbols، الگوی Visitor با معرفی کلاس پایهی SymbolVisitor نیز پیش بینی شدهاست.
static void workingWithSymbols() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { // #insideBar } } class Qux { protected int Baz { get; set; } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse enclosing symbol hierarchy. var cursor = code.IndexOf("#insideBar"); var barSymbol = model.GetEnclosingSymbol(cursor); for (var symbol = barSymbol; symbol != null; symbol = symbol.ContainingSymbol) { Console.WriteLine(symbol); } // Analyze accessibility of Baz inside Bar. var bazProp = ((CompilationUnitSyntax)root) .Members.OfType<ClassDeclarationSyntax>() .Single(m => m.Identifier.Text == "Qux") .Members.OfType<PropertyDeclarationSyntax>() .Single(); var bazSymbol = model.GetDeclaredSymbol(bazProp); var canAccess = model.IsAccessible(cursor, bazSymbol); }
Foo.Bar(int) Foo <global namespace> Demo.exe Demo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
همچنین در ادامهی کد، توسط متد IsAccessible قصد داریم بررسی کنیم آیا Symbol قرار گرفته در محل کرسر، دسترسی به خاصیت protected کلاس Qux را دارد یا خیر؟ که پاسخ آن خیر است.
آشنایی با Binding symbols
یکی از مراحل کامپایل کد، binding نام دارد و در این مرحله است که اطلاعات Symbolic هر نود از Syntax tree دریافت میشود. برای مثال در اینجا مشخص میشود که این x، آیا یک متغیر محلی است، یا یک فیلد و یا یک خاصیت؟
مثال ذیل بسیار شبیه است به مثال getConstantValue ابتدای بحث، با این تفاوت که در حلقهی آخر کار از متد GetSymbolInfo استفاده شدهاست:
static void bindingSymbols() { // Get the syntax tree. var code = @" using System; class Foo { private int y; void Bar(int x) { Console.WriteLine(x); Console.WriteLine(y); int z = 42; Console.WriteLine(z); Console.WriteLine(a); } }"; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Bind the arguments. foreach (var arg in walker.Arguments) { var symbol = model.GetSymbolInfo(arg); if (symbol.Symbol != null) { Console.WriteLine(arg + " is bound to " + symbol.Symbol + " of type " + symbol.Symbol.Kind); } else { Console.WriteLine(arg + " could not be bound"); } } }
x is bound to int of type Parameter y is bound to Foo.y of type Field z is bound to z of type Local a could not be bound
ایجاد متغیرها در Rust
در Rust، متغیرها را میتوان با استفاده از کلمه کلیدی let و به دنبال آن، نام متغیر و مقدار اختصاص داده شدهی به آن ایجاد کرد. مثلا:
let x = 10;
let x: i32 = 10;
متغیرهای تغییرپذیر (Mutable) و تغییرناپذیر (Immutable) در Rust
در Rust، متغیرها به طور پیش فرض تغییر ناپذیر هستند؛ به این معنا که پس از تخصیص، مقدار آنها قابل تغییر نیست؛ مثلا:
let x = 10; x = 20; //compile-time error
let mut x = 10; x = 20;
Scope متغیرها در Rust
متغیرها در Rust دارای دامنه خاصی هستند که توسط curly braces که بیانیه آنها را احاطه کردهاند، تعریف میشود. مثلا
{ let x = 10; } // این متغیر خارج از این محدوده در دسترس نخواهد بود
به این دلایل، به طور کلی توصیه میشود که از متغیرهای global به مقدار کم در Rust استفاده کنید. در عوض، اغلب بهتر است از متغیرهایی استفاده شود که در تابع یا بلوکی که در آن مورد استفاده قرار میگیرند، تعریف و scope شدهاند. این مورد میتواند درک رفتار برنامه را آسانتر کند و از عوارض جانبی ناخواسته ناشی از متغیرهای سراسری جلوگیری کند.
Shadowing Variables in Rust
Shadowing یک مفهوم برنامه نویسی است که به شما امکان میدهد یک متغیر را در یک scope، دوبار اعلام کنید و به طور موثر متغیر اصلی را با متغیر جدیدی به همین نام shadow کنید. وقتی متغیری را shadow میکنید، متغیر جدید، متغیر قبلی را در scope داخلی، "shadow" میکند و هر ارجاعی به این متغیر در آن محدوده، به متغیر جدید اشاره میکند.
fn main() { let x = 5; println!("The value of x is: {}", x); // خروجی برابر 5 است let x = "hello"; println!("The value of x is: {}", x); // خروجی برابر hello }
سایه زدن زمانی میتواند مفید باشد که بخواهید مقدار، یا نوع یک متغیر را در یک scope، بدون اینکه نام آن را تغییر دهید. همچنین میتواند کد را با استفادهی مجدد از یک نام متغیر، برای اهداف مختلف خواناتر کند. با این حال، همچنین میتواند کد را پیچیدهتر و درک آن را سختتر کند؛ بنابراین باید با دقت و با دلیل موجه از آن استفاده کنید .
مراحل کار به این صورت هستند:
بارگذاری تصویر و چرخش آن در صورت نیاز
ابتدا تصویر بارکد دار را بارگذاری کرده و آنرا تبدیل به یک تصویر سیاه و سفید میکنیم:
// load the image and convert it to grayscale var image = new Mat(fileName); if (rotation != 0) { rotateImage(image, image, rotation, 1); } if (debug) { Cv2.ImShow("Source", image); Cv2.WaitKey(1); // do events } var gray = new Mat(); var channels = image.Channels(); if (channels > 1) { Cv2.CvtColor(image, gray, ColorConversion.BgrToGray); } else { image.CopyTo(gray); }
تشخیص گرادیانهای افقی و عمودی
یکی از روشهای تشخیص بارکد، استفاده از روشی است که در تشخیص خودرو قسمت 16 بیان شد. تعداد زیادی تصویر بارکد را تهیه و سپس آنها را به الگوریتمهای machine learning جهت تشخیص و یافتن محدودهی بارکد موجود در یک تصویر، ارسال کنیم. هرچند این روش جواب خواهد داد، اما در این مورد خاص، قسمت بارکد، شبیه به گرادیانی از رنگها است. کتابخانهی OpenCV برای یافتن این نوع گرادیانها دارای متدی است به نام Sobel :
// compute the Scharr gradient magnitude representation of the images // in both the x and y direction var gradX = new Mat(); Cv2.Sobel(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0, ksize: -1); //Cv2.Scharr(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0); var gradY = new Mat(); Cv2.Sobel(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1, ksize: -1); //Cv2.Scharr(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1); // subtract the y-gradient from the x-gradient var gradient = new Mat(); Cv2.Subtract(gradX, gradY, gradient); Cv2.ConvertScaleAbs(gradient, gradient); if (debug) { Cv2.ImShow("Gradient", gradient); Cv2.WaitKey(1); // do events }
ابتدا درجهی شدت گرادیانها در جهتهای x و y محاسبه میشوند. سپس این شدتها از هم کم خواهند شد تا بیشترین شدت گرادیان موجود در محور x حاصل شود. این بیشترین شدتها، بیانگر نواحی خواهند بود که احتمال وجود بارکدهای افقی در آنها بیشتر است.
کاهش نویز و یکی کردن نواحی تشخیص داده شده
در ادامه میخواهیم با استفاده از متدهای تشخیص کانتور (قسمت 12)، نواحی با بیشترین شدت گرادیان افقی را پیدا کنیم. اما تصویر حاصل از قسمت قبل برای اینکار مناسب نیست. به همین جهت با استفاده از متدهای کار با مورفولوژی تصاویر، این نواحی گرادیانی را یکی میکنیم (قسمت 8).
// blur and threshold the image var blurred = new Mat(); Cv2.Blur(gradient, blurred, new Size(9, 9)); var threshImage = new Mat(); Cv2.Threshold(blurred, threshImage, thresh, 255, ThresholdType.Binary); if (debug) { Cv2.ImShow("Thresh", threshImage); Cv2.WaitKey(1); // do events } // construct a closing kernel and apply it to the thresholded image var kernel = Cv2.GetStructuringElement(StructuringElementShape.Rect, new Size(21, 7)); var closed = new Mat(); Cv2.MorphologyEx(threshImage, closed, MorphologyOperation.Close, kernel); if (debug) { Cv2.ImShow("Closed", closed); Cv2.WaitKey(1); // do events } // perform a series of erosions and dilations Cv2.Erode(closed, closed, null, iterations: 4); Cv2.Dilate(closed, closed, null, iterations: 4); if (debug) { Cv2.ImShow("Erode & Dilate", closed); Cv2.WaitKey(1); // do events }
ابتدا با استفاده از متد Threshold، تصویر را به یک تصویر باینری تبدیل خواهیم کرد. در این تصویر تمام نقاط دارای شدت رنگ کمتر از مقدار thresh، به مقدار حداکثر 255 تنظیم میشوند.
سپس با استفاده از متدهای تغییر مورفولوژی تصویر، قسمتهای مجاور به هم را میبندیم و یکی میکنیم. این مورد در یافتن اشیاء احتمالی که ممکن است بارکد باشند، بسیار مفید است.
متدهای Erode و Dilate در اینجا کار حذف نویزهای اضافی را انجام میدهند؛ تا بهتر بتوان بر روی نواحی بزرگتر یافت شده، تمرکز کرد.
یافتن بزرگترین ناحیهی به هم پیوستهی موجود در یک تصویر
تمام این مراحل را انجام دادیم تا بتوانیم بزرگترین ناحیهی به هم پیوستهای را که احتمال میرود بارکد باشد، در تصویر تشخیص دهیم. پس از این آماده سازیها، اکنون با استفاده از متد یافتن کانتورها، تمام نواحی یکی شده را یافته و بزرگترین مساحت ممکن را به عنوان بارکد انتخاب میکنیم:
//find the contours in the thresholded image, then sort the contours //by their area, keeping only the largest one Point[][] contours; HiearchyIndex[] hierarchyIndexes; Cv2.FindContours( closed, out contours, out hierarchyIndexes, mode: ContourRetrieval.CComp, method: ContourChain.ApproxSimple); if (contours.Length == 0) { throw new NotSupportedException("Couldn't find any object in the image."); } var contourIndex = 0; var previousArea = 0; var biggestContourRect = Cv2.BoundingRect(contours[0]); while ((contourIndex >= 0)) { var contour = contours[contourIndex]; var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour var boundingRectArea = boundingRect.Width * boundingRect.Height; if (boundingRectArea > previousArea) { biggestContourRect = boundingRect; previousArea = boundingRectArea; } contourIndex = hierarchyIndexes[contourIndex].Next; } var barcode = new Mat(image, biggestContourRect); //Crop the image Cv2.CvtColor(barcode, barcode, ColorConversion.BgrToGray); Cv2.ImShow("Barcode", barcode); Cv2.WaitKey(1); // do events
خواندن مقدار متناظر با بارکد یافت شده
خوب، تا اینجا موفق شدیم، محل قرارگیری بارکد را تصویر پیدا کنیم. مرحلهی بعد خواندن مقدار متناظر با این تصویر است. برای این منظور از کتابخانهی سورس بازی به نام http://zxingnet.codeplex.com استفاده خواهیم کرد. این کتابخانه قادر است بارکد بسازد و همچنین تصاویر بارکدها را خوانده و مقادیر متناظر با آنها را استخراج کند. برای نصب آن میتوان از دستور ذیل استفاده کرد:
PM> Install-Package ZXing.Net
private static string getBarcodeText(Mat barcode) { // `ZXing.Net` needs a white space around the barcode var barcodeWithWhiteSpace = new Mat(new Size(barcode.Width + 30, barcode.Height + 30), MatType.CV_8U, Scalar.White); var drawingRect = new Rect(new Point(15, 15), new Size(barcode.Width, barcode.Height)); var roi = barcodeWithWhiteSpace[drawingRect]; barcode.CopyTo(roi); Cv2.ImShow("Enhanced Barcode", barcodeWithWhiteSpace); Cv2.WaitKey(1); // do events return decodeBarcodeText(barcodeWithWhiteSpace.ToBitmap()); } private static string decodeBarcodeText(System.Drawing.Bitmap barcodeBitmap) { var source = new BitmapLuminanceSource(barcodeBitmap); // using http://zxingnet.codeplex.com/ // PM> Install-Package ZXing.Net var reader = new BarcodeReader(null, null, ls => new GlobalHistogramBinarizer(ls)) { AutoRotate = true, TryInverted = true, Options = new DecodingOptions { TryHarder = true, //PureBarcode = true, /*PossibleFormats = new List<BarcodeFormat> { BarcodeFormat.CODE_128 //BarcodeFormat.EAN_8, //BarcodeFormat.CODE_39, //BarcodeFormat.UPC_A }*/ } }; //var newhint = new KeyValuePair<DecodeHintType, object>(DecodeHintType.ALLOWED_EAN_EXTENSIONS, new Object()); //reader.Options.Hints.Add(newhint); var result = reader.Decode(source); if (result == null) { Console.WriteLine("Decode failed."); return string.Empty; } Console.WriteLine("BarcodeFormat: {0}", result.BarcodeFormat); Console.WriteLine("Result: {0}", result.Text); var writer = new BarcodeWriter { Format = result.BarcodeFormat, Options = { Width = 200, Height = 50, Margin = 4}, Renderer = new ZXing.Rendering.BitmapRenderer() }; var barcodeImage = writer.Write(result.Text); Cv2.ImShow("BarcodeWriter", barcodeImage.ToMat()); return result.Text; }
الف) این کتابخانه حتما نیاز دارد تا تصویر بارکد، در یک حاشیهی سفید در اختیار او قرار گیرد. به همین جهت در متد getBarcodeText، ابتدا تصویر بارکد یافت شده، به میانهی یک مستطیل سفید رنگ بزرگتر کپی میشود.
ب) برای تبدیل Mat به Bitmap مورد نیاز این کتابخانه میتوان از متد الحاقی ToBitmap استفاده کرد (قسمت 7).
ج) پس از آن وهلهای از کلاس BarcodeReader آماده شده و در آن پارامترهایی مانند بیشتر سعی کن (TryHarder) و اصلاح درجهی چرخش تصویر (AutoRotate) تنظیم شدهاند.
د) بارکدهای موجود در قبضهای ایران عموما بر اساس فرمت CODE_128 ساخته میشوند. بنابراین برای خواندن سریعتر آنها میتوان PossibleFormats را مقدار دهی کرد. اگر این مقدار دهی صورت نگیرد، تمام حالتهای ممکن بررسی میشوند.
در آخر کار این متد، از متد Writer آن نیز برای تولید بارکد مشابهی استفاده شدهاست تا بتوان بررسی کرد این دو تا چه اندازه به هم شبیه هستند.
همانطور که مشاهده میکنید، عدد تشخیص داده شده، با عدد شناسهی قبض و شناسهی پرداخت تصویر ابتدای بحث یکی است.
بهبود تصویر، پیش از ارسال آن به متد Decode کتابخانهی ZXing.Net
در تصویر قبلی، سطر decode failed را هم ملاحظه میکنید. علت اینجا است که اولین سعی انجام شده، موفق نبوده است؛ چون تصویر تشخیص داده شده، بیش از اندازه نویز و حاشیهی خاکستری دارد. میتوان این حاشیهی خاکستری را با دوبار اعمال متد Threshold از بین برد:
var barcodeClone = barcode.Clone(); var barcodeText = getBarcodeText(barcodeClone); if (string.IsNullOrWhiteSpace(barcodeText)) { Console.WriteLine("Enhancing the barcode..."); //Cv2.AdaptiveThreshold(barcode, barcode, 255, //AdaptiveThresholdType.GaussianC, ThresholdType.Binary, 9, 1); //var th = 119; var th = 100; Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.ToZero); Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.Binary); barcodeText = getBarcodeText(barcode); } Cv2.Rectangle(image, new Point(biggestContourRect.X, biggestContourRect.Y), new Point(biggestContourRect.X + biggestContourRect.Width, biggestContourRect.Y + biggestContourRect.Height), new Scalar(0, 255, 0), 2); if (debug) { Cv2.ImShow("Segmented Source", image); Cv2.WaitKey(1); // do events } Cv2.WaitKey(0); Cv2.DestroyAllWindows();
اعداد یافت شده، دقیقا از روی تصویر بهبود یافتهی توسط متدهای Threshold خوانده شدهاند و نه تصویر ابتدایی یافت شده. بنابراین به این موضوع نیز باید دقت داشت.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
من بلدم با set identity_insert table_name on/off کاری کنم که خودم دستی مقداری را برای خصیصه identity لحاظ کنم. ولی متاسفانه نتونستم مقدار یک ستون با خصیصه Identity رو بروز رسانی (یا همون update) کنم. لطفا بهم بگید که اصلا این کار ممکنه یا من بلد نیستم. البته براساس query زیر بمن SQL Server گفته که نمیشه این ستون را update کرد که ظاهرا هم همین طور(ستون id همانطور که در پیام آمده از نوع identity هست)
update t set id = new_id from (select id, row_number() over(order by id) new_id from #temp)t --Cannot update identity column 'id'.
اصلا اجازه بدین یه جور دیگه سوال رو مطرح کنم من نیاز دارم تمام مقادیر identity رو بروز رسانی کنم تا کاملا پشت سر هم و متوالی بشن این کار را میتونم با یک تابع row_number و یک derived table انجام بدم (اگر بذارن!) همانطور که قبلا نشان دادم، یا با روش زیر این کار را بکنم که البته اجرا نمیشه به این دلیل که در یک جدول نمیشه دو identity property داشت. با فرض اجرا شدن دستور select into باز هم در دستور update با مشکل بر میخوردیم (چون نمیشه ستون id را بروز رسانی کرد)
select id, identity(int, 1,1) new_id into #temptable from #temp order by id asc /* cannot add identity column, using the SELECT INTO statement, to table '#temptable', which already has column 'id' that inherits the identity property. */ update t set id = new_id from #temp t join #temptable d on t.id = d.id;
declare @t table(id int) insert into @t select id from #temp delete from #temp set identity_insert #temp on insert #temp (id) select row_number() over(order by id) from @t set identity_insert #temp off
من قصد ندارم صورت مساله نقد و بررسی بشه و اصولی بودن یا صحیح بودنش مورد ارزیابی قرار بگیره فقط برام این یک سوال شده.
مساله عمومی که راجب این ستون وجود داره استفاده کردن از Gapهای حاصل شده در این ستون برای درجهای بعدی است. که query آن نیز بسیار ساده و در دسترس است.
آیا شما میدانید که چگونه این مشکل با sequence ای که در نسخه 2012 معرفی شده است حل میشود؟
روش متداول کار با کتابخانهی iTextSharp ، ایجاد شیء Document ، سپس ایجاد PdfWriter برای نوشتن در آن، گشودن سند و ... افزودن اشیایی مانند Paragraph ، PdfPTable ، PdfPCell و غیره به آن است و در نهایت بستن سند. راه میانبری هم برای کار با این کتابخانه وجود دارد و آن هم استفاده از امکانات فضای نام iTextSharp.text.html.simpleparser آن میباشد. به این ترتیب میتوان به صورت خودکار، یک محتوای HTML را تبدیل به فایل PDF کرد.
مثال : نمایش یک متن HTML ساده انگلیسی
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.html.simpleparser;
using iTextSharp.text.pdf;
namespace HeadersAndFooters
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
var html = @"<span style='color:blue'><b>Testing</b></span>
<i>iTextSharp's</i> <u>HTML to PDF capabilities</u>";
var parsedHtmlElements = HTMLWorker.ParseToList(new StringReader(html), null);
foreach (var htmlElement in parsedHtmlElements)
{
pdfDoc.Add(htmlElement);
}
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
نکتهی جدید کد فوق، استفاده از متد HTMLWorker.ParseToList است. به این ترتیب parser کتابخانهی iTextSharp وارد عمل شده و html تعریف شده را به معادل المانهای بومی خودش تبدیل میکند؛ مثلا تبدیل به chunk یا pdfptable و امثال آن. در نهایت در طی یک حلقه، این عناصر به صفحه اضافه میشوند.
البته باید دقت داشت که HTMLWorker امکان تبدیل عناصر پیچیده، تودرتو و چندلایه HTML را ندارد؛ اما بهتر از هیچی است!
همهی اینها خوب! اما به درد ما فارسی زبانها نمیخورد. همین متغیر html فوق را با یک متن فارسی جایگزین کنید، چیزی نمایش داده نخواهد شد. البته این هم نکته دارد که در ادامه ذکر خواهد شد.
جهت نمایش متون فارسی نیاز است تا نکات ذکر شده در مطلب «فارسی نویسی و iTextSharp» رعایت شوند که شامل:
- تعیین صریح قلم
- تعیین encoding
- استفاده از عناصر دربرگیرندهای است که خاصیت RunDirection را پشتیبانی میکنند؛ مانند PdfPCell و غیره
به این ترتیب خواهیم داشت:
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.html.simpleparser;
using iTextSharp.text.pdf;
using iTextSharp.text.html;
namespace HeadersAndFooters
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
//روش صحیح تعریف فونت
FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
StyleSheet styles = new StyleSheet();
styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.FONTFAMILY, "tahoma");
styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.ENCODING, "Identity-H");
var html = @"<span style='color:blue'><b>آزمایش</b></span>
کتابخانه <i>iTextSharp</i> <u>جهت بررسی فارسی نویسی</u>";
var parsedHtmlElements = HTMLWorker.ParseToList(new StringReader(html), styles);
PdfPCell pdfCell = new PdfPCell { Border = 0 };
pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
foreach (var htmlElement in parsedHtmlElements)
{
pdfCell.AddElement(htmlElement);
}
var table1 = new PdfPTable(1);
table1.AddCell(pdfCell);
pdfDoc.Add(table1);
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
همانطور که ملاحظه میکنید ابتدا قلمی در cache قلمهای این کتابخانه ثبت میشود (FontFactory.Register). سپس نوع قلم و encoding آن توسط یک StyleSheet تعریف شده و به HTMLWorker.ParseToList ارسال میگردد و در نهایت به کمک یک المان دارای RunDirection، در صفحه نمایش داده میشود.
نکته:
ممکن است که به متغیر html ، یک table ساده html را نسبت دهید. در این حالت پس از تنظیم style یاد شده، در هر سلول این html table ، متون فارسی به صورت معکوس نمایش داده خواهند شد که این هم یک نکتهی کوچک دیگر دارد:
foreach (var htmlElement in parsedHtmlElements)
{
if (htmlElement is PdfPTable)
{
var table = (PdfPTable)htmlElement;
table.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
foreach (var row in table.Rows)
{
foreach (var cell in row.GetCells())
{
cell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
}
}
}
pdfCell.AddElement(htmlElement);
}
در قسمتی که قرار است المانهای معادل به pdfCell اضافه شوند، آنها را بررسی کرده و RunDirection آنها را RTL خواهیم کرد.
کاربردها:
بدیهی است این حالت برای تهیه گزارشات پیشرفتهتر برای مثال تهیه قالبهایی که در حین تهیه PDF ، قسمتهایی از آنها توسط برنامه نویس Replace میشوند، بسیار مناسب است.
همچنین مطلب «بارگذاری یک یوزرکنترل با استفاده از جیکوئری» و متد RenderUserControl مطرح شده در آن که در نهایت یک قطعه کد HTML را به صورت رشته به ما تحویل میدهد، میتواند جهت تهیه گزارشهای پویایی که برای مثال قسمتی از آن یک GridView بایند شده حاصل از یک یوزر کنترل است، مورد استفاده قرار گیرد.