这篇文章介绍的是 CEP 的文件的读写与二进制数据处理的相关内容。
首先要说明的是由于 CEP 的 JavaScript 运行在 Node.js 引擎上,所以能够使用 Node.js 的 fs模块,就能进行文件的读写了,如下面的例子,

var fs = require('fs'); //引入 fs 模型
fs.readFile("D:/autorun.inf",function(err,data){ console.log(data)}) //读取文件并交给回调函数

Node.js 的文件读写操作完全能替代 CEP 的提供的 API,但还是推荐使用 CEP 的 API ,毕竟是官方标准,各宿主之间的兼容性更有保障。

此外后面还花了大篇幅讲了 JavaScript 的二进制处理相关知识供参考。

Adobe 为 CEP 提供了自己的 API 来方便进行读写操作,实际上和使用 Node.js 的 fs 没有什么区别,不过 CEP 的接口的方法都是同步的,更容易理解。
CEP 文件操作的方法放在 window.cep.fs 对象中。

文件路径

这里要介绍 CEP 中的文件路径。
CEP 中的路径是 Unix 风格的 / 分隔,而不是 Windows 风格的 \\,但是由于 CEP 扩展可以同时在 Windows 和 OS X 中运行,所以就有了跨平台问题,这个后面会说如何解决跨平台问题。

  • / 代表根目录, CEP 中为 PhotoShop 安装目录的根目录,比如 D:/
  • ./. 代表当前目录,同时如果没有前缀也表示当前目录,比如 cc 等同于 ./cc, CEP 中是 PS 安装目录 ,如 D:/PS/Adobe Photoshop CC 2015
  • ../.. 代表上级目录,可以连续使用: ../../

跨平台路径处理

由于 OSX 和 Windows 上路径格式的不同,所以我们常常得对其处理,虽然 Node.js 和 CEP 提供的文件相关接口都是统一使用 Unix 风格的路径格式,但难免要处理来自本地的路径,要想简单的处理路径格式问题,可以使用 Node.js 自带的 Path 模块提供的功能。

var path = require('path'); //使用前先引入 path 模块
path.join("foo", "bar");
  • 拼接路径
    使用 path.join(); 来替代手动用 + 拼接路径,在不同系统上自动使用相应分隔符(要注意的是 CEP 提供的接口无论在 Windows 还是 OS X 上都是用 /):
path.join("AAA", "fff"); // 取代 "AAA"+"/" +"fff"
//在  OS X  上:AAA/fff
//在 Windows 上: AAA\\fff
  • 路径标准化
    如果你觉得使用 + 拼接路径根据方便也无妨,path 还有一个路径标准化功能,在最终使用路径前用 path.normalize() 处理路径就好了,路径标准化还有去除路径中无效字符的功能(比如 sd///ds 变成 sd/ds)。
path.normalize("Files/Adobe/CEP/");
//在  OS X  上:Files/Adobe/CEP/
//在 Windows 上: Files\Adobe\CEP\"
  • 获得相对路径的绝对路径
    使用 path.resolve(); 可以得到如 ./ 这样相对路径的绝对路径
path.resolve("./");
  // "D:\PS\Adobe Photoshop CC 2015"

常用路径:

*这里的 cs指 CSInterface 对象,即:var cs = new CSInterface();


路径

例子

process.execPath

Node.js 引擎可执行文件。

D:\PS\Adobe Photoshop CC 2015\Required\CEP\CEPHtmlEngine\CEPHtmlEngine.exe

__dirname

扩展所在目录

C:/Program Files (x86)/Common Files/Adobe/CEP/extensions/fonTags

__filename

当前文件目录

C:/Program Files (x86)/Common Files/Adobe/CEP/extensions/fonTags/index.html

cs.getSystemPath(SystemPath.USER_DATA)

系统用户数据文件夹

C:/Users/语冰/AppData/Roaming

cs.getSystemPath(SystemPath.COMMON_FILES)

系统公共库文件夹

C:/Program Files/Common Files

cs.getSystemPath(SystemPath.MY_DOCUMENTS)

“我的文档”

C:/Users/不知语冰/Documents

cs.getSystemPath(SystemPath.HOST_APPLICATION)

宿主应用程序可执行文件

D:/PS/Adobe Photoshop CC 2015/Photoshop.exe

读取文件

window.cep.fs.readFile(路径, 方式编码) ,提供文件路径,和文本编码(默认按 UTF-8 处理),返回一个对象,其中

  • .err 存放返回错误信息
  • .data 以字符串形式存放读取返回的数据,
var path = "D:/1.txt";
    var result = window.cep.fs.readFile(path);
    if (0 == result.err)// err 为 0 读取成功
    {
        console.log(result.data);
    }
    else
    {
        console.log("读取错误:" + result.err);// 失败
    }

Base64 模式读取

Base64 是一种把二进制数据以文本(64个可打印字符)形式表示的编码方法。由于 CEP 的 readFile() 只能读取文件后返回的 .data 只能是字符串,所以如果你要读取一个非文本的二进制文件,比如一张图片,就需要使用 Base64 模式读取,这样返回的.data中会是你读取文件数据的 Base64 编码后的字符串,否则读取的数据会被强制装换为字符串会丢失内容。像 readFile() 第二个参数传入 "Base64" 即可以 Base64 模式读取。

var path = "D:/1.txt"; 
    var result  = window.cep.fs.readFile(path ,"Base64");
    result.data //Base64 字符串: "77+977+977+977+977+977+977+977+977+977+977+977+977…v73v↵v73vv73vv73vv73vv73vv73vv73vv73vv73vv73vv70="

对于读取到的 Base64 的字符串,可以 CEP 提供了 cep.encoding.convertion 里的方法来进行转换。

  • .b64_to_ascii(base64str) Base64 字符串 以 ascii 编码转换为字符串
  • .b64_to_utf8(base64str) Base64 字符串 以 UTF-8 编码转换为字符串
  • .b64_to_binary(base64str) Base64 字符串以二进制编码转换为字符串

另外还有反过来的方法:.ascii_to_b64(ascii).utf8_to_b64(str).binary_to_b64(binary)
要注意的是 .b64_to_binary().binary_to_b64() 实际上是就是 window.atob() 和 window.btoa(),而这 2 个方法返回的是字符串,而且是不支持 Unicode 字符的(比如中文),所以不要因为名字是 binary 就以为它能处理二进制数据,它们是用来处理含不可传输的 ASCII 控制字符的。

要处理其非文本的二进制数据的 Base64 ,可以使用 Node.js 的 Buffer(关于 Buffer 后面有说):

var result  = window.cep.fs.readFile( "D:/1.txt","Base64");
    var buf = new Buffer (result.data, 'base64');

写入文件

与 CEP 的 readFile() 一样,写入文件接受的数据也是字符串:

var data = "文本内容";
    var result = window.cep.fs.writeFile( "D:/1.TXT", data);
    if (0 == result.err) {
        // 成功·
    }
    else {
         // 失败
    }

要想对文件写入二进制数据,必须得使用 Base64 模式,并给 writeFile() Base64 字符串 下面这个例子展示的是读取一个二进制文件,并原样写出的过程:

var inf = window.cep.fs.readFile ("D:/A.ico", "Base64"); //以 Base64 模式读入文件
    window.cep.fs.writeFile ("D:/B.ico", inf.data, "Base64");//以 Base64 模式写出文件

新建目录

window.cep.fs.makedir (path) 在指定位置新建文件夹

var result = window.cep.fs.makedir(__dirname+"/"+"EEE");
    if (0 == result.err) {
        console.log("成功")
    }
    else {
        console.log("错误:" + result.err)
    }

删除文件

window.cep.fs.deleteFile(path); 用法同上,注意只能删除文件,不能删除文件夹

重命名文件

rename(oldPath, newPath) 可以重命名文件和文件夹。

var result = window.cep.fs.rename(__dirname+"/EEE" ,__dirname+"/EEE2" );
    if (0 == result.err)
    {
       console.log("重命名成功");  
    }
    else
    {
        console.log("错误:" + result.err);
    }

读取目录中文件列表

window.cep.fs.readdir(path); 可以读取一个文件夹中存放的文件和文件夹(只是一级)列表。
读取的列表以文件名数组的形式存放在返回值的.data里。

var result = window.cep.fs.readdir(__dirname );
    if (0 == result.err)
    {
       console.log( result.data);  
           // [".idea", "css", "CSXS", "EEE", "font", "img", "js", "jsx", "tem", ".debug", "1.TXT", "index.html"]
    }
    else
    {
        console.log("错误:" + result.err);
    }

判断路径是文件还是路径

window.cep.fs.stat(path).data.isDirectory()window.cep.fs.stat(path).data.isFile() 可以检测一个路径是文件还是文件夹。

var result = window.cep.fs.stat(__dirname + "/" + "EEE");
    if (0 == result.err)
    {
            if (result.data.isDirectory() == true)
            {
              console.log("这是个文件夹");
            }
            else if (result.data.isFile() == true)
            {
               console.log("这是个文件");
            }
    }
    else
    {
        console.log("错误:" + result.err)
    }

获取文件最后一次被修改的时间

读取 window.cep.fs.stat(path).data.mtime 可以获取文件文件最后一次被修改的时间。

设置文件权限

window.cep.fs.chmod (path, mode) 设置指定文件的权限,权限是用 Unix 风格的 chmod 数字表示权限。
在 Windows 上,只能设 0 为文件加上只读属性,777 取消只读属性。

打开、保存文件对话框

window.cep.fs.showOpenDialog (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes)window.cep.fs.ShowOpenDialogEx (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes, friendlyFilePrefix, prompt)window.cep.fs.showSaveDialogEx (title, initialPath, fileTypes, defaultName, friendlyFilePrefix, prompt, nameFieldLabel)

这个方法能弹出一个系统的文件选择对话框,用来选择或者保存文件,得到的文件以数组的形式存放在返回值的 .data 里。

参数的作用为:
showOpenDialog (允许多选, 选择目录模式, 标题, 初始路径, 指定文件类型数组)ShowOpenDialogEx (允许多选, 选择目录模式, 标题, 初始路径, 指定文件类型数组, 文件类型说明, prompt消息)showSaveDialogEx (标题, 初始, 文件类型, 默认名称, 文件类型说明, prompt信息, 名称字段标签)

其中 prompt信息, 名称字段标签是 OSX 中才起作用的。

选择目录

弹出选择目录对话框,这时只有标题初始路径参数起作用。

var result = window.cep.fs.showOpenDialog (true, true, "标题", "D:/", "")
   result.data
  //["E:/Qt_Project"]

java 解析 psd java photoshop api_adobe

选择目录模式

打开文件

var result = window.cep.fs.showOpenDialogEx (false, false, "标题", "D:/", ["txt","ico"] , "文件类型说明");
result;

java 解析 psd java photoshop api_cep_02

打开文件对话框

保存文件

result = window.cep.fs.showSaveDialogEx ("标题", "D:/", ["txt"], "默认名称.TXT", "文件类型说明");
    if (0 == result.err)
    {
        if(result.data.length==0)
        {
           console.log("用户放弃了保存");
        }
        else
        {
           console.log(result.data);
        }
     }
    else
    {
        console.log("错误:" + result.err)
    }

java 解析 psd java photoshop api_ps插件_03

 保存文件对话框

JavaScript 的二进制处理

JavaScript 当年起草的时候并没有想到自己以后应用范围会那么广,所以一开始并没有处理文件\二进制数据的功能,对文件\二进制数据的相关功能是后来一点点补增的,这让 JavaScript 文件\二进制数据处理的知识相较于其他语言要更杂乱,更麻烦。

Blob 和 File 对象

Blob 对象是 ECMAScript 5 标准才引入的新内容,Blod 是一个存放二进制数据的容器,然而 Blob 对象成员属性只有 2 个 :size 表示数据长度,type 表示数据的类型(mime type),并不能读写已经放入 Blod 中的数据,他大多数情况下的作用只是用来生成一个可以下载的文件(还有剪贴板、拖拽操作可能会用到):

var myBlob = new Blob(["这是文本"], { "type" : "text\/xml" }); //把数据装入 Blob
var href = window.URL.createObjectURL(blob); //生成下载链接

File 对像继承了 Blob 对象,并增加了 name,lastModifiedDate 等文件相关属性,但依然和 Blob 一样并不能读写已经放入的数据

ArrayBuffer 和 TypedArray、DataView 对象

ArrayBuffer

要对二进制数据读写,需要使用缓冲数组:ArrayBuffer ,这个对象实际上对应的就是传统语言中的数组,即在创建申请所需长度的内存,数组内容存放在连续的内存中,并且长度不能动态增减。
在 JavaScript 中 ArrayBuffer 本身只有创建指定长度 ArrayBuffer 的功能,要对 ArrayBuffer 进行修改需要使用数据视图对象 TypedArray 或者 DataView。

var buf = new ArrayBuffer(32); // 创建 32 字节长度的 ArrayBuffer

DataView

数据视图要解决的问题是按何种数据类型去处理数据,举例来说就是一个字节(8位)是把他当成取值范围 0~255 的无符号整数(Uint8) 还是取值范围 -128~127 的带符号整数(Int8)处理。 JavaScript 支持以下数据类型:

数据类型

字节长度

含义

对应的传统语言类型

Int8

1

8 位带符号整数

signed char

Uint8

1

8 位无符号整数

unsigned char

Uint8C

1

8 位无符号整数(自动过滤溢出)

unsigned char

Int16

2

16 位带符号整数

short

Uint16

2

16 位无符号整数

unsigned short

Int32

4

32 位带符号整数

int

Uint32

4

32 位无符号的整数

unsigned int

Float32

4

32 位浮点数

float

Float64

8

64 位浮点数

double

java 解析 psd java photoshop api_cep_04

  

一个 8 字节数据不同视图下的处理单位

一个 DataView 只是一个视图,并不存储数据,他指向一个 ArrayBuffer ,我们使用 DataView 的各种方法按想要的数据类型去读写他指向的 ArrayBuffer :

var buf = new ArrayBuffer(32); // 创建 32 字节长度的 ArrayBuffer
var dataView = new DataView(buf);// 以 buf 为数据创建视图 dataView 
dataView.setUint8(3,0xff); //把 dataView 对应数据按 Uint8(无符号 8 位整数 0~255)处理,在第 4 位 Uint8 中写入 0xff
dataView.getUint8(3); // 按 Uint8 读取第 4 位。 返回 255(即 0xff)

DataView 有 3 个属性:

  • buffer :指向的 ArrayBuffer;
  • byteOffset :数据偏移值
  • byteLength :数据长度

表示的是 DataView 可以操作 bufferbyteOffset 开始 byteLength 长度的数据,其可以在创建对象时指定(也可修改):

var buf = new ArrayBuffer(8);// 创建 8字节长度的 ArrayBuffer
var dataView = new DataView(buf, 3, 2);// 以 buf 的第 4 位开始取 2 位长度的数据创建视图 dataView

默认 DataView 会以 ArrayBuffer 的所有数据创建视图。

DataView 读写数据的 .getXXX ,和 setXXX 方法都是从指定字节位来读取数据的,这意味着如果你要依次读取 Uint16 类型(一个 Uint16 占 2 个字节)的数据,你要使用的是 .getUint16(0), .getUint16(2), .getUint16(4), .getUint16(6),需要你手动处理数据类型长度,这有灵活操作的好处,但通常这样只会增加让程序员手动处理数据类型长度的麻烦。所以如果我们不是为了更灵活自由的处理数据类型,或者指定字节序的话更常用 TypedArray (类型化数组)。

TypedArray

TypedArray 类型化数组,和 DataView 实际上的功能是一样,都是为数据操作指定数据类型,不同的是 DataView 是拿到数据后进行可以按各种数据类型进行操作,而 TypedArray 是先按指定的数据类型拿到数据,以后都按这种数据类型进行操作。这样操作带来的好处就是无需程序员手动处理数据类型长度,并且减少每次操作都指定数据类型的麻烦,并且 TypedArray 虽然也是对 ArrayBuffer 的封装,但 TypedArray 可以和数组一样用下标操的方式操作,编写代码和调试都更加方便。

TypedArray 实际上有一组对象,名字为 XXXArray 的形式,XXX 在前面 DataView 那张数据类型表上有。分别是:Int8ArrayUint8ArrayUint8ClampedArrayInt16ArrayUint16ArrayInt32ArraUint32ArrayFloat32ArrayFloat64Array

var buf = new ArrayBuffer(8); // 创建 8 字节长度的 ArrayBuffer
var u8 = new Uint8Array(buf);// 以创建 buf 创建 TypedArray

//同 DataView 一样 TypedArray 也可以只截取 ArrayBuffer 的一部分:
var u8_2 = new Uint8Array(buf,4,2);// 以创建 buf 的第 5 位开始取 2 字节长度创建 TypedArray

//TypedArray 可以直接指定要创建长度,省去手动创建 ArrayBuffer 的过程
var f64a = new Float64Array(8);

//TypedArray 还可以直接输入数组
var x = new Uint8Array([1, 1,4,222]);
      x // [1, 1,4,222]

//TypedArray 还可以根据另一个 TypedArray 克隆
var x = new Uint8Array([1, 1,4,222]);
var y = new Uint8Array(x);
      x // [1, 1,4,222]
      y // [1, 1,4,222]

//TypedArray 还可以与另一个 TypedArray  共用一个 ArrayBuffer
var x = new Uint8Array([1, 1,4,222]);
var y = new Uint8Array(x.buffer); // x.buffer 获取 x 指向的 ArrayBuffer
      x // [1, 1,4,222]
      y // [1, 1,4,222]
y[0] // 1;
x[0] = 2;
y[0] // 2;

TypedArray 使用数组的操作方式读写指向的 ArrayBuffer ,普通数组的操作方法和属性,对TypedArray数组完全适用。
TypedArray 的 .buffer 属性表示指向的 ArrayBuffer 。
TypedArray 有 length 属性,和 byteLength 2个长度属性,其中 length 表示 TypedArray 数组成员个数,也就是有多少个,而 byteLength 是这些成员共占多少字节长度。

Buffer

与上面说的 JavaScript 原生的二进制数据操纵方法不同,Buffer 是 Node.js 提供的对象,Buffer 与 TypedArray 类似,都是以数组的操作方式来处理二进制数据,只是它不依赖于 ArrayBuffer ,但是 Buffer 也并不是直接存储数据,它也是指向一块数据,所以多个 Buffer 对象的实例也可以共用一块数据(使用 .slice())。
相较于 TypedArray ,Buffer 在创建时会有更好的性能表现,因为 ArrayBuffer 创建时会把申请的内存全白写 0 ,而 Buffer 创建时没有这步操作(所以新创建的 Buffer 里会有随机数据)。

var buf = new Buffer(16);  //创建 64 个字节长度的 Buffer

// Buffer 也能从普通数组创建
var x = new Buffer([1,2,3,4]);

// 由于 TypedArray 也有数组的特征,所以可以直接把 TypedArray 转化成 Buffer ,
var v = new Buffer(new Uint8Array([1, 1,4,222]));
        v // {0: 1, 1: 1, 2: 4, 3: 222}

// 反过来 Buffer  也能这样转成 TypedArray 
var g = new Uint8Array(v);
        g //[1, 1,4,222]


// Buffer 能根据另一个 Buffer 克隆 
var y = new Buffer(x);
        y // [1,2,3]
// 所谓克隆自然是深拷贝,与原 Buffer 已经毫无关系了
y[0] = 222;
        x[0] // 1
//而使用 .slice() 得到的是指向原 Buffer 指针
z = x.slice(0) //截取 x 从 0 位置到结尾,并赋值给 z ,这截取的只是指针,也就是 x 和 z 共用一段数据
          z //{0: 1, 1: 2, 2: 3,} 
z[0] = 222; // z 改变 x 也会同时改变
        x[0] // 222

Buffer 也有 readInt8readUInt16LEwriteFloatBE 这样指定数据类型和字节序的方法,使用起来和 DataView 差不多,不在繁述。