Unicorn 1.0.3 Released

I’ve pushed Unicorn 1.0.3 to NuGet. This release fixes a number of bugs, improves logging, and removes some annoyances:

  • Deleting a whole tree could cause the app pool to be killed due to a bug in Sitecore’s serialization event handler (380479) - this fix expands on a previous workaround to cover recursive deletes
  • Fixed an issue where if you changed an item too rapidly after saving or moving it, you could get file read errors or spurious “conflicts” reported due to the async ShadowWriter still being in process.
  • “Inconsequential” item saves are now ignored. For example, the Template Editor gleefully changes the revision and last updated date of every template field when it saves, not merely changed ones. Those, as well as rename events where the name does not actually change (again, Template Editor ftw), are ignored and updated files are not written to disk. These fields are also ignored when calculating serialization conflicts on item save.
  • Items deleted by Unicorn are now placed in the recycle bin instead of completely removed, so they can be restored if mistakenly deleted.
  • Unicorn sync logs are automatically written to the Sitecore log as well (requires dependency upgrade to Kamsar.WebConsole 1.2.2 for the new TeeProgressStatus)

A subtle error: using System.IO.Path in a HTTP context

Here’s a fun little issue that you might eventually run across. Suppose you’re writing some code that needs to retrieve the extension from a URL. For example, a URL rewriter or Sitecore pipeline that acts only on certain file types.

You might think, as I would have, “oh, that’s built in to .NET - we’ll just use System.IO.Path.GetExtension()!”

And that would work, almost all of the time. The only issue comes from an internal implementation detail of IO.Path: it checks that the path does not contain characters that are invalid in a filesystem path. Specifically, “, <, >, |, and ASCII unprintables (< 32). Well, those characters (except perhaps the unprintables) are valid in a URL - so trying to get the extension of a URL path containing these characters will throw a nice fat exception:

System.ArgumentException: Illegal characters in path.
   at System.IO.Path.CheckInvalidPathChars(String path)
   at System.IO.Path.GetExtension(String path)

Unfortunately there is no way to disable this behavior - which is logical, given the purpose of IO.Path as a processor for file system paths, not URLs. Not even first parsing the URL using the System.Uri class will fix this, as this StackOverflow question suggests. The LocalPath property still includes the invalid characters that break Path.GetFileName() or Path.GetExtension().

There are a couple of ways I could see solving this problem. The first, and simplest - though possibly prone to security issues, would be to replicate what Path.GetExtension() does but omitting the invalid characters check. Reference:

int length = path.Length;
for (int i = length; --i >= 0; )
{
    char ch = path[i];
    if (ch == '.')
    {
        if (i != length - 1)
            return path.Substring(i, length - i);
        else
            return String.Empty;
    }
    if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
        break;
}
return String.Empty;

The second would be to remove any invalid characters prior to calling Path.GetExtension():

string url = "\"http://mo\"nkeys/foo/bar/\"";

var invalidChars = Path.GetInvalidPathChars().ToHashSet();
for (int i = url.Length - 1; i >= 0; i--)
{
    // technically you could use a stringbuilder, but since 99% of the time this won't ever be used, this seems optimized enough
    if (invalidChars.Contains(url[i])) url = url.Remove(i, 1);
}

var ext = Path.GetExtension(url);

Personally I like the second solution, since you’d benefit from any upstream fixes in Path.GetExtension() in future framework releases.

Announcing the release of Synthesis and Blade

For years at ISITE Design we’ve been inventing ways of making Sitecore development easier and more fun. Today I’m happy to announce that some of those tools we’ve been using internally for ages are now publicly available for everyone to use.

Synthesis

Synthesis is an object mapping framework that automatically generates strongly typed C# classes to represent your Sitecore templates. While this sort of library is nothing new in Sitecore circles, Synthesis brings some unique features to the table:


  • A universal object interface for Sitecore 7. The same generated objects that work in database queries also work great in LINQ against an index. If you access data that isn’t in the index, they’ll even transparently load the item from the database for you.

  • Automatic LINQ query filtering. You won’t have to remember to filter by template, language, and version when you query LINQ with Synthesis.

  • Generation of an interface hierarchy to represent template inheritance. This brings a whole world of power to you by enabling you to write polymorphic renderings. Nearly all of your interaction can be done by interface.

  • Automatic checking to see if your generated classes match what’s defined in Sitecore

  • Extensible field API allows you to automatically map custom field types onto a strongly typed version of themselves. Ever dealt with custom XML fields before? This makes them awesome.

All of the other expected features of an object mapper are also available, such as:


  • Full support for Page Editor

  • Support for standard field types such as images, multilists, and rich text fields

In Action

There are some examples of using Synthesis on the GitHub page.

Getting Synthesis

Synthesis is available now on NuGet for a one-click install. You can also see the source on GitHub, or review the documentation.

Which NuGet package should I download? For installing into a web project, use the Synthesis package - it comes with configuration files. If you’re referencing it from a library, use the Synthesis.Core package which is binary only.

Blade


While Synthesis handles the mapping side, Blade handles making renderings awesome and has some nice integration points with Synthesis.

Blade uses the MVP pattern to allow you to create very DRY, single-responsibility renderings, allowing you to focus on presentation code and not data access. With Blade, your renderings only request the type of model they expect. For example:

public partial class SampleSublayout : UserControlView<MyViewModelClass>

Then Blade steps in and maps the model type onto a Presenter class, which defines how to supply the model type. For example:

public class SamplePresenter : SitecorePresenter<MyViewModelClass>
{
    protected override MyViewModelClass GetModel(IView view, Item dataSource)
    {
        var model = new MyViewModelClass();
        model.Example = dataSource.DisplayName;

        return model;
    }
}

This enables simple isolation of both views and presenters for writing testable frontends, and looser coupling even if you aren’t testing. Don’t worry, not every rendering needs a presenter. If you’re just using a model type from a Sitecore object mapper such as Synthesis (via the Synthesis.Blade package), Glass, or Compiled Domain Model, you can make that automatically map to the data source item without defining an explicit presenter.

Ubiquitous Razor 2 Support

Blade also brings first-class support of the Razor 2 templating language into Sitecore, without using the Sitecore MVC functionality. This is advantageous when developing sites that use modules that are not compatible with the Sitecore MVC rendering technology - you can reap the benefits of MVC-style renderings without the hassle of implementing renderings twice or resorting to XSLT. A Razor rendering with Blade works exactly like a sublayout - only the path is to a cshtml file instead of an ascx. There are also many useful helpers included that simplify things like rendering dates, images, and controlling page editor functionality. For example:

@inherits RazorRendering<MyProject.MyViewModelClass>
<div>
<h1>@Html.Raw(Model.Example)</h1>

@Html.TextFor(model => model.SitecoreDataField)
</div>

Blade does its best to feel as much as possible like Sitecore as well as ASP.NET MVC, so it doesn’t require digesting lots of unfamiliar ways of doing things. Sitecore features such as Page Editor are fully supported. MVC conventions like automatic HTML encoding, partial views, anonymous types defining HTML attributes, and relative view referencing are all there.

Awesome Rendering Diagnostics

Ever spent an hour figuring out why a rendering was busted, only to realize it was output cached without you realizing it? What about having to find a rendering file for a HTML/CSS dev so they can make markup changes? You’ll love Blade then. Blade’s rendering types emit HTML comments that indicate where a specific rendering begins and ends, as well as if it was output cached, when, and what the cache criteria were. The comments disappear when dynamic debug compilation is disabled, leaving production sites pristine.

Check this out:



<!– Rendering was output cached at 5/16/2013 9:31:42 PM, ClearOnIndexUpdate, VaryByData, VaryByQueryString –>

<h1>Hello, world!</h1>
<!– End Rendering ~/layouts/Sample Inner Sublayout.ascx, render took 0ms –>


Full Sitecore 7 Support



Blade supports the enhancements made to renderings in Sitecore 7. It supports renderings that decache on index update, have multiple items as data sources, index-query based data sources, and even defining your own data source resolution logic. Even better, it removes boilerplate code by automatically normalizing all kinds of data source specifications back into simple Item (or Item[]) instances.

Getting Blade

Blade is available now on NuGet (and its Synthesis integration package) for a one-click install. Make sure you have Sitecore Rocks configured and connected before installing Blade as it uses Sitecore NuGet to install some supporting items. You can also see the source on GitHub, or review the documentation.

Which NuGet package should I download? For installing into a web project, use the Blade (and/or Synthesis.Blade) package - it comes with configuration files. If you’re referencing it from a library, use the Blade.Core (and/or Synthesis.Blade.Core) package which is binary only.

Feedback

I’d love to hear what you think about these libraries or answer questions about them. Feel free to comment below, submit issues on the GitHub repositories (bonus points if there’s a PR attached!), tweet @kamsar, or yell “hey you!” over the back fence.

Unicorn now available on NuGet

Unicorn, a library I wrote to handle automatically serializing Sitecore content items into a source control system, is now available on NuGet.

Be sure to review the post-installation steps in the GitHub README file - if used incorrectly, Unicorn can be dangerous to your items.

In other news, I began a major refactoring of Unicorn this weekend that will bring it a ton of new features mainly in the form of extensibility. Don’t want it to delete orphaned items? Custom comparison to determine updating? Implement your own Evaluator. Want to serialize to SQL? Use a custom serialization format that ignores versions? Sweet - make a SerializationProvider. Want to filter serialization based on anything you can imagine (like the rules engine? an Excel spreadsheet? presence of a version in a specific language?)? That’s where a Predicate comes in.

At the moment “Unicorn 2.0” is not yet fully stable. I plan to add a few more things to it such as detecting duplicate item IDs during processing and possibly a “what-if” mode. I’ll be working on it time permitting :)

WebConsole now available on NuGet

My handy WebConsole library is now available from NuGet for easier installation.


This release also fixes an issue where disposal was working in a rather silly fashion on sub-tasks.


If you’re unaware of this library, it’s a very useful thing to use if you find yourself writing “hack pages,” “processor pages,” “rebuild pages” or other things of the like on a regular basis. Any long running or complex task you’d run from a page can easily get a realtime progress bar, console-style logging, exception handling support, and the ability to break the task up into ‘subtasks’ with their own tracked progress.


In short, you can make your processing pages more informative and even vaguely pretty if you like the console aesthetic, in practically no extra time over simply writing them in the first place. I wrote this for myself, and I use it in a bunch of things :)

Scoping searches to the current context in Sitecore 7's LINQ

As a followup to my previous post about gotchas with Sitecore 7’s LINQ provider, here’s another thing to consider.


The indexing providers are very greedy about indexing. This means that unlike with traditional querying with Sitecore.Data.Item, where your results are automatically filtered by the context language and latest item version, no such filtering occurs with LINQ. You will receive all versions and all languages unless you specify otherwise.


As you might imagine, this can result in unexpectedly large quantities of search results in some cases. It can also be extra sneaky since during development you might only have one version in one language - so you wouldn’t even notice the issue.


So how do you fix the issue? First, let’s talk about versions. The default indexing configuration includes a field called _latestversion.
This is a boolean field that is only set to true for items that are the latest version in their language. We can take advantage of this by implementing a property on our mapped objects and mapping it to this index field like so:

[IndexField(“_latestversion”)]
public bool IsLatestVersion { get; set; }



Then, when we write a query we want to limit, we simply add a clause to the query:

.Where(x => x.IsLatestVersion)

// alternatively if you don’t want to be strongly typed and have an indexer, you can use
.Where(x=> x[“_latestversion”] == “1”)


Now you’ll only get the latest version. Now for languages, which are also pretty simple. If you’re inheriting from the SearchResultItem class, you already have a Language property. Otherwise you can add one like so:

[IndexField(“_language”)]
public string Language { get; set; }



Then, we add the following clause to the query:

.Where(x => x.Language == Sitecore.Context.Language.Name)

Now we get results like regular queries. If you’re like me, the next question you’re asking is “how can I just write this once and forget about it?” For example something like:

public static IQueryable<T> GetFilteredQueryable<T>(this IProviderSearchContext context)
    where T : MyItemBaseClass
{
    return context.GetQueryable<T>().Where(x => x.Language == Sitecore.Context.Language.Name && x.IsLatestVersion);
}

Unfortunately, this seems to be nigh impossible with the current revision of Sitecore 7. The issue has to do with how LINQ resolves expressions involving generic types that are not resolved at compile time. Effectively the expression in the example above converts to:

.Where(x=> ((T)x).Language == Sitecore.Context.Language.Name)

Notice the cast to T? That throws the expression parser for a loop. I’ve been told this will be fixed in a later release of Sitecore 7, but will not be part of the RTM release, so for the moment it looks like we’re writing filtering on each query.

Sitecore 7 LINQ gotchas

The upcoming Sitecore 7 release brings with it a new “LINQ-to-provider” architecture that effectively allows you to query search indexes using standard LINQ syntax. Mark Stiles wrote a pretty good synopsis of the feature that you should probably read first if you’re unfamiliar with the basics of how it works. This post won’t cover the basics.


I’ve been diving in to the underpinnings of the LINQ architecture and have discovered a number of things that may well cause confusion when people start using this technology. Be warned that this post is based on a non-final release of Sitecore 7, and may well contain technical inaccuracies compared to the final release.


You have to enable field storage to get output

By default, the values of fields are not stored in the index. If the values are not stored, you can query against the index using custom object mapping (e.g. filters work), but you will not see any field values mapped into results objects. You can define field storage parameters either on a per-field basis, or a per-field-type (e.g. Single-Line Text) in the default index configuration file (in App_Config/Include).

Changes to the storage type require an index rebuild before the storage is reflected.

LINQ is not C Sharp

Yeah, you heard me right. LINQ may look exactly like C#, but it is not parsed the same way. The lambda expressions you may use to construct queries against Sitecore are compiled into an Abstract Syntax Tree (AST) - sort of a programmatic representation of the code forming the lambda - and that is in turn parsed by the Sitecore LINQ provider.

The code you write into lambdas is not executed as normal C# code is. This is important to remember, because effectively the LINQ provider is simply mapping your query in as simple terms as possible to a key-value query inside of Lucene (or SOLR, etc). For example:

// we'll use this as a complex property type to map into a lucene object<br>
public class FieldType {
    public string Value { get; set; }
    public string SomeOtherValue { get; set; }
}
// this will be the class we'll query on in LINQ
public class MappedClass {
    [IndexField("field1")]
    public FieldType Field1 { get; set; }
}

// example queries (abbreviated code)
var query1 = context.GetQueryable<mappedclass>().Where(x=>x.Field1.Value == "foo");
var query2 = context.GetQueryable<mappedclass>().Where(x=>x.Field1.SomeOtherValue == "foo");

You’d expect query1 and query2 to be different wouldn’t you? NOPE. You’re not writing C# here, you’re writing an AST in C# syntax. The Sitecore LINQ engine takes the shortest path to a key-value query pair. What this really means is that it:

  1. Resolves the Lucene field name of the field you’re querying on (in this case, “field1”)

  2. Evaluates the operator you used, and the constant value you’ve assigned to compare to

  3. Constructs a Lucene query expression based on that

In effect, you can only ever have one value queried for each property. In the example above both examples are in effect x.Field1 == "foo". The query would be that way even if you did a crazy query like x.Field1.Foo.Bar.Baz.Boink.StartsWith("foo") - that would boil down to x.Field1.StartsWith("foo").

There is a facility you can tie into that controls how Sitecore converts types in queries (TypeConverters). Unfortunately, that does not solve the problem of disambiguating properties - the conversion only informs the engine how to convert the constant portion of the query (in this case, the string.

Sitecore LINQ does not care if the return entity type is mapped to a valid object for it

If you execute a query against a type, say the MappedClass type in the previous example, the LINQ layer will map all results of the query against the MappedClass type. Sounds great, but be careful - it will also map results that may not have the expected template to map to the MappedClass type onto it.

For example, suppose I made a model for the Sample Item template that comes with Sitecore. Then I queried the whole database as SampleItem. Out of my results, probably only two are really designed to be mapped to my SampleItem - the rest will have nulls everywhere. This is potentially problematic if you forget to be specific in your queries to limit the template ID to the expected one.

You must enumerate the results before the SearchContext is disposed

If you’ve ever dealt with NHibernate or other lazy-loaded ORMs, this might make perfect sense to you. A typical search query method might look something like this:

public IEnumerable<MappedClass> Foo()
{
    var index = SearchManager.GetIndex("sitecore_master_index");
    using (var context = index.CreateSearchContext(SearchSecurityOptions.EnableSecurityCheck)) {
        return context.GetQueryable<mappedclass>().Where(x=>x.Name == "foo");
    }
}

Can you guess the problem? IEnumerable doesn’t actually execute until you enumerate the values (e.g. run it through foreach, .ToArray(), etc). If you return the IEnumerable<mappedclass>, it cannot be enumerated until the SearchContext has already been disposed of. Which means that will throw an exception!

To avoid this problem you need to either:

  • Return a pre-enumerated object. Usually the simplest form of this is simply to return the query with .ToArray() at the end, thus enumerating the query into an array before the context is out of scope. Warning: This also means that you should filter as far as possible within the query, including paging, especially with large result sets.

  • Execute all the code within the context’s scope. This is probably less desirable as it either means spaghetti code in your codebehind, or a request-level context manager like NHibernate sometimes does.

The object mapper uses cached reflection to set each mapped property

Yes, it uses “the R word.” Reflection is great, but it’s not all that fast. This is fine if you’re running optimized code: you’ll want to perform pagination and filtering within Lucene, and avoid returning large quantities of mapped objects. The performance is dependent on both the number of objects and the number of properties you have on each object as each property results in a reflection property set.

I would suggest trying to keep the number of mapped objects returned under 100 in most cases, and/or using output caching to minimize the effect of reflection.

It’s also possible to patch the way the mapping code works (I have a reflection-less proof of concept that in relatively unscientific tests is about 50-100x faster than the reflection method. It enforces some code generation requirements however and is not as general purpose or as simple to use as the reflection method.

It’s still awesome

While this post has largely focused on unexpected gotchas around the Sitecore LINQ provider, it’s actually pretty darn nice once you get used to its quirks. It’s certainly loads easier to use than any previous Lucene integration, and the backend extensibility allows for all sorts of interesting extensions and value storage.

New Open Source Projects

I’ve recently released several new open source projects on GitHub:


Kamsar.WebConsole


A library that makes it easy to build progress-reporting “console” pages in websites. These are useful for pages that execute long-running processes like search index rebuilds, CMS publishing, etc.




An example WebConsole page using the default styling




https://github.com/kamsar/Kamsar.WebConsole


Beaver




Beaver is a flexible build/configure/deploy system for ASP.NET projects based in PowerShell. It is designed to simplify the task of maintaining builds that may span many target environments (such as dev, QA, production) where each has its own slightly different configuration requirements.





Everything in Beaver is based on a consistent, simple idea: the “pipeline.” Pipelines are simply a folder that contain “buildlet” files that perform tasks in alphabetical order. Their structure is quite similar to POSIX System V-style init scripts, only instead of a pipeline per runlevel there’s a pipeline per build stage. Buildlets can be written in several domain-specific languages (PowerShell, XDT Transform, MSBuild, or even sub-pipelines), and you can implement your own buildlet provider if you wish. Using this architecture allows you to implement small, easily digestible single-responsibility units of action - as well as gain an immediate understanding of how the build process works by simply looking in a few folders and seeing visually the order things run in and their descriptions.





To manage multiple target environments, Beaver implements a powerful inheritance chain of pipelines. Archetypes are a way to extend the global pipelines to perform a specific sort of configuration that might apply to multiple environments - for example, configuring live error handling or hardening a CMS site’s content delivery-type servers. Environments then declare themselves as using 0-n archetypes (in addition to being able to extend the global pipelines themselves), allowing them to inherit cross-environment configurations.





Beaver was originally designed to support build and deploy operations for the Sitecore CMS. However, Sitecore is merely a set of archetypes - the system can be used for any sort of ASP.NET project.







Unicorn



Unicorn is a utility for Sitecore that solves the issue of moving templates, renderings, and other database items between Sitecore instances. This becomes problematic when developers have their own local instances - packages are error-prone and tend to be forgotten on the way to production. Unicorn solves this issue by using the Serialization APIs to keep copies of Sitecore items on disk along with the code - this way, a copy of the necessary database items for a given codebase accompanies it in source control.





Unicorn avoids the need to manually select changes to merge unlike some other serialization-based solutions because the disk is always kept up to date by the event handlers. This means that if you pull changes in from someone else’s Sitecore instance you will have to immediately merge and/or conflict resolve the serialized files in your source control system - leaving the disk still the master copy of everything. Then if you execute the sync page, the merged changes are synced into your Sitecore database.





Installing Unicorn is very simple and using it is nearly automatic as long as you follow the conventions that allow it to work (local databases required).




Selecting a Sitecore Rendering Technology



Sitecore has a dizzying array of ways you can write renderings. While there is documentation that explains what they are, there’s a lot less about where each kind should be used. This will attempt to explain when you should use each kind of rendering.


So what kinds of renderings are there?

  • Sublayouts (UserControls/.ascx)

  • Web controls (WebControl/.cs)
  • Razor renderings (.cshtml) - Sitecore MVC-style
  • Razor renderings (.cshtml) - WebForms-style
  • Method renderings (.cs)
  • URL renderings
  • XSLT renderings (.xslt)
  • Custom - yes, you can write your own rendering provider


  • That’s a lot of ways to emit HTML to a page. Let’s take a look at each kind and examine their strengths and weaknesses.


    Sublayouts (User Controls)


    These are probably the kind of rendering you should be using most of the time unless you have Razor at your disposal. Sublayouts are confusingly named because most of the time they are simply a rendering and not an actual subdivision of a layout. These are basically identical to a traditional .NET User Control - they have a page lifecycle, code behinds, events and all the other traditional Web Forms trappings. They have a relatively HTML-like appearance that makes them sensible to edit if you have HTML/CSS folks collaborating with you, unlike the C#-based renderings.


    However they also have the same issues as their User Control cousins. Web Forms’ at times utterly verbose syntax and confusing event/postback model can introduce bugs. Highly branched markup emission is also very hard in User Controls because the markup is all encoded in the .ascx file, and you have to resort to MultiViews or PlaceHolders and setting a ton of Visible properties to make it work.

    Verdict: Use these for relatively static markup emission or places where the Web Forms event model will help you - like custom form renderings.


    Web Controls


    Web controls are simply C# classes that derive from the Sitecore WebControl base class. Web controls are perfect if you have to do a rendering whose markup has a lot of branching, for example a list that might have two or three different kinds of elements in the list because you can modularize the rendering into C# methods.


    On the other hand WebControls can be extremely hard to read if not written in a disciplined manner. There is no obvious HTML emission, so you’ll have trouble with HTML/CSS folks when they need to change markup or add a class - it’s all in C#. You can also write spaghetti renderings that are very hard to follow how the code runs. You also have to remember that unless you override the GetCachingID() method, your WebControl cannot be output cached.

    Verdict: Use these for places where you need tight control over HTML emitted, or have a lot of branches in your markup emission.


    Razor Renderings (MVC-style)


    Razor is a templating language traditionally associated with ASP.NET MVC. It dispenses with a lot of the usually unnecessary page lifecycle of a Web Form for a more simple template that is both readable as HTML and allows a decent amount of flexibility in terms of composing branch-heavy renderings. If you’re using Sitecore’s MVC support it’s a no-brainer to use Razor renderings for nearly all purposes.


    However to use the built in Razor support you must use Sitecore’s MVC mode - which means you have to do everything in Razor. You also have to register controllers and views as items, and lose a lot of module support - for example, Web Forms for Marketers cannot presently run in MVC mode. At present this makes it nearly untenable to implement most real world sites using Sitecore’s MVC mode.

    Verdict: If you’ve got a Sitecore MVC site, use it.


    Razor Renderings (custom)


    There are a couple of third party shared source modules (such as Glass) that have implemented a custom rendering type that invokes the Razor template engine outside of a MVC context. This means you could reap the benefits of a Razor template’s syntax, without needing to resort to Sitecore’s shaky configuration-over-convention MVC implementation. These are dependent on how you feed the view data to the Razor rendering however, and each implementation works slightly differently.

    Verdict: If you’re using one of these modules, you’ll probably implement most of your renderings in Razor without needing Sitecore MVC


    Method Renderings, URL Renderings


    These render output from a C# method that returns a string, and a given URL respectively.


    Verdict: There’s almost no good use case for either of these rendering types.


    XSLT Renderings


    These were once the main type of rendering in use. They use extended XSLT syntax to transform an item “XML” into a rendering. While they can be useful for EXTREMELY simple renderings, they do not scale well to any level of complexity. Most very simple renderings may at some point gain a measure of complexity, which would then mean either very ugly and slow XSLT or a complete rewrite in a different rendering technology. Do yourself a favor and save the rewrite - use something other than XSLT in the first place.

    Verdict: Don’t use.



    Feel free to take these recommendations with a grain of salt. These are my opinions, based on the projects I’ve worked on. Your project may come across good reasons why I’m absolutely wrong about one of these options. Keep your eyes open :)