利用 Notepad++ 插件技术开发直接打开 FastInfoset 格式 XML 文件
骆毅
2012 年 9 月 03 日发布
简述
FastInfoset定制了一种在二进制级别处理 XML 信息集合的方式,通过这种特殊的序列化,与普通的 XML 文档相比,无论在大小和解析速度上都有特殊的优势。
Notepad++是一款优秀的文本编辑软件,开源、免费和多种插件支持使得它成为最流行的编辑软件之一,其中强大的插件功能可以让 Notepad++ 对各种文本文件良好的支持,如下图所示 :
图 1. Notepad++ 展示 XML
对 XML 而言,优秀的处理能力体现在标签着色和树形折叠,但是 Notepad++不能直接打开 FastInfoset 文档,本文就是已解决这个问题为目的,利用 Notepad++的插件技术,来直接打开和保存 FastInfoset文档。包括以下几个问题:打开、存储 FastInfoset 的库由 Java 提供,必须建立 JNI 的调用结构来管理代码;
插件的开发模型,包括重点使用的函数,调用的流程和开发代码;
JNI 调用。
FastInfoset 文件处理
FastInfoset 技术简述
FastInfoset利用现实当中 XML 文件当中大量存在的重复信息的特点,比如前面提到的 books.xml,可以看到大量重复的标签,然后利用这种各种处理字符的技术,压缩 XML 文件和提高访问速度。这些技术包括动态表、原始词集合和外部词汇表等等。
打开和保存 FastInfoset
来自 GlassFish 的技术中的一部分,用户可以访问 http://fi.java.net 来获得 Fast Infoset的技术支持,由于 Java 的技术,FastInfoset 的接口都是使用 Java 编写,其中的部分代码如下所示:打开 FastInfoset 文档
清单 1. 打开 FastInfoset文档DefaultContentHandler builder = new DefaultContentHandler();
// Instantiate the FI SAX parser
XMLReader saxReader = new SAXDocumentParser();
saxReader.setContentHandler(builder);
// Parse the fast infoset document
InputSource inputSource = new InputSource(stream);
saxReader.parse(inputSource);
保存 FastInfoset 文档
清单 2. 保存 FastInfoset文档final java.io.StringReader reader = new java.io.StringReader(saveString);
// Get the input stream for the XML document
InputStream xmlDocument = new InputStream(){
@Override
public int read() throws IOException {
// TODO Auto-generated method stub
return reader.read();
}
};
// Set up output stream for fast infoset document
OutputStream fiDocument = new FileOutputStream(new File(filePath));
// Create Fast Infoset SAX serializer
SAXDocumentSerializer saxDocumentSerializer = new SAXDocumentSerializer();
// Set the output stream
saxDocumentSerializer.setOutputStream(fiDocument);
// Instantiate JAXP SAX parser factory
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
/* Set parser to be namespace aware
* Very important to do otherwise invalid FI documents will be
* created by the SAXDocumentSerializer
*/
saxParserFactory.setNamespaceAware(true);
// Instantiate the JAXP SAX parser
SAXParser saxParser = saxParserFactory.newSAXParser();
// Set the lexical handler
saxParser.setProperty("http://xml.org/sax/properties/lexical-handler",
new FastInfosetDefaultHandler());
// Parse the XML document and convert to a fast infoset document
saxParser.parse(xmlDocument, saxDocumentSerializer);
Notepad++ 插件技术
Notepad++ 的插件简单结构
Notepad++的核心是 Scintilla 的跨平台编辑控件,由 C++ 编写,使用纯 Win32 接口和 STL。所以一个典型的 Notepad++ 插件需要由 C++ 编写,核心是控制 Scintilla 控件的行为。为此,整个代码的结构分为以下几个部分。Java 部分,包括开放的接口和对 FastInfoset XML 文档的支持;
插件部分,开发包括控制 Notepad++ 中 Scintilla 控件文本的显示,以及通过 JNI 接口构建 Java 虚机调用 Java 接口的方法。
Notepad++插件的开发提供了一套标准的流程,提供了操作菜单的消息,操作 Scintilla 控件的消息,还有插件的入口,资源回收的终结等等。开发起来只需要提供自己的类型,让它工作在合适的位置即可。
Notepad++ 插件开发要旨
Notepad++ 开发的要点主要分布在以下几个接口中,描述如下:插件的名称和自我描述,这一部分定义了插件在 plugin 菜单的显示,包括入口名称和子菜单条目。一般来说入口的名称显示在菜单向上,Notepad++ 定义了一个全局变量 NPP_PLUGIN_NAME 来描述这个名称,我们只需要更改它即可。
图 2 菜单项目和子菜单
插件的运行核心。所有的插件依赖 UI 的 plugin 菜单来激发一个特定的任务,所有的菜单初始化选项必须在 commandMenuInit 函数中完成。利用 Notepad++暴露的方法 setCommand 方法,可以把菜单项与插件自定的方法体关联起来,setCommand 的方法定义如下:
清单 3. setCommand 定义bool setCommand(size_t index,
TCHAR *cmdName,
PFUNCPLUGINCMD pFunc,
ShortcutKey *sk = NULL,
bool check0nInit = false);
其中的参数意义如下 :index:从零开始,表示菜单项目的顺序;
commandName: 显示在菜单上的名称;
functionPointer: 函数指针,表示函数的确切地址;
shortcut: 可选择,快捷键定义;
check0nInit: 可选择,定义这个菜单项是否为默认已选择条目。
如图 2 所示,利用 setCommand 函数所设置的相会自动显示在已定义的插件菜单条目中。
与 Notepad++的交互。在自定义的函数体内,可以获取 Notepad++的句柄,用来设置一些全局的条目,利用模板中定义好的导出函数 setInfo,Notepad++ 程序就会给插件提供一个 NppData 结构实例,其中就有当前 Notepad++的句柄。在本文中所示的插件视线中,打开 FastInfoset文件后,为了显示 XML 着色,必须发送一条切换语言的消息如下:
清单 4. 发送消息到 Notepad++::SendMessage(nppData._nppHandle,NPPM_SETCURRENTLANGTYPE,
0,(LPARAM)LangType::L_XML);
这样就可以在打开 FastInfoset 文件立刻切换至 XML 语言。
更重要的,插件需要与 Scintilla 控件交互,为了获取 Scintilla 的句柄,需要对 Notepad++ 程序问询,使得 Notepad++ 能够返回当前需要的 Scintilla 句柄。有了 Scintilla 句柄 ,就可以通过多种消息来控制显示和获取当前的文本了。方式如下:
清单 5. 设置文本// 设置文本
::SendMessage(curScintilla, SCI_SETTEXT, 0, (LPARAM)str);
清单 6. 获取文本// 获取文本
::SendMessage(curScintilla, SCI_GETTEXT, (WPARAM)(Length+1), (LPARAM)str);
需要说明的是,文本的获取必须传递文本的大小和存储缓存,文本大小可以通过 SCI_GETLENGTH 消息来取得。
利用 JNI 沟通 C++ 与 Java
Java Native Interface(JNI)是 Java 语言的本地编程接口,是 J2SDK 的一部分。在 java 程序中,我们可以通过 JNI 实现一些用 java 语言不便实现的功能。通常有以下几种情况我们需要使用 JNI 来实现。标准的 java 类库没有提供你的应用程序所需要的功能,通常这些功能是平台相关的;
你希望使用一些已经有的类库或者应用程序,而他们并非用 java 语言编写的;
程序的某些部分对速度要求比较苛刻,你选择用汇编或者 c 语言来实现并在 java 语言中调用他们。
在 Notepad++中调用 Java 代码
为了使用 Java 代码来打开和保存 FastInfoset文档,必须做到以下几点:构建 Java 虚拟机,JVM 可以使用 JNI 暴露的 JNI_CreateJavaVM 方法来构建,最好的方法是提供 JVM 动态库的本地地址,使用系统提供的 LoadLibrary 方法加载,然后之间查找到“JNI_CreateJavaVM”方法的地址,调用之后来获取 JVM 和 JNIEnv。具体如下:
清单 7. 构造 JNI 的运行环境JavaVMInitArgs vm_args;
JavaVMOption options[3];
/* 设置初始化参数 */
// 设置 classpath,如果程序用到了第三方的 JAR 包,也可以在这里面包含进来
options[0].optionString=cp;
// 内存大小设置
options[1].optionString="-Xmx256m";
options[2].optionString="-Xms128m";
// 设置版本号,版本号有 JNI_VERSION_1_1,JNI_VERSION_1_2 和 JNI_VERSION_1_4
// 选择一个根你安装的 JRE 版本最近的版本号即可,不过你的 JRE 版本一定要等于或者高于指定的版本号
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 3;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
// 加载 JVM.DLL 动态库
TCHAR JVM_LIB_HOME[MAX_PATH];
ZeroMemory(JVM_LIB_HOME,MAX_PATH*sizeof(TCHAR));
GetEnvironmentVariable(L"JVMDLL_HOME",JVM_LIB_HOME,MAX_PATH);
if(JVM_LIB_HOME[0] == '\0')
{
GetEnvironmentVariable(L"JAVA_HOME",JVM_LIB_HOME,MAX_PATH);
if(JVM_LIB_HOME[0] == '\0')
{
return;
}
}
TCHAR* FIX_JVM_LIB_PATH=L"\\jre\\bin\\server\\jvm.dll";
wcscat(JVM_LIB_HOME,FIX_JVM_LIB_PATH);
hInstance = ::LoadLibrary(JVM_LIB_HOME);
if(hInstance == NULL)
{
delete cp;
return;
}
// 取得里面的 JNI_CreateJavaVM 函数指针
PFunCreateJavaVM funCreateJavaVM = (PFunCreateJavaVM)::GetProcAddress(hInstance,
"JNI_CreateJavaVM");
// 调用 JNI_CreateJavaVM 创建虚拟机
jint res = (*funCreateJavaVM)(&jvm, (void**)&env, &vm_args);
构建的过程中,经常会因为各种问题失败,由于返回值仅仅是无符号数字,在 jni.h 的头文件中有有对应返回值的宏定义,有需要可以查询。比如文中提到的构建过程中遇到了返回值是 -4 的情况,查询后得知内存的大小未知,设置好需要的内存参数即可解决。
调用 Java 中的代码。利用创建好的 JVM 和 JNIEnv,就可以调用已有的 Java 代码,这些代码的库存在于穿件 JVM 时提供的 Classpath,首先必须查找到提供的类型,如下所示:
清单 8. 调用 Java 代码// 构造需要的类型实例
jclass cls = env->FindClass("plugin/xml/entryreader/internal/DocAchieve2");
// 然后查询所需要的函数:
jmethodID mid = env->GetMethodID(cls, "openFIDocument",
"(Ljava/lang/String;)Ljava/lang/String;");
// 最后调用函数即可:
jstring content = (jstring)env->CallObjectMethod(doc,mid,path);
在这里,env 的类型 JNIEnv,暴露了各种方法来构建 Java 实例和调用 Java 实例的方法,另外参数传递当中经常使用 Java String 类型和 C++ 中的 char 类型的转换,JNIEnv 也提供了完善的方法 GetStringUTFChars。
小结
虽然插件技术可以解决打开和保存 FastInfosetXML 文档的问题,但是使用上还是不方便。如果未来可以拦截打开文件的消息,然后判断是否需要插件介入,就可以自动完成打开的工作,未来的工作方式就可以跟优秀。
相关主题参考
"Notepad++"主站 首页,查看 Notepad++ 插件开发技术的详细信息。
参考“Java JNI Technology”,了解更多如何使用 JNI 技术的细节。
在 developerWorks 中国网站 XML 专区 进一步了解 XML 技术及其标准。查找技术文档、how-to 文章、培训等。
现在您可以免费使用 DB2。下载 IBM 软件下载:IBM DB2 Express-C 10.1,这是 DB2 Express Edition 的一个面向社区的免费版本,提供了与 DB2 Express Edition 相同的核心数据功能,为构建和部署应用程序提供了牢固的基础。