When I first got into web development, one of my first projects was to create a custom blog for myself. Apart from the sheer necessity of needing a blog at the time, I embarked on this coding journey because I had read somewhere that developing a blog would provide a good introduction to the nitty-gritty of application design.

While this wasn’t 100% accurate, it also was not terrifically far from the truth. Through many struggles and achievements, I finally wound up with my own custom blog, complete with commenting system, RSS delivery, and eventually automatic posting to Twitter.

This modest blog served me pretty well for a few months, but I quickly outgrew it. I found myself pouring precious hours into little development projects to try to get it to do cool stuff that I came across in other, more robust systems.

Eventually, however, I ran out of time and motivation. First, the continual development stopped. Then, out of total laziness, important things like bug fixes and comment-security fell to the wayside. While I still loved to blog, the sheer effort of posting (my interface was a bit clunky…) was a big hindrance, so the posts became quite sparse.

Then I got my iPhone. More than anything, this killed my blogging life because my site was just not robust enough to talk to my phone, and I was tired of having to boot up my dinosaur of a PC to make a post.

Finally, I decided to make the move to WordPress, and so here I am.

Now, as you probably know, WordPress offers a pretty nice selection of import tools, and there are plenty of third-party add-ons available for platforms that WordPress doesn’t officially provide import scripts for. What no one really offers, however, is custom blog importing. So the problem that faced me was how, exactly, to import not just one, but two custom blogs into WordPress without the nightmare of moving each entry post by post (not to mention comments!).

Well, it turns out that while WordPress doesn’t have an automagic custom blog importer, their custom XML schema is pretty darn smart, and it’s super-easy to roll a custom script from your own blog to write out an XML file that the WordPress importer will understand.

So enough with the blah-blah-blah. Let me show you what I did.

First, you need to know the important stuff about the WordPress XML file. The best thing I found was to create a throw-away WordPress site and make a few posts. For each of these, add both categories and tags (if these are something you have on your custom blog), and make a few comments as well.

Your dump (well, the important bits at least) should look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:excerpt="http://wordpress.org/export/1.0/excerpt/"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:wp="http://wordpress.org/export/1.0/">
     <channel>
          <title>Myblog's Blog</title>
          <link>http://existdissolvetest.wordpress.com</link>
          <description>Just another WordPress.com site</description>
          <pubDate>Sat, 29 May 2010 09:42:53 +0000</pubDate>
          <generator>http://wordpress.org/?v=MU</generator>
          <language>en</language>
          <wp:wxr_version>1.0</wp:wxr_version>
          <wp:base_site_url>http://wordpress.com/</wp:base_site_url>
          <wp:base_blog_url>http://existdissolvetest.wordpress.com</wp:base_blog_url>
          <generator>http://wordpress.com/</generator>
          <cloud domain='existdissolvetest.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
          <image>
               <url>http://www.gravatar.com/blavatar/96e3fd278d482433a8edd02c65dc667d?s=96&d=http://s2.wp.com/i/buttonw-com.png</url>
               <title>Myblog's Blog</title>
               <link>http://existdissolvetest.wordpress.com</link>
          </image>
          <atom:link rel="search" type="application/opensearchdescription+xml" href="http://existdissolvetest.wordpress.com/osd.xml" title="Myblog's Blog" />
          <atom:link rel='hub' href='http://existdissolvetest.wordpress.com/?pushpress=hub'/>
          .....[rest of posts/comments go here].....
     </channel>
</rss>

Pretty straightforward. This is the guts of your import–tells WordPress everything it needs to know about your blog. I’m not sure what’s really needed here or not…I just left it alone for my purposes. The most important part is what comes next…populating the posts, categories, tags, and comments from your custom blog.

Let’s start with the posts. I’m using ColdFusion, so here’s what I did to create the “posts” section of this XML file:

<cfoutput query="getposts">
<item>
     <title>#trim(posttitle)#</title>
     <cfset urltitle = trim(lcase(replace(posttitle,' ','-','all'))) />
     <link>http://existdissolve.wordpress.com/#dateformat(postdate,'yyyy')#/#dateformat(postdate,'mm')#/#dateformat(postdate,'dd')#/#urltitle#/</link>
     <cfset pubdate = "#dateformat(postdate,'dd mmm yyyy')# #timeformat(postdate,'HH:mm:ss')# +0000" />
     <pubDate>#pubdate#</pubDate>
     <dc:creator><![CDATA[existdissolve]]></dc:creator>
     <cfloop from="1" to="#listlen(posttags)#" index="i">
          <category domain="tag"><![CDATA[#trim(listgetat(posttags,i))#]]></category>
          <category domain="tag" nicename="#lcase(replace(trim(listgetat(posttags,i)),' ','-','all'))#"><![CDATA[#trim(listgetat(posttags,i))#]]></category>
     </cfloop>
     <guid isPermaLink="false"></guid>
     <description></description>
     <content:encoded><![CDATA[#postcontent#]]></content:encoded>
     <excerpt:encoded><![CDATA[]]></excerpt:encoded>
     <cfset postdate = "#dateformat(postdate,'dd mmm yyyy')# #timeformat(postdate,'HH:mm:ss')#" />
     <wp:post_date>#postdate#</wp:post_date>
     <wp:post_date_gmt>#postdate#</wp:post_date_gmt>
     <wp:comment_status>open</wp:comment_status>
     <wp:status>publish</wp:status>
     <wp:post_type>post</wp:post_type>
     <wp:post_password></wp:post_password>
     <wp:is_sticky>0</wp:is_sticky>
     <wp:postmeta>
          <wp:meta_key>superawesome</wp:meta_key>
          <wp:meta_value><![CDATA[false]]></wp:meta_value>
     </wp:postmeta>
     <wp:postmeta>
          <wp:meta_key>_searchme</wp:meta_key>
          <wp:meta_value><![CDATA[1]]></wp:meta_value>
     </wp:postmeta>
     <wp:postmeta>
          <wp:meta_key>_encloseme</wp:meta_key>
          <wp:meta_value><![CDATA[1]]></wp:meta_value>
     </wp:postmeta>
     <wp:postmeta>
          <wp:meta_key>_pingme</wp:meta_key>
          <wp:meta_value><![CDATA[1]]></wp:meta_value>
     </wp:postmeta>
     ......[comments go here].....
</item>
</cfoutput>

Nothing terribly interesting here.  I will note, however, that there are a few of the “meta” key/values that I left out.  I didn’t see that they were necessary, and it turns out they weren’t :)

Finally, the last thing you’ll want to do is loop over your comments (if you have any).  Basically, comments for posts are added immediately after the post meta tags, each collection of comments nested within each post “item.”  My final CF code for comments looked like this:

<cfquery name="qcomments" datasource="#dsn#">
     select *
     from comments c
     join users u on c.userid_fk = u.userid
     where postid_fk = #postid#
</cfquery>
<cfif qcomments.recordcount>
<cfloop query="qcomments">
     <wp:comment>
           <wp:comment_id>#cc#</wp:comment_id>
           <wp:comment_author><![CDATA[#trim(commentname)#]]></wp:comment_author>
           <wp:comment_author_email>#trim(commentemail)#</wp:comment_author_email>
           <wp:comment_author_url>#trim(commentwebsite)#</wp:comment_author_url>
           <cfset commdate = "#dateformat(commentdate,'dd mmm yyyy')# #timeformat(commentdate,'HH:mm:ss')#" />
           <wp:comment_date>#commdate#</wp:comment_date>
           <wp:comment_date_gmt>#commdate#</wp:comment_date_gmt>
           <wp:comment_content><![CDATA[#commentcontent#]]></wp:comment_content>
           <wp:comment_approved>1</wp:comment_approved>
           <wp:comment_type></wp:comment_type>
           <wp:comment_parent>0</wp:comment_parent>
           <wp:comment_user_id>0</wp:comment_user_id>
     </wp:comment>
     <cfset cc = cc + 1>
</cfloop>
</cfif>

As before, nothing amazing going on.  One note I will make, however, is about the “comment_id”.  While it doesn’t seem that WP cares what the ID is, it does seem to care that an id of some sort is provided.  I tried without it, and it had only grabbed the last comment for every post…and I had to start all over :(

So, I simply created an auto-increment number, and used it as the comment_id.  All of the comments imported, and were properly associated to their respective posts.

The End

That’s about it.  Importing into WordPress from a custom blog is very simple–sometimes, it just takes a bit of playing around to get the right answer.  I hope this is helpful to someone, and please let me know if there is anyway I can improve this walk-through.