A few weeks ago, I finally finished the migration of Daniela’s old blog to a new domain and a new blogging platform: we moved from Subtext to WordPress.
The reasons of the move
There are two reason behind that move: the big ecosystem around WordPress, and the lack of a real desktop blogging tool on the Mac (combined to the outdated FCKeditor 2 that is used by Subtext).
During the migration Daniela thought about a different Information Architecture of her content spread around in the web, designed and implemented a HTML5 template and later we made it into a Wordpress theme. I’ll write about this experience in a future post, but now I want to focus on who to migrate the contents from Subtext to WordPress.
How to move contents
Exporting from Subtext and importing to WordPress should be easy since both blog engines support BlogML (Subtext natively, WordPress via a plugin), but unfortunately they support it in different way: Subtext, being one of the main advocates of BlogML support all the specs correctly, but this is not true for WordPress, so there are a few intermediate steps to take before you can import the content.
The tools needed
Before you start the process you need to download and install the following tools:
The Process
Step 1: Export posts for Subtext
The first thing to do is getting your posts out of Subtext. This is pretty easy since Subtext natively support BlogML. Logon to your blog admin, go to Options>Import/Export and click on the “Save” button in the “Export to BlogML” section. Un-check the “embed attachment” flag because WordPress will not be able to import the file.
Step 2: Post-Process the BlogML file
Subtext will base64 encode the content of posts to avoid possible problems with encodings, but unfortunately the only BlogML import plugin I found cannot support it, so you have to reconvert it to normal text. Get the Base64 to Plain converter, compile it and run it over your BlogML file. And you will then have a normal plain text BlogML export file.
Step 3: Configure your permalink structure
Whether you are changing domain or not, you have to change the permalink structure if you want to match the format of url in Subtext.
The format you have to specify is:
/archive/%year%/%monthnum%/%day%/%postname%.aspx
Otherwise if you don’t want to keep the .aspx at the end, you can set it the way you prefer, but then you have to import the contents as if you were changing domain.
Step 4: Import your contents
To import your contents you have two options:
- BlogML WP plugin: it doesn’t import the name of your categories (you will have to rename them manually after the import) but gives you a nice url rewriting table that you can use if you are changing url or permalink structure for your blog.
- Blog Migrator: imports the categories name, but doesn’t give you the old url->new url mapping and require another post-processing step.
I used the first approach because we were also moving domain, but if you are not doing it, and not even changing url structure, using the Blog Migrator will save you a bit (or a lot, depending on how many categories you have) of keystrokes.
Step 4a: Import using BlogML WP plugin
After having installed and activated the plugin, just upload the BlogML file to WordPress, and save the csv file with the url mapping.
Step4b: Import using Blog Migrator
You have to run the imported BlogML file through the Blog Migrator tool, and export it as WXR. And then go to the import section of your WordPress admin and import it as native WordPress export file.
Step 5: Move all your files
If you also changed domain, and don’t want to keep the images you had in your posts in the old location, you also have to copy all your images manually and change all the URLs. Too bad none of the BlogML import plugins can handle attachments, otherwise this would have been done automatically.
Redirecting the old url to the new one
If together with blog engine you also changed url of your blog (like Daniela did, moving from http://tech.piyodesign.it to http://tsoda.eu/) and you don’t want to loose your Google rank and links to you blog old url, you have to setup some kind of redirect.
IIS 7 makes it easy to redirect based on some basic text manipulation rules, but if you need more complicate rules, or if you don’t have access to the IIS configuration, I wrote a small ASP.NET MVC based web application that reads the CVS file exported by the WordPress BlogML importer and issues 301 Permanent redirects to the new location.
The code for this is pretty easy.
Routing
What is important to notice here is the usage of the catch-all route to get all the requests.
routes.MapRoute(
"Default",
"{*url}",
new { controller = "Home", action = "Redirect" }
Controller
The action just delegates the retrieval of the new url to the repository and finally sends a Permanent Redirect. Notice the new Url is the new blog root if no match it found.
public ActionResult Redirect(string url)
{
string newUrl = MvcApplication.MappingService.FindMapping(url);
Uri redirectTo = new Uri(new Uri(Settings.NewRoot), newUrl);
return new PermanentRedirectResult(redirectTo.AbsoluteUri);
}
The FindMapping method in the Repository
The following code is pretty easy: just a Linq query over the list of mappings
public string FindMapping(string url)
{
UrlMapping mapping = _mappings.SingleOrDefault(
m => m.OldPermalink == url);
if (mapping == null)
return String.Empty;
return mapping.NewPermalink;
}
Loading the CSV
Probably the most “complicate” part of the code.
The CSV file is in format OldPermalink,NewPermalink and it contains just absolute urls.
OldPermalink,NewPermalink
http://tech.piyodesign.it/.../157.aspx,http://www.tsoda.eu/blog/...un-div/
But the matching above is done using just the relative urls, so the loading code has to remove the blog root from the of Urls. (The oldRoot and newRoot variables are stored in the appsettings.
public void LoadMappings(string oldRoot, string newRoot)
{
string path = HttpContext.Current.Server.MapPath("App_Data");
string csvPath = Path.Combine(path, "permalinkmap.csv");
StreamReader streamReader = new StreamReader(csvPath);
while (!streamReader.EndOfStream)
{
string line = streamReader.ReadLine();
if(line.StartsWith("http://"))
{
string[] urls = line.Split(',');
_mappings.Add(new UrlMapping()
{
OldPermalink = urls[0].Replace(oldRoot,""),
NewPermalink = urls[1].Replace(newRoot,"")
});
}
}
}
The PermanentRedirectResult
It’s a custom action result that just sends a permanent redirect back to the browser (or, more importantly, search engine spider).
.NET4 version
If you are lucky to have .NET4 on your server this could be done using the new RedirectPermanent method.
public class PermanentRedirectResult : ActionResult
{
public string Url { get; set; }
public PermanentRedirectResult(string url)
{
Url = url;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.RedirectPermanent(Url);
}
}
.NET 3.5 version
Or if you are, like me, still on .NET 3.5, the ExecuteResult method has to be changed with the following code:
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Status = "301 Moved Permanently";
context.HttpContext.Response.AddHeader("Location", Url);
context.HttpContext.Response.End();
}
Or just download the code
But if you want you can download the complete solution file.
That was it
It took me a while to understand how to complete the migration without spending to much time with coding, and apparently I managed to do it (it took more time to write this post than writing all the code needed for the migration).
I hope not many people will migrate away from Subtext, but in case the need to, I hope this post helps.