Inicio > .NET Framework, C#, Código y ejemplos > Crear nuestra propia etiqueta [url]

Crear nuestra propia etiqueta [url]


Hoy me vi en la necesidad de crear una columna personalizada para un sitio en SharePoint que estoy desarrollando. SharePoint cuenta con una columna, Hyperlink, que permite guardar enlaces a una dirección web, en un par URL-título. Pero no permite guardar en una sola columna varios de estos pares, por lo que si requiero que mi lista tenga un número variable de enlaces, tengo que crear varias columnas Hyperlink, y eso no está chidito.

Así las cosas, me di a la tarea de crear mi propia columna. Sin embargo, solo puedo guardar texto, así que decidí que lo mejor era guardar los diferentes enlaces en pares URL-título de la siguiente forma:

[url=https://fermasmas.wordpress.com]Blog de Fer++[/url]

 

Este tipo de notación no es nueva, y de hecho se puede encontrar en diversas wikis y blogs. Lo que yo necesito, pues, es poder guardar varios de estos valores, y el control que muestra la columna personalizada parsea las diferentes etiquetas y las muestra en forma de lista.

Si te encuentras en una situación similar, sigue leyendo.

Para resolver este problema, me cree una clase hace precisamente eso. Lo interesante de ésta, por supuesto, es el método Parse, que utiliza una expresión regular para obtener la URl y el título.

La clase en cuestión es la siguiente.

using System;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;

namespace Fermasmas.Wordpress.Com
{
  public class Url : ISerializable
  {
    private string _url;
    private string _title;
    private string _description;
    private static Regex _regex;

    public static readonly Url Empty;

    static Url()
    {
      _regex = new Regex(
           @"\[url=(http|https)://{1}([a-zA-Z0-9/%@?:#&+._=-]*)\](.*?)\[/url\]",
           RegexOptions.Compiled);
      Empty = new Url("http://");
    }

    public Url()
      : this(string.Empty, string.Empty)
    {
    }

    public Url(string url)
      : this(url, string.Empty)
    {
    }

    public Url(string url, string title)
    {
      if (url == null)
        throw new ArgumentNullException("url");
      if (title == null)
        throw new ArgumentNullException("title");

      _url = url;
      _title = title;
      _description = string.Empty;
    }

    protected Url(SerializationInfo info, StreamingContext context)
    {
      if (info == null)
        throw new ArgumentNullException("info");

      _url = info.GetString("UrlPath") ?? string.Empty;
      _title = info.GetString("Title") ?? string.Empty;
      _description = info.GetString("Description") ?? string.Empty;
    }

    public string UrlPath
    {
      get { return _url; }
      set
      {
        if (value == null)
          throw new ArgumentNullException("value");
        _url = value;
      }
    }

    public string Title
    {
      get { return _title; }
      set
      {
        if (value == null)
          throw new ArgumentNullException("value");
        _title = value;
      }
    }

    public string Description
    {
      get { return _description; }
      set
      {
        if (value == null)
          throw new ArgumentNullException("value");
        _description = value;
      }
    }

    public string CodedUrl
    {
      get { return string.Format("[url={0}]{1}[/url]", _url, _title); }
    }

    public void Parse(string input)
    {
      _url = string.Empty;
      _title = string.Empty;

      Match match = _regex.Match(input ?? string.Empty);
      if (match.Success)
      {
        string protocol = string.Empty;
        if (match.Groups.Count >= 2)
          protocol = match.Groups[1].Value;
        string url = string.Empty;
        if (match.Groups.Count >= 3)
          url = match.Groups[2].Value;
        string text = string.Empty;
        if (match.Groups.Count >= 4)
          text = match.Groups[3].Value;
        _url = string.Format("{0}://{1}", protocol, url);
        _title = text;
      }
    }

    public Uri ToUri()
    {
      return ToUri(true);
    }

    public Uri ToUri(bool fail)
    {
      Uri uri;
      bool created = Uri.TryCreate(UrlPath, UriKind.RelativeOrAbsolute, out uri);
      if (!created && fail)
        throw new UriFormatException(
            string.Format("La url '{0}' está mal formada. ", UrlPath));
      
      return uri;
    }

    public override string ToString()
    {
      return CodedUrl;
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      if (info == null)
        throw new ArgumentNullException("info");

      info.AddValue("UrlPath", _url);
      info.AddValue("Title", _title);
      info.AddValue("Description", _description);
    }
  }
}

Creo que la clase no es muy difícil de entender. Lo interesante ocurre en el método Parse (línea 94). Este método utiliza una expresión regular (estática, definida en el constructor estático, línea 18), la cual obtiene hasta cuatro grupos:

  • El primer grupo obtiene la etiqueta completa. Es decir: [url=https://fermasmas.wordpress.com]Blog de Fer++[/url].
  • El segundo grupo obtiene el protocolo, limitado a http y https.
  • El tercer grupo obtiene la URL sin el protocolo, en nuestro ejemplo, fermasmas.wordpress.com.
  • El cuarto grupo obtiene el título, osea Blog de Fer++.

La expresión regular fuerza a que ésta sea de la siguiente forma:

  • [url=http://url]título[/url]
  • [url=https://url]título[/url]

Donde URL es cualquier URL válida formada por caracteres alfanuméricos y cualesquiera signos “%@?:#&+._=-“. Nada fuera del otro mundo.

La clase implementa ISerializable porque necesito poder serializarla a binario o XML, y para ello implementamos el constructor protegido, que se encarga de de-serializar, y el método GetObjectData. Nada de interés para el tema en cuestión.

Fuera de eso todo lo demás es fácil de comprender.

Ahora bien, también tuve que crear una clase UrlCollection que me permita almacenar una colección de URLs. Esta clase también implementa un método Parse, pero a diferencia del de la clase Url donde el método obtiene la primera concordancia de la expresión regular, aquí se obtienen todas las concordancias, y para cada una agrega un nuevo objeto Url a la colección. He aquí el código.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;

namespace ePeople.VirtualStore
{
  public class UrlCollection : Collection<Url>, ISerializable
  {
    private static Regex _regex;
    private static readonly string _serializationName;

    static UrlCollection()
    {
      _regex = new Regex(
           @"\[url=(http|https)://{1}([a-zA-Z0-9/%@?:#&+._=-]*)\](.*?)\[/url\]", 
           RegexOptions.Compiled);
      _serializationName = "Values";
    }

    public UrlCollection()
      : base()
    {
    }

    public UrlCollection(IList<Url> list)
      : base(list)
    {
    }

    protected UrlCollection(SerializationInfo info, StreamingContext context)
    {
      if (info == null)
        throw new ArgumentNullException("info");

      string values = info.GetString(_serializationName);
      if (!string.IsNullOrEmpty(values))
        Parse(values);
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      if (info == null)
        throw new ArgumentNullException("info");
      info.AddValue(_serializationName, ToString());

    }

    public Url Add(string url, string title)
    {
      Url newUrl = new Url(url, title);
      Add(newUrl);
      return newUrl;
    }

    public Url Add(string url)
    {
      return Add(url, string.Empty);
    }

    public void Parse(string input)
    {
      Parse(input, false);
    }

    public void Parse(string input, bool clear)
    {
      if (clear)
        Clear();

      MatchCollection matches = _regex.Matches(input ?? string.Empty);
      foreach (Match match in matches)
      {
        string protocol = string.Empty;
        if (match.Groups.Count >= 2)
          protocol = match.Groups[1].Value;
        string url = string.Empty;
        if (match.Groups.Count >= 3)
          url = match.Groups[2].Value;
        string text = string.Empty;
        if (match.Groups.Count >= 4)
          text = match.Groups[3].Value;
        url = string.Format("{0}://{1}", protocol, url);
        Add(url, text);
      }
    }

    public override string ToString()
    {
      StringBuilder value = new StringBuilder();
      foreach (Url url in this)
        value.Append(url.ToString());

      return value.ToString();
    }
  }
}

 

Ahora sí, el siguiente programa muestra cómo se puede utilizar.

class Program
{
  static void Main(string[] args)
  {
    string singleInput = 
            @"[url=https://fermasmas.wordpress.com]Blog de Fer++[/url]";
    string multipleInput = 
            @"[url=https://fermasmas.wordpress.com]Blog de Fer++[/url]
                 [url=http://www.tiburones-rojos.com]Tiburones Rojos[/url]
                 [url=http://www.codeproject.com]Code Project[/url]
                 [url=http://msdn.microsoft.com]MSDN[/url]";

    Url url = new Url();
    url.Parse(singleInput);
    Console.WriteLine("URL sencilla\n=====\n ");
    Console.WriteLine("Original: {0}", url.CodedUrl);
    Console.WriteLine("URL: {0}", url.UrlPath);
    Console.WriteLine("Título: {0}", url.Title);
    Console.WriteLine();

    UrlCollection urls = new UrlCollection();
    urls.Parse(multipleInput);
    Console.WriteLine("URL múltiples\n=====\n ");
    foreach (Url thisUrl in urls)
    {
      Console.WriteLine("Original: {0}", thisUrl.CodedUrl);
      Console.WriteLine("URL: {0}", thisUrl.UrlPath);
      Console.WriteLine("Título: {0}", thisUrl.Title);
      Console.WriteLine();
    }

    Console.ReadKey(true);
  }
}

Y al ejecutarse, obtenemos la siguiente salida en la consola.

URL sencilla
=====

Original: [url=https://fermasmas.wordpress.com]Blog de Fer++[/url]
URL: https://fermasmas.wordpress.com
Título: Blog de Fer++

URL múltiples
=====

Original: [url=https://fermasmas.wordpress.com]Blog de Fer++[/url]
URL: https://fermasmas.wordpress.com
Título: Blog de Fer++

Original: [url=http://www.tiburones-rojos.com]Tiburones Rojos[/url]
URL: http://www.tiburones-rojos.com
Título: Tiburones Rojos

Original: [url=http://www.codeproject.com]Code Project[/url]
URL: http://www.codeproject.com
Título: Code Project

Original: [url=http://msdn.microsoft.com]MSDN[/url]
URL: http://msdn.microsoft.com
Título: MSDN


Y eso es todo. Sirva este código para que lo utilices directamente o bien para que lo adaptes para crear tus propias etiquetas. Nos vemos la próxima.

Post Scriptum: cuando termine la columna personalizada de SharePoint, pondré aquí el código para que veas cómo quedó.

Anuncios
Categorías:.NET Framework, C#, Código y ejemplos Etiquetas:
  1. Aún no hay comentarios.
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s