Blog Arolla

Linq provider : un essai… partie 5

Améliorons la boite à outils : construisons nos propres visualisations d’arbres d’expression.

Dans les deux derniers billets, Linq provider : un essai… partie 3 et Linq provider : un essai… partie 4, j’ai inclus des exemples de visualisations d’arbres d’expression. Je les ai construit en utilisant un pattern Visiteur (et oui, encore !), qui produit du markup HTML. Il s’agit ici d’un bon exemple de ce que ce pattern permet d’accomplir, et je vais donc en profiter pour expliquer son fonctionnement.

La visualisation d’expression qui est générée est basée sur du code HTML / CSS3, en utilisant le code présenté à l’adresse suivante: http://thecodeplayer.com/walkthrough/css3-family-tree. Pour résumer, le markup est formé de listes non ordonnées (balises “<ul>” et “<li>”) qui contiennent elles-même d’autres listes. Pour des raisons techniques, je n’ai pas pu injecter simplement le CSS nécessaire à la visualisation des arbres en HTML directement sur ce blog, mais vous pouvez regarder le code HTML généré en inspectant le DOM des deux exemples suivants : arbre 1 et arbre 2.

Le générateur de visualisation est une classe nommée TreeVisualizer, qui hérite de la classe ExpressionVisitor :

internal class TreeVisualizer : ExpressionVisitor
{
    private StringBuilder builder;
    private HtmlTextWriter writer;

    private TreeVisualizer()
    {
        this.builder = new StringBuilder();
        this.writer = new HtmlTextWriter(
            new StringWriter(
                this.builder,
                CultureInfo.InvariantCulture),
            "    ");
    } [...]

Le constructeur de cette classe est privé, la seule manière d’utiliser la classe est par l’intermédiaire de la méthode statique BuildVisualization :

internal static string BuildVisualization(Expression expression)
{
    TreeVisualizer visualizer = new TreeVisualizer();
    visualizer.Visit(expression);
    return visualizer.Visualization;
}

La propriété retournée par la méthode, Visualization, est définie comme ceci :

public string Visualization
{
    get
    {
        return this.builder.ToString();
    }
}

La classe utilise également deux méthodes utilitaires :

  • GetSimplifiedType, qui permet d’obtenir une représentation textuelle d’un type donné.
private string GetSimplifiedType(Type type)
{
    if (!type.IsGenericType)
        return type.Name;

    string genericName = type.Name.Split('`').First();

    string genericArguments = string.Join(
        ", ",
        type.GenericTypeArguments.Select(
            t => GetSimplifiedType(t)));

    return string.Format(
        "{0}<{1}>",
        genericName,
        genericArguments);
}
  • VisitAndBuildTree, qui ajoute du contenu au HtmlTextWriter, en fonction des nœuds de l’arbre visité :
private Expression VisitAndBuildTree(
    string nodeName,
    string nodeType,
    string nodeDescription,
    Func<Expression> childrenVisitorFunction = null)
{
    this.writer.RenderBeginTag("li");
    this.writer.WriteLine();
    this.writer.Indent++;

    this.writer.AddAttribute("href", "#");
    this.writer.RenderBeginTag("a");

    this.writer.AddAttribute("class", "node-name");
    this.writer.RenderBeginTag("span");
    this.writer.WriteEncodedText(nodeName);
    this.writer.RenderEndTag();
    this.writer.WriteBreak();

    if (!string.IsNullOrEmpty(nodeType))
    {
        this.writer.AddAttribute("class", "node-type");
        this.writer.RenderBeginTag("span");
        this.writer.WriteEncodedText(nodeType);
        this.writer.RenderEndTag();
        this.writer.WriteBreak();
    }

    this.writer.WriteEncodedText(nodeDescription);
    this.writer.RenderEndTag();
    this.writer.WriteLine();

    Expression baseReturn = null;
    if (childrenVisitorFunction != null)
    {
        this.writer.RenderBeginTag("ul");
        this.writer.WriteLine();
        this.writer.Indent++;

        baseReturn = childrenVisitorFunction();

        this.writer.Indent--;
        this.writer.RenderEndTag();
        this.writer.WriteLine();

    }

    this.writer.Indent--;
    this.writer.RenderEndTag();
    this.writer.WriteLine();

    return baseReturn;
}

Grâce aux deux fonctions précédentes, nous pouvons désormais surcharger chacune des méthodes VisitSomeTypeOfNode et insérer les détails appropriés dans le HTML. Par exemple :

protected override Expression VisitBinary(
    BinaryExpression node)
{
    return VisitAndBuildTree(
        "Binary",
        string.Empty,
        node.NodeType.ToString(),
        () => base.VisitBinary(node));
}
protected override Expression VisitLambda<T>(
    Expression<T> node)
{
    return VisitAndBuildTree(
        "Lambda",
        GetSimplifiedType(node.Type),
        node.ToString(),
        () => base.VisitLambda<T>(node));
}
protected override Expression VisitMethodCall(
    MethodCallExpression node)
{
    return VisitAndBuildTree(
        "Call",
        GetSimplifiedType(node.Type),
        node.Method.Name,
        () => base.VisitMethodCall(node));
}

Il y a de nombreuses méthodes, je ne vais pas toutes les reproduire dans ce billet. Je vais juste finir avec une méthode particulière : VisitConstant. Cette méthode sert à représenter un nœud de type constante, et il est intéressant de faire figurer des informations sur la valeur de la constante contenue dans le nœud. J’ai géré deux cas particuliers, lorsque la constante est un IEnumerable, et lorsqu’il s’agit d’une chaîne de caractères.

protected override Expression VisitConstant(
    ConstantExpression node)
{
    string type = GetSimplifiedType(node.Type);

    string value;
    if (node.Type.IsGenericType
        && node.Type.FindInterfaces(
            (t, o) => t.Name.StartsWith("IEnumerable"),
            true).Any())
    {
        value = type;
    }
    else if(type == "String")
    {
        value = string.Concat(
            "\"",
            ((string)node.Value).Replace("\"", "\\\""),
            "\"");
    }
    else
    {
        value = node.Value.ToString();
    }

    VisitAndBuildTree(
        "Constant",
        type,
        value);
    return base.VisitConstant(node);
}

Ainsi, nous pouvons visualiser les valeurs des constantes pour les types simples dans l’arbre, et une description simplifiée du type de données lorsque la constante est un IEnumerable.

Dans le prochain billet, nous nous attacherons à améliorer la testabilité du provider, à partir du feedback obtenu lors de la Jam de Code Arolla du mois de juin.

Site Web | Plus de publications

Contributeur enthousiaste

1 comment for “Linq provider : un essai… partie 5