In this episode, Robert is joined by Mads Kristensen, who shows us the Web Extension Pack. This extension installs a number of extensions that help you become a more productive Web developer. The extensions contained in the Web Extension Pack have been proven to be stable over time and useful for all Web developers.
روال کار به این صورت بوده که قبل از رندر شدن صفحه در سمت سرور خروجی نهایی بررسی شده و با استفاده از عبارات با قاعده الگوهای مورد نظر لیست شده و سپس حذف میشوند و در نهایت خروجی مورد نظر حاصل خواهد شد. برای راحتی کار و عدم نوشتن این روال در تمامی صفحات میتواند در مستر پیج این عمل را انجام داد. مثلا:
private static readonly Regex RegexBetweenTags = new Regex(@">\s+<", RegexOptions.Compiled); private static readonly Regex RegexLineBreaks = new Regex(@"\r\s+", RegexOptions.Compiled); protected override void Render(HtmlTextWriter writer) { using (var htmlwriter = new HtmlTextWriter(new System.IO.StringWriter())) { base.Render(htmlwriter); var html = htmlwriter.InnerWriter.ToString(); html = RegexBetweenTags.Replace(html, "> <"); html = RegexLineBreaks.Replace(html, string.Empty); html = html.Replace("//<![CDATA[", "").Replace("//]]>", ""); html = html.Replace("// <![CDATA[", "").Replace("// ]]>", ""); writer.Write(html.Trim()); } }
این عمل در ASP.NET Webform به آسانی انجام شده و باعث حذف فضاهای خالی در خروجی صفحه میشود.
برای انجام این عمل در ASP.NET MVC روال کار به این صورت نیست و نمیتوان مانند ASP.NET Webform عمل کرد.
چون در MVC از ViewPage استفاده میشود و ما مستقیما به خروجی آن دسترسی نداریم یک روش این است که میتوانیم یک کلاس برای ViewPage تعریف کرده و رویداد Write آن را تحریف کرده و مانند مثال بالا فضای خالی را در خروجی حذف کرد. البته برای استفاده باید کلاس ایجاد شده را به عنوان فایل پایه جهت ایجاد صفحات در MVC فایل web.config معرفی کنیم. این روش در اینجا به وضوح شرح داده شده است.
اما هدف ما پیاده سازی با استفاده از اکشن فیلتر هاست. برای پیاده سازی ایتدا یک اکشن فیلتر به نام CompressAttribute تعریف میکنیم مانند زیر:
using System; using System.IO; using System.IO.Compression; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; namespace PWS.Common.ActionFilters { public class CompressAttribute : ActionFilterAttribute { #region Methods (2) // Public Methods (1) /// <summary> /// Called by the ASP.NET MVC framework before the action method executes. /// </summary> /// <param name="filterContext">The filter context.</param> public override void OnActionExecuting(ActionExecutingContext filterContext) { var response = filterContext.HttpContext.Response; if (IsGZipSupported(filterContext.HttpContext.Request)) { String acceptEncoding = filterContext.HttpContext.Request.Headers["Accept-Encoding"]; if (acceptEncoding.Contains("gzip")) { response.Filter = new GZipStream(response.Filter, CompressionMode.Compress); response.AppendHeader("Content-Encoding", "gzip"); } else { response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress); response.AppendHeader("Content-Encoding", "deflate"); } } // Allow proxy servers to cache encoded and unencoded versions separately response.AppendHeader("Vary", "Content-Encoding"); //حذف فضاهای خالی
response.Filter = new WhitespaceFilter(response.Filter); } // Private Methods (1) /// <summary> /// Determines whether [is G zip supported] [the specified request]. /// </summary> /// <param name="request">The request.</param> /// <returns></returns> private Boolean IsGZipSupported(HttpRequestBase request) { String acceptEncoding = request.Headers["Accept-Encoding"]; if (acceptEncoding == null) return false; return !String.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"); } #endregion Methods } /// <summary> /// Whitespace Filter /// </summary> public class WhitespaceFilter : Stream { #region Fields (3) private readonly Stream _filter; /// <summary> /// /// </summary> private static readonly Regex RegexAll = new Regex(@"\s+|\t\s+|\n\s+|\r\s+", RegexOptions.Compiled); /// <summary> /// /// </summary> private static readonly Regex RegexTags = new Regex(@">\s+<", RegexOptions.Compiled); #endregion Fields #region Constructors (1) /// <summary> /// Initializes a new instance of the <see cref="WhitespaceFilter" /> class. /// </summary> /// <param name="filter">The filter.</param> public WhitespaceFilter(Stream filter) { _filter = filter; } #endregion Constructors #region Properties (5) //methods that need to be overridden from stream /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports reading. /// </summary> /// <returns>true if the stream supports reading; otherwise, false.</returns> public override bool CanRead { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports seeking. /// </summary> /// <returns>true if the stream supports seeking; otherwise, false.</returns> public override bool CanSeek { get { return true; } } /// <summary> /// When overridden in a derived class, gets a value indicating whether the current stream supports writing. /// </summary> /// <returns>true if the stream supports writing; otherwise, false.</returns> public override bool CanWrite { get { return true; } } /// <summary> /// When overridden in a derived class, gets the length in bytes of the stream. /// </summary> /// <returns>A long value representing the length of the stream in bytes.</returns> public override long Length { get { return 0; } } /// <summary> /// When overridden in a derived class, gets or sets the position within the current stream. /// </summary> /// <returns>The current position within the stream.</returns> public override long Position { get; set; } #endregion Properties #region Methods (6) // Public Methods (6) /// <summary> /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed. /// </summary> public override void Close() { _filter.Close(); } /// <summary> /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// </summary> public override void Flush() { _filter.Flush(); } /// <summary> /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// </summary> /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns> /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. /// </returns> public override int Read(byte[] buffer, int offset, int count) { return _filter.Read(buffer, offset, count); } /// <summary> /// When overridden in a derived class, sets the position within the current stream. /// </summary> /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param> /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param> /// <returns> /// The new position within the current stream. /// </returns> public override long Seek(long offset, SeekOrigin origin) { return _filter.Seek(offset, origin); } /// <summary> /// When overridden in a derived class, sets the length of the current stream. /// </summary> /// <param name="value">The desired length of the current stream in bytes.</param> public override void SetLength(long value) { _filter.SetLength(value); } /// <summary> /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. /// </summary> /// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param> /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param> /// <param name="count">The number of bytes to be written to the current stream.</param> public override void Write(byte[] buffer, int offset, int count) { string html = Encoding.Default.GetString(buffer); //remove whitespace html = RegexTags.Replace(html, "> <"); html = RegexAll.Replace(html, " "); byte[] outdata = Encoding.Default.GetBytes(html); //write bytes to stream _filter.Write(outdata, 0, outdata.GetLength(0)); } #endregion Methods } }
در کلاس WhitespaceFilter با تحریف متد Write الگوهای فضای خالی موجود در درخواست یافت شده و آنها را حذف میکنیم. در نهایت خروجی این کلاس که از نوع استریم است به ویژگی فیلتر صفحه اعمال میشود.
برای معرفی فیلتر تعریف شده میتوان در فایل Global.asax در رویداد Application_Start به صورت زیر فیلتر مورد نظر را به فیلترهای MVC اعمال کرد.
GlobalFilters.Filters.Add(new CompressAttribute());
پ.ن: جهت سهولت، در این کلاس ها، صفحات فشرده سازی و همزمان فضاهای خالی آنها حذف شده است.
بحث دیگری هم در اینجا «How do you disable browser Autocomplete on web form field / input tag?»
تغییر مجوز استفادهی از Redis
Redis, the popular in-memory data store, is switching away from the open source three-clause BSD license. Instead, in a move that is clearly aimed to prevent the large cloud providers from offering free alternatives to Redis’ own hosted services, Redis will now be dual-licensed under the Redis Source Available License (RSALv2) and Server Side Public License (SSPLv1). Under this new license, cloud service providers hosting Redis will need to enter into a commercial agreement with Redis. The first company to do so is Microsoft.
نام این جدول را با درنظر گرفتن شرایط موجود میتوان Resources گذاشت.
ستون Name برای ذخیره نام منبع درنظر گرفته شده است. این نام برابر نام منابع درخواستی در سیستم مدیریت منابع ASP.NET است که درواقع برابر همان نام فایل منبع اما بدون پسوند resx. است.
ستون Key برای نگهداری کلید ورودی منبع استفاده میشود که دقیقا برابر همان مقداری است که درون فایلهای resx. ذخیره میشود.
ستون Culture برای ذخیره کالچر ورودی منبع به کار میرود. این مقدار میتواند برای کالچر پیشفرض برنامه برابر رشته خالی باشد.
ستون Value نیز برای نگهداری مقدار ورودی منبع استفاده میشود.
برای ستون Id میتوان از GUID نیز استفاده کرد. در اینجا برای راحتی کار از نوع داده bigint و خاصیت Identity برای تولید خودکار آن در Sql Server استفاده شده است.
نکته: برای امنیت بیشتر میتوان یک Unique Constraint بر روی سه فیلد Name و Key و Culture اعمال کرد.
برای نمونه به تصویر زیر که ذخیره تعدای ورودی منبع را درون جدول Resources نمایش میدهد دقت کنید:
اصلاح کلاس DbResourceProviderFactory
برای ذخیره منابع محلی، جهت اطمینان از یکسان بودن نام منبع، متد مربوطه در کلاس DbResourceProviderFactory باید بهصورت زیر تغییر کند:
public override IResourceProvider CreateLocalResourceProvider(string virtualPath) { if (!string.IsNullOrEmpty(virtualPath)) { virtualPath = virtualPath.Remove(0, virtualPath.IndexOf('/') + 1); // removes everything from start to the first '/' } return new LocalDbResourceProvider(virtualPath); }
ارتباط با دیتابیس
خوشبختانه برای تبادل اطلاعات با جدول بالا امروزه راههای زیادی وجود دارد. برای پیادهسازی آن مثلا میتوان از یک اینترفیس استفاده کرد. سپس با استفاده از سازوکارهای موجود مثلا بهکارگیری IoC، نمونه مناسبی از پیادهسازی اینترفیس مذبور را در اختیار برنامه قرار داد.
اما برای جلوگیری از پیچیدگی بیش از حد و دور شدن از مبحث اصلی، برای پیادهسازی فعلی از EF Code First به صورت مستقیم در پروژه استفاده شده است که سری آموزشی کاملی از آن در همین سایت وجود دارد.
پس از پیادهسازی کلاسهای مرتبط برای استفاده از EF Code First، از کلاس ResourceData که در بخش اول نیز نشان داده شده بود، برای کپسوله کردن ارتباط با دادهها استفاده میشود که نمونهای ابتدایی از آن در زیر آورده شده است:
using System.Collections.Generic; using System.Linq; using DbResourceProvider.Models; namespace DbResourceProvider.Data { public class ResourceData { private readonly string _resourceName; public ResourceData(string resourceName) { _resourceName = resourceName; } public Resource GetResource(string resourceKey, string culture) { using (var data = new TestContext()) { return data.Resources.SingleOrDefault(r => r.Name == _resourceName && r.Key == resourceKey && r.Culture == culture); } } public List<Resource> GetResources(string culture) { using (var data = new TestContext()) { return data.Resources.Where(r => r.Name == _resourceName && r.Culture == culture).ToList(); } } } }
using System.Collections.Generic; using System.Globalization; using DbResourceProvider.Data; namespace DbResourceProvider { public class DbResourceManager { private readonly string _resourceName; private readonly Dictionary<string, Dictionary<string, object>> _resourceCacheByCulture; public DbResourceManager(string resourceName) { _resourceName = resourceName; _resourceCacheByCulture = new Dictionary<string, Dictionary<string, object>>(); } public object GetObject(string resourceKey, CultureInfo culture) { return GetCachedObject(resourceKey, culture.Name); } private object GetCachedObject(string resourceKey, string cultureName) { if (!_resourceCacheByCulture.ContainsKey(cultureName)) _resourceCacheByCulture.Add(cultureName, new Dictionary<string, object>()); var cachedResource = _resourceCacheByCulture[cultureName]; lock (this) { if (!cachedResource.ContainsKey(resourceKey)) { var data = new ResourceData(_resourceName); var dbResource = data.GetResource(resourceKey, cultureName); if (dbResource == null) return null; var cachedResources = _resourceCacheByCulture[cultureName]; cachedResources.Add(dbResource.Key, dbResource.Value); } } return cachedResource[resourceKey]; } } }
private object GetCachedObject(string resourceKey, string cultureName) { lock (this) { if (!_resourceCacheByCulture.ContainsKey(cultureName)) { _resourceCacheByCulture.Add(cultureName, new Dictionary<string, object>()); var cachedResources = _resourceCacheByCulture[cultureName]; var data = new ResourceData(_resourceName); var dbResources = data.GetResources(cultureName); foreach (var dbResource in dbResources) { cachedResources.Add(dbResource.Key, dbResource.Value); } } } var cachedResource = _resourceCacheByCulture[cultureName]; return !cachedResource.ContainsKey(resourceKey) ? null : cachedResource[resourceKey]; }
using System.Collections; using System.Collections.Generic; using System.Globalization; namespace DbResourceProvider { public class CultureFallbackProvider : IEnumerable<CultureInfo> { private readonly CultureInfo _startingCulture; private readonly CultureInfo _neutralCulture; private readonly bool _tryParentCulture; public CultureFallbackProvider(CultureInfo startingCulture = null, CultureInfo neutralCulture = null, bool tryParentCulture = true) { _startingCulture = startingCulture ?? CultureInfo.CurrentUICulture; _neutralCulture = neutralCulture; _tryParentCulture = tryParentCulture; } #region Implementation of IEnumerable<CultureInfo> public IEnumerator<CultureInfo> GetEnumerator() { var reachedNeutralCulture = false; var currentCulture = _startingCulture; do { if (_neutralCulture != null && currentCulture.Name == _neutralCulture.Name) { yield return CultureInfo.InvariantCulture; reachedNeutralCulture = true; break; } yield return currentCulture; currentCulture = currentCulture.Parent; } while (_tryParentCulture && !HasInvariantCultureName(currentCulture)); if (!_tryParentCulture || HasInvariantCultureName(_startingCulture) || reachedNeutralCulture) yield break; yield return CultureInfo.InvariantCulture; } #endregion #region Implementation of IEnumerable IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion private bool HasInvariantCultureName(CultureInfo culture) { return culture.Name == CultureInfo.InvariantCulture.Name; } } }
public object GetObject(string resourceKey, CultureInfo culture) { foreach (var currentCulture in new CultureFallbackProvider(culture)) { var value = GetCachedObject(resourceKey, currentCulture.Name); if (value != null) return value; } throw new KeyNotFoundException("The specified 'resourceKey' not found."); }
ابتدا تنظیمات موردنیاز فایل کانفیگ را که در قسمت قبل نشان داده شد، در برنامه خود اعمال کنید.
دادههای نمونه نشان داده شده در ابتدای این مطلب را درنظر بگیرید. حال اگر در یک برنامه وب اپلیکیشن، صفحه Default.aspx در ریشه سایت حاوی دو کنترل زیر باشد:
<asp:Label ID="Label1" runat="server" meta:resourcekey="Label1" /> <asp:Label ID="Label2" runat="server" meta:resourcekey="Label2" />
سپس تغییر زیر را در فایل web.config اعمال کنید تا کالچر UI سایت به fa تغییر یابد (به بخش "uiCulture="fa دقت کنید):
<globalization uiCulture="fa" resourceProviderFactoryType = "DbResourceProvider.DbResourceProviderFactory, DbResourceProvider" />
میبینید که با توجه به عدم وجود مقداری برای Label2.Text برای کالچر fa، عملیات fallback اتفاق افتاده است.
کار تولید یک پرووایدر منابع سفارشی دیتابیسی به اتمام رسید. تا اینجا اصول کلی تولید یک پرووایدر سفارشی شرح داده شد. بدین ترتیب میتوان برای هر حالت خاص دیگری نیز پرووایدرهای سفارشی مخصوص ساخت تا مدیریت منابع به آسانی تحت کنترل برنامه نویس قرار گیرد.
اما نکتهای را که باید به آن توجه کنید این است که در پیادهسازیهای نشان داده شده با توجه به نحوه کششدن مقادیر ورودیها، اگر این مقادیر در دیتابیس تغییر کنند، تا زمانیکه سایت ریست نشود این تغییرات در برنامه اعمال نخواهد شد. زیرا همانطور که اشاره شد، مدیریت نمونههای تولیدشده از پرووایدرهای منابع برای هر منبع درخواستی درنهایت برعهده ASP.NET است. بنابراین باید مکانیزمی پیاده شود تا کلاس DbResourceManager از بهروزرسانی ورودیهای کششده اطلاع یابد تا آنها را ریفرش کند.
در ادامه درباره روشهای مختلف نحوه پیادهسازی قابلیت بهروزرسانی ورودیهای منابع در زمان اجرا با استفاده از پرووایدرهای منابع سفارشی بحث خواهد شد. همچنین راهحلهای مختلف استفاده از این پرووایدرهای سفارشی در جاهای مختلف پروژههای MVC شرح داده میشود.
البته مباحث پیشرفتهتری چون تزریق وابستگی برای پیادهسازی لایه ارتباط با دیتابیس در بیرون و یا تولید یک Factory برای تزریق کامل پرووایدر منابع از بیرون نیز جای بحث و بررسی دارد.
منابع
http://msdn.microsoft.com/en-us/library/aa905797.aspx
http://msdn.microsoft.com/en-us/library/system.web.compilation.resourceproviderfactory.aspx
http://www.dotnetframework.org/default.aspx/.../ResourceFallbackManager@cs
http://www.codeproject.com/Articles/14190/ASP-NET-2-0-Custom-SQL-Server-ResourceProvider
کلاس ArrayList
using System; using System.Collections; class ProgrArrayListExample { static void Main() { ArrayList list = new ArrayList(); list.Add("Hello"); list.Add(5); list.Add(3.14159); list.Add(DateTime.Now); for (int i = 0; i < list.Count; i++) { object value = list[i]; Console.WriteLine("Index={0}; Value={1}", i, value); } } }
Index=0; Value=Hello Index=1; Value=5 Index=2; Value=3.14159 Index=3; Value=29.02.2015 23:17:01
ArrayList list = new ArrayList(); list.Add(2); list.Add(3.5f); list.Add(25u); list.Add(" ریال"); dynamic sum = 0; for (int i = 0; i < list.Count; i++) { dynamic value = list[i]; sum = sum + value; } Console.WriteLine("Sum = " + sum); // Output: Sum = 30.5ریال
مجموعههای جنریک Generic Collections
GenericType<T> instance = new GenericType<T>();
List<int> intList = new List<int>(); List<bool> boolList = new List<bool>(); List<double> realNumbersList = new List<double>();
List<int> intList = new List<int>();
- استفاده از index برای دسترسی به یک مقدار، صرف نظر از اینکه چه میزان دادهای در آن وجود دارد، بسیار سریع انجام میگیرد.
- جست و جوی یک عنصر بر اساس مقدار: جست و جو خطی است در نتیجه اگر مقدار مورد نظر در آخرین خانهها باشد بدترین وضعیت ممکن رخ میدهد و بسیار کند عمل میکند. داده هر چی کمتر بهتر و هر چه بیشتر بدتر. البته اگر بخواهید مجموعهای از مقدارهای برابر را برگردانید هم در بدترین وضعیت ممکن خواهد بود.
- حذف و درج (منظور insert) المانها به خصوص موقعی که انتهای آرایه نباشید، شیفت پیدا کردن در آرایه عملی کاملا کند و زمانبر است.
- موقعی که عنصری را بخواهید اضافه کنید اگر ظرفیت آرایه تکمیل شده باشد، نیاز به عمل زمانبر افزایش ظرفیت خواهد بود که البته این عمل به ندرت رخ میدهد و عملیات افزودن Add هم هیچ وابستگی به تعداد المانها ندارد و عملی سریع است.
- افزودن به انتهای لیست به خاطر این که همیشه گره آخر در tail وجود دارد بسیار سریع است.
- عملیات درج insert در هر موقعیتی که باشد اگر یک اشاره گر به آن محل باشد یک عملیات سریع است یا اینکه درج در ابتدا یاانتهای لیست باشد.
- جست و جوی یک مقدار چه بر اساس اندیس باشد و چه مقدار، کار جست و جو کند خواهد بود. چرا که باید تمامی المانها از اول به آخر اسکن بشن.
- عملیات حذف هم به خاطر اینکه یک عمل جست و جو در ابتدای خود دارد، یک عمل کند است.
استفاده از لیست پیوندی برای پیاده سازی پشته:
Stack<string> stack = new Stack<string>(); stack.Push("A"); stack.Push("B"); stack.Push("C"); while (stack.Count > 0) { string letter= stack.Pop(); Console.WriteLine(letter); } //خروجی //C //B //A
ابتدای آرایه مکانی است که عنصر از آنجا برداشته میشود و Head به آن اشاره میکند و tail هم به انتهای آرایه که جهت درج عنصر جدید مفید است. با برداشتن هر خانهای که head به آن اشاره میکند، head یک خانه به سمت جلو حرکت میکند و زمانی که Head از tail بیشتر شود، یعنی اینکه دیگر عنصری یا المانی در صف وجود ندارد و head و Tail به ابتدای صف حرکت میکنند. در این حالت موقعی که المان جدیدی قصد اضافه شدن داشته باشد، افزودن، مجددا از اول صف آغاز میشود و به این صفها، صف حلقوی میگویند.
عملیات اصلی صف دو مورد هستند enqueue که المان جدید را در انتهای صف قرار میدهد و dequeue اولین المان صف را بیرون میکشد.
پیاده سازی صف به صورت پویا با لیستهای پیوندی
برای پیاده سازی صف، لیستهای پیوندی یک طرفه کافی هستند:
در این حالت عنصر جدید مثل سابق به انتهای لیست اضافه میشود و برای حذف هم که از اول لیست کمک میگیریم و با حذف عنصر اول، متغیر Head به عنصر یا المان دوم اشاره خواهد کرد.
کلاس از پیش آمده صف در دات نت <Queue<T است و نحوهی استفاده آن بدین شکل است:
static void Main() { Queue<string> queue = new Queue<string>(); queue.Enqueue("Message One"); queue.Enqueue("Message Two"); queue.Enqueue("Message Three"); queue.Enqueue("Message Four"); while (queue.Count > 0) { string msg = queue.Dequeue(); Console.WriteLine(msg); } } //خروجی //Message One //Message Two //Message Thre //Message Four
کتابخانه Zepto.js
Zepto is a minimalist JavaScript library for modern browsers with a largely jQuery-compatible API. If you use jQuery, you already know how to use Zepto.
While 100% jQuery coverage is not a design goal, the APIs provided match their jQuery counterparts. The goal is to have a ~5-10k modular library that downloads and executes fast, with a familiar and versatile API, so you can concentrate on getting stuff done.
npm install zepto
اگر تعداد کاراکترهای ناخواسته محدود و مشخص هستند میتوانید با دستور REPLACE آنها را حذف کنید، مثلا میخواهیم هر سه کاراکتر ~!@ از رشته حذف شوند:
DECLARE @s VARCHAR(50) = '~~~~~~!@@@@@@@ salam'; SET @s = REPLACE(REPLACE(REPLACE(@s, '~', ''), '!', ''), '@', ''); SELECT @s AS new_string
ولی هنگامی که کاراکترها نامحدود بوده امکان نوشتن تابع REPLACE به کرات بی معنا است در این حالت باید دنبال روشی پویا و تعمیم پذیر بود.
با جستجویی که در اینترنت انجام دادم متوجه شدم تکنیک WHILE یا همون loop یکی از روشهای رایج برای انجام اینکار هست، که احتمالا به دلیل سهولت در بکارگیری و سادگی آن بوده که عمومیت پیدا کرده است.
مستقل از این صحبتها هدف معرفی یک روش مجموعه گرا (set-based) برای این مساله میباشد.
حذف کاراکترها ناخواسته با تکنیک Recursive CTE
راه حل بر اساس جدول زیر است:
CREATE TABLE test_string (id integer not null primary key, string_value varchar(500) not null); INSERT INTO test_string VALUES (1, '@@@@ #### salam 12345'), (2, 'good $$$$$ &&&&&& bye 00000');
حالا فرض کنید میخواهیم هر کاراکتری غیر از حروف الفبای انگلیسی و فاصله(space) از رشته حذف شود. پس دو داده فوق به صورت salam و good bye در انتها در خواهند آمد.
برای حذف کاراکترهای ناخواسته فوق query زیر را اجرا کنید.
WITH CTE (ID, MyString, Ix) AS ( SELECT id, string_value, PATINDEX('%[^a-z ]%', string_value) FROM test_string UNION ALL SELECT id, CAST(REPLACE(MyString, SUBSTRING(MyString,Ix , 1), '') AS VARCHAR(500)), PATINDEX('%[^a-z ]%', REPLACE(MyString, SUBSTRING(MyString,Ix , 1), '')) FROM CTE WHERE Ix > 0 ) SELECT * FROM cte --WHERE Ix = 0; ORDER BY id, CASE WHEN Ix = 0 THEN 1 ELSE 0 END, Ix;
توضیح query:
در قسمت anchor اندیس اولین کاراکتر ناخواسته (خارج از رنج حروف الفبا و فاصله) بدست میآید. سپس در قسمت recursive هر کاراکتری که برابر باشد با کاراکتر ناخواسته ای که در مرحله قبل بدست آمده از رشته حذف میشود این عملیات توسط تابع replace صورت میگیرد و اندیس کاراکتر ناخواسته بعدی بعد از حذف کاراکتر ناخواسته قبلی بدست میآید که به مرحله بعد منتقل میشود. این مراحل تا آنجایی پیش میرود که دیگر کاراکتر ناخواسته ای در رشته وجود نداشته باشد.
به جدول زیر توجه بفرمایید (خروجی query فوق)
نتیجه مطلوب ما آن دو سطری است که در کادر بنفش هستند. که اگر به ستون Ix اشان توجه کنید مقدارش برابر با 0 است.
لطفا به سطر اول جدول توجه بفرمایید مشاهده میشود که هر 4 کاراکتر @ یکباره از رشته حذف شدند که بدلیل استفاده از تابع REPLACE میباشد.
One of the most exciting announcements during this year’s Connect(); event was the ability to embed .NET libraries into existing iOS (Objective-C/Swift) and Android (Java) applications with .NET Embedding. This is great because you can start to share code between your iOS and Android applications, and you can also share the user interface between your apps when you combine .NET Embedding with Xamarin.Forms Native Forms. This means that you can leverage your existing investments and apps without having to re-write them from scratch to start adding cross-platform logic and UI. In fact, during the Connect(); keynote I showed how I was able to extend the open source Swift-based Kickstarter iOS application with .NET Embedding and Xamarin.Forms Native Forms. You can check out the clip below.
فرض کنید جدولی دارید با چند ستون محدود که نتیجهی نهایی گزارش آن مثلا 100 صفحه است. جهت صرفه جویی در کاغذ مصرفی شاید بهتر باشد که این جدول را به صورت چند ستونی مثلا 5 ستون در یک صفحه نمایش داد؛ چیزی شبیه به شکل زیر:
روش انجام اینکار به کمک iTextSharp به صورت زیر است:
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
var table1 = new PdfPTable(1);
table1.WidthPercentage = 100f;
table1.HeaderRows = 2;
table1.FooterRows = 1;
//header row
var headerCell = new PdfPCell(new Phrase("header"));
table1.AddCell(headerCell);
//footer row
var footerCell = new PdfPCell(new Phrase(" "));
table1.AddCell(footerCell);
//adding some rows
for (int i = 0; i < 400; i++)
{
var rowCell = new PdfPCell(new Phrase(i.ToString()));
table1.AddCell(rowCell);
}
// wrapping table1 in multiple columns
ColumnText ct = new ColumnText(pdfWriter.DirectContent);
ct.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
ct.AddElement(table1);
int status = 0;
int count = 0;
int l = 0;
int columnsWidth = 100;
int columnsMargin = 7;
int columnsPerPage = 4;
int r = columnsWidth;
bool isRtl = true;
// render the column as long as it has content
while (ColumnText.HasMoreText(status))
{
if (isRtl)
{
ct.SetSimpleColumn(
pdfDoc.Right - l, pdfDoc.Bottom,
pdfDoc.Right - r, pdfDoc.Top
);
}
else
{
ct.SetSimpleColumn(
pdfDoc.Left + l, pdfDoc.Bottom,
pdfDoc.Left + r, pdfDoc.Top
);
}
var delta = columnsWidth + columnsMargin;
l += delta;
r += delta;
// render as much content as possible
status = ct.Go();
// go to a new page if you've reached the last column
if (++count > columnsPerPage)
{
count = 0;
l = 0;
r = columnsWidth;
pdfDoc.NewPage();
}
}
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
توضیحات:
تا قسمت تعریف جدول و اضافه کردن سطرها و ستونهای مورد نظر، همان بحث «تکرار خودکار سرستونهای یک جدول در صفحات مختلف، توسط iTextSharp» میباشد.
اصل مطلب از قسمت ColumnText شروع میشود. با استفاده از شیء ColumnText میتوان محتوای خاصی را در طی چند ستون در صفحه نمایش داد. عرض این ستونها هم توسط متد SetSimpleColumn مشخص میشود و همچنین محل دقیق قرارگیری آنها در صفحه. در اینجا دو حالت راست به چپ و چپ به راست در نظر گرفته شده است.
اگر حالت راست به چپ را در نظر بگیریم، محل قرارگیری اولین ستون از سمت راست صفحه (pdfDoc.Right) باید تعیین شود. سپس هربار به اندازهی عرضی که مد نظر است باید محل شروع ستون را مشخص کرد (pdfDoc.Right - l). هر زمانیکه ct.Go فراخوانی میشود، تاجایی که میسر باشد، اطلاعات جدول 1 در یک ستون درج میشود. سپس بررسی میشود که تا این لحظه چند ستون در صفحه نمایش داده شده است. اگر تعداد مورد نظر ما (columnsPerPage) تامین شده باشد، کار را در صفحهی بعد ادامه خواهیم داد (pdfDoc.NewPage)، در غیراینصورت مجددا مکان یک ستون دیگر در همان صفحه تعیین شده و کار افزودن اطلاعات به آن آغاز خواهد شد و این حلقه تا جایی که تمام محتوای جدول 1 را درج کند، ادامه خواهد یافت.