adam78
adam78

Reputation: 10068

XSLT - Populate XML Form Inputs with Name Value Pair Dictionary

I have an xml template for a form which gets rendered using xslt as follows:

xml:

<form-template>
  <fields>
    <field type="text" required="true" label="Subject" class="form-control" name="subject" subtype="text"></field>
    <field type="textarea" required="true" label="Description" class="form-control" name="description" subtype="textarea" rows="3"></field>
  </fields>
</form-template>

xsl:

<xsl:template match="/*">
     <xsl:for-each select="fields/field">  
         <xsl:if test="@type='text'"> 
            <label for="{@name}"><xsl:value-of select="@label"/></label>
            <input class="form-control" data-val="{@required}" data-val-required="The {@label} field is required." id="{@name}" name="{@name}" type="text" value=""></input>
            <span class="field-validation-valid" data-valmsg-for="{@name}" data-valmsg-replace="true"></span>
          </xsl:if> 
     </xsl:for-each>  
</xsl:template>

The form is rendered using a custom HTMLHelper.

 @Html.RenderXml(Model.Template, Server.MapPath("~/Content/form.xslt"))

The values are stored as name/value pairs:

var values = new Dictionary<string, string> {{"subject", "This is the subject"}, {"description", "This is the description"}};

How can I pass these values to the xsl and iterate against each form field in the xml template, if the name matches the field name then populate the respective field with the value.

I've searched around but I cant see how you can pass an array type variable to an xsl file.

Do I convert the dictionary into a node set and pass it as argumentlist parameter?

Does anyone have any other suggestions or workarounds. some code snippet would be helpful too as I'm not great working with xsl/xml

Note I'm trying to achieve this in .Net MVC

Upvotes: 0

Views: 785

Answers (2)

adam78
adam78

Reputation: 10068

I've managed something similar to Martin Honnen answer but without moving to xslt 3.0

Below is what I did. I updated the HTML helper by adding a xsltargument and converting the dictionary into a XPathNodeIterator:

 var args = new XsltArgumentList();
 var dict = new Dictionary<string, string> {{ "subject", "This is the subject" }, { "description", "This is the description" }};
 var el = new XElement("keyvalues", dict.Select(kv => new XElement("keyvalue", new XAttribute("key", kv.Key), new XAttribute("value", kv.Value))));
 args.AddParam("keyvalues", "", el.CreateNavigator());

In my xslt I updated as follows:

<xsl:param name="keyvalues"/>  
<xsl:template match="/*">
 <xsl:for-each select="fields/field">  
     <xsl:if test="@type='text'"> 
        <label for="{@name}"><xsl:value-of select="@label"/></label>
        <input class="form-control" data-val="{@required}" data-val-required="The {@label} field is required." id="{@name}" name="{@name}" type="text" value="{$keyvalues/keyvalue[@key = current()/@name]/@value}"></input>
        <span class="field-validation-valid" data-valmsg-for="{@name}" data-valmsg-replace="true"></span>
      </xsl:if> 
 </xsl:for-each>  

Upvotes: 1

Martin Honnen
Martin Honnen

Reputation: 167571

If you want to represent a .NET Dictionary in XSLT then you might want to move to XSLT 3.0 as supported by Saxon 9.8 HE (available on NuGet and Sourceforge) where you can just convert it into an XPath 3.1 map.

Then your XSLT would look like

<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"
>
  <xsl:param name="field-map" as="map(xs:string, xs:string)" required="yes"/>

  <xsl:output method="html" indent="yes"/>

  <xsl:template match="/*">
    <xsl:for-each select="fields/field">
      <xsl:if test="@type='text'">
        <label for="{@name}">
          <xsl:value-of select="@label"/>
        </label>
        <input class="form-control" data-val="{@required}" data-val-required="The {@label} field is required." id="{@name}" name="{@name}" type="text" value="{$field-map(@name)}"></input>
        <span class="field-validation-valid" data-valmsg-for="{@name}" data-valmsg-replace="true"></span>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

(so basically you declare a parameter of type map(xs:string, xs:string) and then you can use it as a function passing in your @name map/dictionary key in the function call $field-map(@name)).

A C# snippet to run the above, passing in the dictionary and writing out a string with the transformation result is

        var values = new Dictionary<string, string> { { "subject", "This is the subject" }, { "description", "This is the description" } };
        XdmMap mapParam = XdmMap.MakeMap(values);

        Processor proc = new Processor();

        Uri baseUri = new Uri(new FileInfo(".").FullName);

        XmlResolver resolver = new XmlUrlResolver();


        Xslt30Transformer transformer = proc.NewXsltCompiler().Compile(resolver.ResolveUri(baseUri, "sheet1.xslt")).Load30();

        transformer.SetStylesheetParameters(new Dictionary<QName, XdmValue> { { new QName("field-map"), mapParam } });

        Serializer serializer = proc.NewSerializer();

        using (StringWriter sw = new StringWriter())
        {
            serializer.SetOutputWriter(sw);
            using (FileStream fs = File.OpenRead(@"template1.xml"))
            {
                transformer.ApplyTemplates(fs, serializer);
            }
            Console.WriteLine(sw.ToString());
        }

Output I get is e.g.

<label for="subject">Subject</label><input class="form-control" data-val="true" data-val-required="The Subject field is required." id="subject" name="subject" type="text" value="This is the subject"><span class="field-validation-valid" data-valmsg-for="subject" data-valmsg-replace="true"></span>

so the value attribute has been set.

Upvotes: 0

Related Questions