An XML-RPC WordPress Weblog Client for Mathematica

(Find this project on GitHub.)

I’m getting tired of having to manually copy the HTML code generated by Mathematica’s Export[file, “HTML”] command into the WordPress editor in order to post a notebook to my blog.So I decided to write a WordPress weblog client for Mathematica.The client uses the WordPress XML-RPC (XML remote procedure call) API to send blog post data directly from my home computer to my web server.

The first step was to find some documentation on the WordPress XML-RPC API, which I found on a few different sites:the main WP API codex, a helpful blog detailing more of the API functions than are listed in the codex, and the actual XML-RPC spec.

I also discovered that I needed to enable the XML-RPC server in my WordPress installation
before attempting to post to the blog remotely.

The basic steps in posting a Mathematica notebook to my WordPress blog are:

1. Export the Mathematica notebook as HTML
2. Encode the resulting HTML in an XML message using the XML-RPC format expected by WordPress
3. Send the XML to the WordPress server as an HTTP POST request
4. Decode the XML response sent back from the server
5. Repeat steps 2-4 to upload all image files generated by the HTML export to the server

In order to easily test that all this was working, I decided to install a local web server on my MacBook Pro.Rather than mess with trying to install Apache Tomcat from scratch, I opted to install MAMP, which is a self-contained web server for Macs that includes Apache, MySQL, and PHP all in one.

After using MAMP to create a web server on http://localhost:8888, I installed WordPress at http://localhost:8888/wordpress.So now I was up and running with a local WordPress installation that I could test my blog client code against.

Here’s the simple example notebook I want to post to my blog:

weblog-client-021211_3.gif

1. Export the Mathematica notebook as HTML </p> The first step is to export the notebook in HTML format.I took some pointers from another Mathematica-based blog in getting a first version of the following function to work.It's working at a basic level now, but it needs improvement.Mainly, I need to add a "ConversionRules" option to Export in order to wrap the various Mathematica styes (from Mathematica's own stylesheet) in custom tags, which will allow me to match the CSS classes referenced in the generated HTML with the CSS file used in my WordPress theme.

weblog-client-021211_4.gif

weblog-client-021211_5.gif

I feed this function a notebook file name, an HTML file name, and a date.It does the HTML export (which creates a .html file and bunch of .gif files for every cell in the notebook containing code) and then reads the HTML file back in and does some hacky string processing on it in order to convert local image file paths into file paths valid for my web server (which is what the date is used for).

weblog-client-021211_6.gif

weblog-client-021211_7.gif

weblog-client-021211_8.gif

weblog-client-021211_9.gif

I use the HTML export option "FullDocument"→False so the HTML is generated without headers (because WordPress will wrap the blog post content I send it with its own header material):

weblog-client-021211_10.gif

weblog-client-021211_11.gif

weblog-client-021211_12.gif

The Export command generates an HTML file:

weblog-client-021211_13.gif

weblog-client-021211_14.gif

... and an image file for every Input/Output cell:

weblog-client-021211_15.gif

weblog-client-021211_16.gif

Here are the thumbnails of the generated image files:

weblog-client-021211_17.gif

weblog-client-021211_18.gif

2. Encode the HTML in an XML-RPC formatted message

In order to use the WordPress weblog client API, I had to be able to create XML blobs using the XML-RPC encoding conventions.I decided to handle this in top level Mathematica by creating an XMLObject expression for each request, and using ExportString[\_XMLObject, "XML"] to create the XML blob as a String.This XML string then serves as the HTTP POST request body sent to the XML-RPC server. The XML-RPC encoding functions for the atomic datatypes are below.I use a common head (xmlrpcencode) with a different DownValue for each XML-RPC datatype ("int", "double", "base64", etc.).Each XML-RPC datatype corresponds to a different Mathematica Head: Integer -> int, String -> string, Real -> double, "Base64" -> base64, and Boolean -> boolean:

weblog-client-021211_19.gif

weblog-client-021211_20.gif

weblog-client-021211_21.gif

weblog-client-021211_22.gif

weblog-client-021211_23.gif

weblog-client-021211_24.gif

Then there are two types of XML-RPC data structures: struct and array.A struct gets encoded from a list of rules, for which the Mathematica pattern is {__Rule} (see my NKS Summer School lecture on patterns in Mathematica). An array gets encoded from any List: {___}.The following DownValues take care of the data structure encodings.Note that they're set up recursively, so that the elements inside the lists get properly encoded.The XML-RPC spec allows for nesting of these data structures, so an element inside a struct can be another struct or array, and similarly for arrays.

weblog-client-021211_25.gif

weblog-client-021211_26.gif

This final DownValue creates the overall XMLObject, and serves as the entry point for creating the request:

weblog-client-021211_27.gif

Using this encoder I can generate arbitrary XML-RPC requests.So for example, here's the request given as an example in the XML-RPC spec:

weblog-client-021211_28.gif

weblog-client-021211_29.gif

And here's what an actual WordPress API request looks like (in this case, a request to create a new blog post):

weblog-client-021211_30.gif

weblog-client-021211_31.gif

Then I also need top level code for decoding the XML responses sent back by the server:

weblog-client-021211_32.gif

weblog-client-021211_33.gif

weblog-client-021211_34.gif

weblog-client-021211_35.gif

weblog-client-021211_36.gif

weblog-client-021211_37.gif

Here's an example of a decoding, using the example response given in the XML-RPC spec:

weblog-client-021211_38.gif

weblog-client-021211_39.gif

weblog-client-021211_40.gif

3. Send the XML to the WordPress server as an HTTP POST request

Now that I have the ability to encode and decode these XML-RPC blobs, I need to be able to send HTTP POST requests from Mathematica to the web server running WordPress. The key to all this is the ability to send an HTTP POST request from Mathematica.While this isn't directly supported by any top-level function in Mathematica, you can accomplish it using the built-in J/Link framework, which allows you to instantiate and access methods in Java objects directly from Mathematica.A recent Wolfram Research blog post by Rob Raguet-Schofield explains very nicely how to send HTTP POST requests using J/Link. I'll use my WordPress install running on localhost to illustrate the steps in creating and sending the POST request. The basic idea is to instantiate an org.apache.commons.httpclient.HttpClient object, and set up its authentication credentials:

weblog-client-021211_41.gif

weblog-client-021211_42.gif

weblog-client-021211_43.gif

weblog-client-021211_44.gif

weblog-client-021211_45.gif

weblog-client-021211_46.gif

weblog-client-021211_47.gif

weblog-client-021211_48.gif

weblog-client-021211_49.gif

Then the real work happens by instantiating an org.apache.commons.httpclient.methods.PostMethod, and filling it out with the XML-RPC blob to serve as the request body. Instantiate the PostMethod:

weblog-client-021211_50.gif

weblog-client-021211_51.gif

Here's explicitly what the XML-RPC request will look like:

weblog-client-021211_52.gif

weblog-client-021211_53.gif

(Note the XML preamble, e.g.
"POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181"
isn't included in the XML blob.That's because the preamble is added for you by the HttpClient when the POST request is sent.I learned this the hard way.I was manually adding the preamble, but all the POST requests were generating fault responses (malformed request).I had to packet-sniff a bunch of failed requests to realize they had two copies of the preamble in them: one generated by the HttpClient, and a superfluous one I was tacking on.) The request is often referred to as the "payload":

weblog-client-021211_54.gif

Instantiate an org.apache.commons.httpclient.methods.StringRequestEntity:

weblog-client-021211_55.gif

weblog-client-021211_56.gif

... and add it to the PostMethod object:

weblog-client-021211_57.gif

Now execute the POST request:

weblog-client-021211_58.gif

weblog-client-021211_59.gif

If the request is well-formed, we get the response we're looking for from the server:

weblog-client-021211_60.gif

weblog-client-021211_61.gif

weblog-client-021211_62.gif

4. Decode the XML response sent back from the server

Now I just have to decode the XML response from the server into a Mathematica expression that I can digest easily.The code for that was described in step 2.

weblog-client-021211_63.gif

Since the API method I used was "metaWeblog.newPost", the server sent back the post ID of the newly created post:

weblog-client-021211_64.gif

weblog-client-021211_65.gif

And the post shows up on WordPress under post ID 392 (at "http://localhost:8888/wordpress/?p=392") as expected:

weblog-client-021211_66.gif

But notice that none of the images are showing up.That's because we haven't uploaded the images to the server yet ...

5. Upload all image files to the server

In step 1 I mentioned that the HTML export generates a bunch of image files on the client.Here are the thumbnails again:

weblog-client-021211_67.gif

weblog-client-021211_68.gif

These all have to be uploaded to the server, since the generated HTML is expecting them to be there. We can use the API method "wp.uploadFile" to upload the image files. Take the first of these image files:

weblog-client-021211_69.gif

... and encode it (as XML-RPC "base64" data) within a wp.uploadFile request:

weblog-client-021211_70.gif

(Note the idiom ExportString[Import[image, "String"], "Base64"] for encoding the image in base-64 as required by the XML-RPC spec.) Now instantiate and execute a PostMethod as before, this time with the new image payload:

weblog-client-021211_71.gif

weblog-client-021211_72.gif

weblog-client-021211_73.gif

weblog-client-021211_74.gif

weblog-client-021211_75.gif

weblog-client-021211_76.gif

weblog-client-021211_77.gif

weblog-client-021211_78.gif

weblog-client-021211_79.gif

Success!The server responds with the URL of the uploaded image:

weblog-client-021211_80.gif

weblog-client-021211_81.gif

weblog-client-021211_82.gif

Do it again for the second image:

weblog-client-021211_83.gif

weblog-client-021211_84.gif

weblog-client-021211_85.gif

weblog-client-021211_86.gif

weblog-client-021211_87.gif

weblog-client-021211_88.gif

weblog-client-021211_89.gif

weblog-client-021211_90.gif

weblog-client-021211_91.gif

weblog-client-021211_92.gif

And now the images show up in the post:

weblog-client-021211_93.gif

Blog this notebook

Putting this all together, here's all the code thus far packaged up into a few functions:

weblog-client-021211_94.gif

weblog-client-021211_95.gif

weblog-client-021211_96.gif

weblog-client-021211_97.gif

weblog-client-021211_98.gif

weblog-client-021211_99.gif

weblog-client-021211_100.gif

weblog-client-021211_101.gif

weblog-client-021211_102.gif

weblog-client-021211_103.gif

weblog-client-021211_104.gif

weblog-client-021211_105.gif

weblog-client-021211_106.gif

weblog-client-021211_107.gif

weblog-client-021211_108.gif

weblog-client-021211_109.gif

weblog-client-021211_110.gif

weblog-client-021211_111.gif

weblog-client-021211_112.gif

weblog-client-021211_113.gif

weblog-client-021211_114.gif

weblog-client-021211_115.gif

weblog-client-021211_116.gif

Now let's blog this very notebook!

weblog-client-021211_117.gif

weblog-client-021211_118.gif

weblog-client-021211_119.gif

weblog-client-021211_120.gif

And here's what it looks like:

weblog-client-021211_121.gif