[译]JavaScript:将字符串两边的双引号转换成单引号
原文:http://ariya.ofilabs.com/2012/02/from-double-quotes-to-single-quotes.html
代码的不一致性总是让人发狂,如果每位开发者都能遵守约定好的编码规范(coding conventions),那么生活将变的更加美好.比如在JavaScript中,一个字符串字面量可以用单引号引起,也可以用双引号来引起(ECMAScript 5规范7.8.4小节).很多人习惯于使用某种特定的引号,比如jQuery编码风格推荐人们使用双引号.
但我个人更喜欢使用单引号,这仅仅是我的偏好.自从有了Esprima,我意识到,我可以利用Esprima能够以非破坏式(non-destructive)的方式对输入的JavaScript源码进行局部修改(partial modification)的能力,来强制让输入JavaScript源码中的每个字符串字面量都使用单引号.于是我就写出了下面的singlequote.js脚本:
var fs = require('fs'), esprima = require('esprima'), input = process.argv[2], output = process.argv[3], offset = 0, content = fs.readFileSync(input, 'utf-8'), tokens = esprima.parse(content, { tokens: true, range: true }).tokens; function convert(literal) { var result = literal.substring(1, literal.length - 1); result = result.replace(/'/g, '\''); return ''' + result + '''; } tokens.forEach(function (token) { var str; if (token.type === 'String' && token.value[0] !== '\'') { str = convert(token.value); content = content.substring(0, offset + token.range[0]) + str + content.substring(offset + token.range[1] + 1, content.length); offset += (str.length - token.value.length); } }); fs.writeFileSync(output, content);
这个脚本需要用Node.js来执行,像这样:
node singlequote.js inputfile outputfile
该脚本具体是如何工作的?让我们假设输入源码的内容是这样的:
console.log("Hello")
在我们把这句代码传入Esprima解析器的时候,要把其中一个解析选项tokens设为true,这样解析器才会在解析的过程中把遇到的所有token(词法单元)收集到一个数组中,并返回它.对于我们上面的这句代码,返回的token数组看起来是这样的:
[ { type: "Identifier", value: "console", range: [0, 6] }, { type: "Punctuator", value: ".", range: [7, 7] }, { type: "Identifier", value: "log", range: [8, 10] }, { type: "Punctuator", value: "(", range: [11, 11] }, { type: "String", value: ""Hello"", range: [12, 18] }, { type: "Punctuator", value: ")", range: [19, 19] } ]
译者注:在编译原理领域中,token这个词可以被翻译成词法单元或者词法记号,它表示一个在源码中拥有独立意义的最小单位,是不可再分的词法单元(lexical unit).一个token是由若干个字符组成的,就像英文中的单词一样,所以也有人直接把它翻译成单词.在ES标准中,token具体包含有保留字,标识符,字面量,标点符号这几种输入元素(input element).
一旦我们拿到了所有的token对象,剩下的事情就简单多了.我们只需要遍历这个token对象数组,找到那些与某个字符串字面量关联的token(type属性为String的token对象).每个token对象都会在自己的range属性中存放有所关联字符串字面量的位置信息,这是一个表示了所关联字符串字面量在输入源码中的开始位置和结束位置的索引区间数组(闭区间).
{ type: "String", value: ""Hello"", range: [12, 18] }
有了这个位置信息,我们就能使用一些基本的字符串操作来替换输入源码中的某段内容.对于上面这个例子的话,修改目标就是索引位置在[12, 18]之间的源码内容.值得注意的是,如果原始字符串字面量的值(两个引号中间夹着的内容)中包含有一个或多个的单引号,则我们需要做一些额外的工作,就是要把这些单引号进行转义(查看第7.8.4小节的SingleEscapeCharacters).如果真的需要进行这样的转义,则转义后的字符串字面量的长度会大于原始字符串字面量的长度(多了反斜杠字符),从而改变了整个源码的长度,再从而让token数组中其它还未处理的token对象中包含的位置信息产生错位,因此我们还需要进行偏移量的调整.下面是个需要进行转义的字符串字面量的例子:
// 输入源码 "color = 'blue'"; // 不进行转义操作的话会输出非法的字符串字面量 'color = 'blue'';
另外,我写的转换代码还差一件事情没做,就是要把那些不再需要的转义字符也删除掉.也就是原来字符串字面量中包含的双引号前面的那个反斜杠,它已经不再需要了.这项工作就留给读者们实现吧!
译者注:作者说的是这种情况,原字符串字面量为"\"",这时用反斜杠转义里面的双引号是必须的,但按照上面的规则转换之后就成了'\"',虽然这也是一个合法的字符串字面量,且求值结果不变.不过作者的意思是这个反斜杠是多余的,是应该删除掉的.
很显然,我写的这个工具只是为教学演示而用.另外,虽然现在大部分编辑器都支持搜索替换的功能,如果你需要使用编辑器来完成这项任务,也有一定的难度,注意不要替换掉那些不在字符串字面量两边的引号.
译者注:你觉的能用正则表达式来完成这项任务吗?注释和正则字面量中的引号字符会让你束手无策.
你还可以想想看,利用token列表和源码局部修改的技术,还能干哪些事情?
译者注:你有没有发现作者忽略了一个比较极端的情况,就是假如原字符串字面量为"\'",虽然这个反斜杠是多余的,但这的确是一个合法的字符串写法,求值后字符串的值为一个单引号.按照上面的算法转换之后会变成'\\''.显然,这会导致一个语法错误,因为少了一个反斜杠.是不是呢?
有了Esprima,实现这样一个转换器真的是很简单,如下