最近博客更新比较频繁,尝试了几种不同的写作方式,总结如下:
<br />, <h1>, <h2>, <pre>, <a>, <img>
。 倒还凑合,写好的文章可以直接放在我的个人主页,也可以直接粘贴到博客中。 不过每次写这些标签还是很烦人,而且最让我受不了的是对于 <pre>
中的代码都要手工替换 <, >
为 <, >
。刚好最近在翻译 JavaScript秘密花园,发现它使用的是 Markdown 的简单语法,通过 python 下的 Markdown 解析器解析成 html 源代码, 使用非常方便。由于我一直关心前段相关技术,就想能不能用 JavaScript 来实现这个自动编译过程。
[注]:也看到博客园里有人说用 Live Writer,也曾尝试装了个最新版的 Windows Live Writer 2011,居然发现登录时就报错,也就没下文了。 后来想想还是自己靠得住,程序员嘛,多折腾折腾没啥坏处。
首先我们来看下 Markdown 是何方神圣?可能很多程序员还不了解这个东西,不过你应该知道 BBCode 吧,其实 Markdown 是类似的东西。
不过 Markdown 有更广泛的用户群体,比如 Stack Overflow, Reddit, GitHub 等都选择 Markdown 作为默认的写作格式,由此可见 Markdown 的魅力。
我们来看下 Markdown 的创建者 John Gruber 对它的描述:
Markdown is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).
Thus, “Markdown” is two things: (1) a plain text formatting syntax; and (2) a software tool, written in Perl, that converts the plain text formatting to HTML.
大意是 Markdown 是一个为 Web 写作者创建的一个简便工具,用于把文本信息转换为网页信息。Markdown 包含两方面的内容,一个是普通文本的格式化语法,另一个是把这个用于特定语法的文本转化为网页的工具(Perl)。
为了有一个直观的认识,我们简单看几个 Markdown 的常见语法:
Markdown 文本:
一号标题
====================
二号标题
---------------------
这是一个正常的段落。
### 三号标题
> 这是一段引用的文本。
>
> 这是第二段引用的文本。
Html 输出:
<h1>一号标题</h1>
<h2>二号标题<h2>
<p>这是一个正常的段落。</p>
<h3>三号标题</h3>
<blockquote>
<p>这是一段引用的文本。</p>
<p>这是第二段引用的文本。</p>
</blockquote>
Markdown 文本:
- 苹果
- 橘子
- 香蕉
Html 输出:
<ol>
<li>苹果</li>
<li>橘子</li>
<li>香蕉</li>
</ol>
Markdown 文本:
我的[个人主页地址](http://sanshi.me/)。
Html 输出:
<p>我的<a href="http://sanshi.me/">个人主页地址</a>。</p>
Markdown 文本:
var source = "<h1>这个页面标题</h1>";
document.getElementById("#title").innerHTML = source;
Html 输出:
<pre><code>var source = "<h1>这个页面标题</h1>";
document.getElementById("#title").innerHTML = source;</pre></code>
我的初步打算是这样的:
<pre>
指定语言(比如 <pre class="brush:js;">
), 这个 Markdown 默认生成的 <pre><code>
不兼容。 而 google-code-prettify 更加小巧精致,而且不需要指定代码所使用的语言,它会根据代码内容进行猜测,这也是我最终使用它的原因。MarkdownJS 本身已经是 nodejs 的模块了,所以调用方法非常简单:
var fs = require('fs'),
markdown = require("./markdown"),
fileContent;
// 读入 Markdown 源文件
fileContent = fs.readFileSync('test.md', 'utf8');
// 使用 MarkdownJS 模块把源文件转换为 HTML 源代码
fileContent = markdown.toHTML(fileContent);
// 保存
fs.writeFileSync('test.html', fileContent);
console.log('Done!');
PrettifyJS 本身不是 nodejs 的模块,我们需要手工改造一下,这里遇到了麻烦。
注:可能有人会问为什么要在 nodejs 中调用 PrettifyJS,直接在页面中引入不就行了么? 事实的确是这样的,如果你有自己的域名和服务器的话。 而一般的博客提供商不会允许你私自引入 JavaScript 的,但是大部分都允许自定义 CSS, 所以我们可以直接在本机生成着色后的代码片段,然后再粘贴到博客中去。
首先,下载最新的版本,我们发现其中有一个不依赖于 DOM 节点的独立调用的函数 prettyPrintOne:
/**
* @param sourceCodeHtml {string} The HTML to pretty print.
* @param opt_langExtension {string} The language name to use.
* Typically, a filename extension like 'cpp' or 'java'.
*/
function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
// ...
}
这应该是个好的入手点,在 Chrome 中直接执行下面代码:
prettyPrintOne('var i = 1;')
居然有错误:
后来发现这个 BUG 已经被 Fix 了,于是下载最新的 prettify.js 的最新源代码,再次运行:
目前为止,这只是在浏览器中运行良好,怎么把它改造成 nodejs 的模块呢,首先来大致看下 prettifyjs 的源代码:
window['PR_SHOULD_USE_CONTINUATION'] = true;
window['PR_TAB_WIDTH'] = 8;
window['_pr_isIE6'] = function () {
var ieVersion = navigator && navigator.userAgent &&
navigator.userAgent.match(/\bMSIE ([678])\./);
ieVersion = ieVersion ? +ieVersion[1] : false;
window['_pr_isIE6'] = function () { return ieVersion; };
return ieVersion;
};
(function () {
function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
// ...
}
window['prettyPrintOne'] = prettyPrintOne;
})();
但是在 nodejs 中并不存在 window 对象。所以我们需要做如下两步改造:
改造后的代码:
// 这里定义 window 变量,在 nodejs 上下文是没有这个变量的
var window = {};
window['PR_SHOULD_USE_CONTINUATION'] = true;
window['PR_TAB_WIDTH'] = 8;
window['_pr_isIE6'] = function () {
var ieVersion = navigator && navigator.userAgent &&
navigator.userAgent.match(/\bMSIE ([678])\./);
ieVersion = ieVersion ? +ieVersion[1] : false;
window['_pr_isIE6'] = function () { return ieVersion; };
return ieVersion;
};
(function () {
function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
// ...
}
window["prettyPrintOne"] = prettyPrintOne;
})();
// Make window as a nodejs module.
if (typeof module !== 'undefined') {
module.exports = window;
}
如果这时直接拿来使用,你会发现 prettifyjs 的返回的结果是不正确的,后经调试发现是 window['_pr_isIE6']
的问题, 这里我们只需简单的返回 false
,告诉 prettifyjs 我们不是在 IE6 下,所以完整的改造后的 prettifyjs 代码如下:
// 修改1:这里定义 window 变量,在 nodejs 上下文是没有这个变量的
var window = {};
window['PR_SHOULD_USE_CONTINUATION'] = true;
window['PR_TAB_WIDTH'] = 8;
window['_pr_isIE6'] = function () {
// 修改2:简单的返回 false
return false;
};
(function () {
function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
// ...
}
window["prettyPrintOne"] = prettyPrintOne;
})();
// 修改3:将自定义的 window 对象返回,作为 nodejs 的一个模块
if (typeof module !== 'undefined') {
module.exports = window;
}
现在万事俱备,只欠整合了。下篇文章,我将会详细介绍如何使用 MarkdownJS 和 改造后的PrettifyJS 在 NodeJS 平台下完成文章的自动编译工作。 并发布一个基于 Markdown 语法的高效博客写作自动编译平台,敬请期待。
注:本篇文章就是使用 Markdown 语法完成并编译的,你可以下载 文章 Markdown 源代码 和 编译后的文章 HTML 源代码。