OneNote 开发与使用

本人是一名大四软件工程开发狗,使用过的技术和框架也有很多,至于编程语言的话,主流的编程开发语言都有所了解,比较熟悉的是 C#、JAVA和Python,另外对 C++也比较熟悉。废话不多说,今天是我第一次写博客,有点小激动,如有不足,可以指出。

开始进入正题

我目前在一家公司实习,做一些关于本土化的工作,涉及到了开发、测试、数据分析和翻译(一直不愿意讲出来),最近我在工作中有一个小小的需求,利用开发手段识别图片上的文字,这个大家都懂,就是 OCR 技术,如今 OCR 算法和技术都已经很多了,而且网络中也有很多 API 可以使用,但是出于稳定和其他因素考虑,我准备使用 Microsoft Graphic,这个组件存在于 Office 套件中,之前一直存在于 Office 工具中,被称作是 Microsoft Office Document Imaging,但是可能因为很多人在使用 Office 时忽略了他的存在,因此在 Office 2010 版之后,这个功能不独立存在,而是被集成到了 OneNote 中,也就是当你在 OneNote 中右击鼠标弹出菜单时会有一个选项选择复制图片上的文字,没错他就是 Microsoft Graphic,这显然已经成为 OneNote 的一个组件,而且微软对 OneNote 有很大的技术支持,所以我们可以很轻松的开发出基于 OneNote 的应用程序,本篇博客我们采用 Office 2013 开发。

接下来,我们正式进入主题部分

开启 Programming Tool

就是要开启 OneNote 的 Programming Tool 功能,这个功能在我们系统的 Control Panel\All Control Panel Items\Programs and Features 中。

  1. 打开 Office 添加功能窗口
    你可以单击你安装 Office 的那一项,我的计算机上面是 Microsoft Office Professional Plus 2013,然后点击修改按钮或者添加与修改按钮,即可弹出下面的这个窗口。
    Office 2013 修改窗口
    然后一步一步进行到下面这个窗口
    Office 2013 添加功能窗口
  2. 安装 OneNote 的编程开发支持
    选择 Microsoft OneNote-〉.NET 可编程性支持-〉从本机运行
    这里写图片描述
  3. 安装 Office 工具中其他与 OCR 相关的工具
    接下来应该选择 Office 工具,这里我的建议是最好全部安装,如果有 x,都选择从本机运行
    这里写图片描述

这样,就对 OneNote 添加了开发支持,接下来,我将会讲一下OneNode的原理。

有人肯定会问, OneNote 怎么开发,这个既不像写 TXT 一样,用 StreamWriter 方法直接写文字到文件,也不像开发 Word 那样直接可以在一个方法里面写上文字,就把文字写到了文档中,所以怎么办?这里就要讲到一个概念——文件结构。其实文件都是有一些特定的结构,才能把它组织起来。如果开发过Word的朋友可能都知道,Word开发首先要新建一个 Microsoft.Office.Interop.Word.Application 对象,然后再新建一个 Document 对象,表示新建一个文档,再对其进行操作,如完全插入和增量插入、删除文字、设置字体等等功能。对于 OneNote 开发,其实与 Word 开发大同小异,唯一不同的是 OneNote 的文件结构其实是由标签(类似于一种XML或者HTML)组织起来的,也就是说我们在操作它时,其实是在对 XML 进行操作,所以在开发过程中,我们还需要处理 XML 函数的支持,下面是一个 OneNote 文件结构树:

OneNote File
   ├─notebook1
   │  └─sectiongroup
   │      └─section
   │          └─page
   │              └─pagecontent
   │                  └─resource
   ├─notebook2
   │  └─ ...
   ├─ ...
   ├──Open Sections(sectiongroup)
   │      └─section
   │          └─page
   │              └─pagecontent
   │                  └─resource
   ├─Misplace Sections(sectiongroup)
   │      └─section
   │          └─page
   │              └─pagecontent
   │                  └─resource
   └──────Quick Notes(Section)
               └─OneNote: one place for all of your notes
                   └─ ...
               └─OneNote Basics
                    └─ ...

正如我们看到的那样,在 OneNote 中,最顶层是每一个 notebook,每一个 notebook 包含1个 section group,每一个 section group 包含多个 section,一个 section 又包含1个 pagegroup,每一个pagegroup 包含多个 page,一个 page 里面就是我们的内容,内容不仅仅可以包含文字照片,还有其他形式的数据资源。但是有两个 section group 比较特殊,它是单独存在于 OneNote 中,一个是 Open Sections,它的作用是组织单独的且不存在于 notebook,以文件形式存在的 onenote 文件(以 *.one 结尾),我们的开发目的是为了不影响其他 notebook 的正常使用,所以 OneNote 开发操作的文件一般是在Open Sections 中。另一个 Misplace Sections 我不太常用到,所以没去了解,总之,对我们的项目不会有什么影响。

下面是一个Section的组织结构:

<?xml version="1.0"?>
    <one:Section 
    xmlns:one="http://schemas.microsoft.com/office/onenote/2013/onenote" 
    name="newfile" ID="{C4B83D4E-D5DE-02CE-106E-C48306D33FB6}{1}{B0}"
    path="C:\Users\john\Documents\Visual Studio 2015\Projects\Test_Automation_Research\Test_Automation_Research\bin\Debug\tmpPath\newfile.one" lastModifiedTime="2016-10-31T05:42:58.000Z" 
    color="#ADE792"
    isCurrentlyViewed="true">
        <one:Page 
        xmlns:one="http://schemas.microsoft.com/office/onenote/2013/onenote"
         ID="{C4B83D4E-D5DE-02CE-106E-C48306D33FB6}{1}{E1956424128518262860961971909334097201394501}" 
         name="Title"
         dateTime="2016-10-31T05:59:56.000Z" 
         lastModifiedTime="2016-10-31T05:59:56.000Z" 
         pageLevel="1">
            <one:PageSettings RTL="false" color="automatic">
                <one:PageSize><one:Automatic/></one:PageSize>
                <one:RuleLines visible="false"/>
            </one:PageSettings>
            <one:Outline 
                author="Chen, Jinglei"
                authorInitials="CJ(PTTPP" 
                lastModifiedBy="Chen, Jinglei"
                lastModifiedByInitials="CJ(PTTPP" 
                lastModifiedTime="2016-10-31T06:00:04.000Z"
                objectID="{BB3D1B81-C691-44DE-8A8C-0B07A5714EB9}{10}{B0}">
                <one:Position x="36.0" y="32.39999771118164" z="0"/>
                <one:Size width="468.0" height="72.19833374023437"/>
                <one:OEChildren>
                    <one:OE creationTime="2016-10-31T06:00:04.000Z"
                         lastModifiedTime="2016-10-31T06:00:04.000Z"
                         objectID="{BB3D1B81-C691-44DE-8A8C-0B07A5714EB9}{11}{B0}"
                         alignment="left">
                         <one:Image>
                             <one:Size width="1089.0" height="168.0"/>
                             <one:Data>…各种资源信息</one:Data>
                             <one:OCRData lang="en-US">
                                 <one:OCRText><![CDATA[...OCR识别结果...]]></one:OCRText>
                                 <one:OCRToken 
                                         startPos="0" 
                                         region="0" 
                                         line="0" 
                                         x="3.751515865325928" 
                                         y="3.001212596893311" 
                                         width="78.7818374633789" 
                                         height="15.00606346130371"/>
                                         ...OCR识别位置描述...
                                </one:OCRData>
                        </one:Image>
                    </one:OE>
                </one:OEChildren>
            </one:Outline>
        </one:Page>
    </one:Section>

上述结构描述对应的 One 文件图示
一切开发来自于需求,所以我们博客通过一个实际的需求,来对 OneNote 进行讲解。我们的步骤如下:

Created with Raphaël 2.1.0 开始 新建一个OneNote文件 在OneNote的Page里面写入一张图片 识别图片文字 输出图片文字 销毁创建的OneNote文件 结束

准备

打开 Visual Studio,新建一个 C# 项目,并添加一个来自 COM 的引用,名称为 Microsoft OneNote 15.0 Object Library,同时应该还要添加 Microsoft Office 15.0 Object Library 和 Microsoft Graph 15.0 Object Library。
添加项目引用

正式编写

接下来,我们开始正式编写 OneNote 应用程序,由于开发步骤与实际使用相符,我们可以按照实际使用一步一步编写代码:

正如 Word 开发一样,首先我们要新建一个 OneNote 对象,正如下面这句代码所示

var onenoteApp = new Microsoft.Office.Interop.OneNote.Application();  //onenote提供的API

接下来,我们应该要建立一个 One 文件,在微软MSDN中有详细的文档,使用的函数和解释如下:
文档说明
所以我们代码应该是这样的

string sectionID;//这里存储了新建的section的id
private static readonly string tmpPath = AppDomain.CurrentDomain.BaseDirectory + "tmpPath/";
onenoteApp.OpenHierarchy(tmpPath + "newfile.one", null, out sectionID, CreateFileType.cftSection);

接着,我们要创建一个新的页面,所以我们要使用到 CreateNewPage 函数,它的 out 参数则存储了 pageID

string pageID = "{A975EE72-19C3-4C80-9C0E-EDA576DAB5C6}{1}{B0}";  // 格式 {guid}{tab}{??}
onenoteApp.CreateNewPage(sectionID, out pageID, NewPageStyle.npsBlankPageNoTitle);

现在我们就创建了一个页面,如果在 OneNote 中,我们应该要在 Page 中进行相关操作,而对于代码来讲,我们应该要做的操作是先拿到创建好的 Page 的 ID,然后把照片、文字资源按照 OneNote 的结构格式进行包装,最后将包装好的 PageCotent 插入到Page中,也就是将 PageContent 这个子节点插入到Page这个父节点中,这样就将图片或者文字资源添加到页面当中了。
所以我们应该要先拿到 Page 的 ID,由于 Page 的 ID 是存在于整个 Section 中,所以我们要获取整个 Section 对应的 XML 树,这里就用到了 GetHierarchy 这个函数,而它有一个 out 参数是保存了 Section 的内容,同时我们也获取了 ns 参数。

string notebookXml;
onenoteApp.GetHierarchy(pageID, HierarchyScope.hsPages, out notebookXml);
var doc = XDocument.Parse(notebookXml);
var ns = doc.Root.Name.Namespace;
var pageNode = doc.Descendants(ns + "Page").FirstOrDefault();
var existingPageId = pageNode.Attribute("ID").Value;

下一步是对图片和文字资源进行包装,按照 onenote 的标准利用 XmlDocument 对象将资源进行包装,我们的格式应该为

Page->Outline->OEChildren->OE->Image->format
                                    ->originalPageNumber
                                    ->Position->x
                                              ->y
                                              ->z
                                    ->Size->width
                                          ->height
                                    ->Data

所以,与 XML 操作类似,我们的代码应该如下:

Tuple<string, int, int> imgInfo = this.GetBase64(fi);
var page = new XDocument(new XElement(ns + "Page",
                             new XElement(ns + "Outline",
                                 new XElement(ns + "OEChildren",
                                     new XElement(ns + "OE",
                                         new XElement(ns + "Image",
                                             new XAttribute("format", fi.Extension.Remove(0, 1)), 
                                                 new XAttribute("originalPageNumber", "0"),
                                                 new XElement(ns + "Position",
                                                     new XAttribute("x", "0"), 
                                                     new XAttribute("y", "0"), 
                                                     new XAttribute("z", "0")),
                                                 new XElement(ns + "Size",
                                                     new XAttribute("width", imgInfo.Item2), 
                                                     new XAttribute("height", imgInfo.Item3)),
                                                 new XElement(ns + "Data", imgInfo.Item1)))))));
page.Root.SetAttributeValue("ID", existingPageId);

除此之外,oneNote 还要求图片资源应该转换成为 Base64 码,因此我们还需要编写一个函数用于其转换:

private Tuple<string, int, int> GetBase64(FileInfo file)
{
    using (MemoryStream ms = new MemoryStream())
    {
       Bitmap bp = new Bitmap(file.FullName); 
       switch (file.Extension.ToLower())
       {
           case ".jpg":
               bp.Save(ms, ImageFormat.Jpeg);
               break;

           case ".jpeg":
               bp.Save(ms, ImageFormat.Jpeg);
               break;

           case ".gif":
               bp.Save(ms, ImageFormat.Gif);
               break;

           case ".bmp":
               bp.Save(ms, ImageFormat.Bmp);
               break;

           case ".tiff":
               bp.Save(ms, ImageFormat.Tiff);
               break;

           case ".png":
               bp.Save(ms, ImageFormat.Png);
               break;

           case ".emf":
               bp.Save(ms, ImageFormat.Emf);
               break;

           default:
               return new Tuple<string, int, int>("不支持的图片格式。", 0, 0);
        }
        byte[] buffer = ms.GetBuffer();
        return new Tuple<string, int, int>(Convert.ToBase64String(buffer), bp.Width, bp.Height);
    }
}

当然,完成了包装之后,我们没有将数据插入到 page 中,我们这里可以采用 UpdatePageContent 这个函数

onenoteApp.UpdatePageContent(page.ToString(), DateTime.MinValue);

到目前为止,我们已经把图片资源插入到了 Page 中,在这里,我比较建议的是根据文件大小需要有一个休眠,因为这时 OCR 线程要对图片进行识别,所以代码如下:

 // 线程休眠时间,单位毫秒,若图片很大,则延长休眠时间,保证Onenote OCR完毕
int fileSize = Convert.ToInt32(fi.Length / 1024 / 1024); // 文件大小 单位M
System.Threading.Thread.Sleep(waitTime * (fileSize > 1 ? fileSize : 1)); // 小于1M的都默认1M

这时,OneNote 已经完成了整个数据的识别,我们需要获取识别结果,通过 XmlDocument 对象,我们可以很轻松的获取到识别结果,代码如下:

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(pageXml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("one", ns.ToString());
XmlNode xmlNode = xmlDoc.SelectSingleNode("//one:Image//one:OCRText", nsmgr);
string str = xmlNode.InnerText;

最后一步,不要忘记要销毁掉刚才创建的这个页面,具体代码如下:

onenoteApp.DeleteHierarchy(pageID, DateTime.MinValue, true);

至此,整个识别过程就完成了。

测试

我在网站上面截取了一张图片,如下图:
这里写图片描述
运行代码:
这里写图片描述

效果还是不错滴!

注意事项:

  • 识别识别结果受到分辨率的限制而有所差异

  • 识别中文需要在 Office 中文语言环境下进行,其他语言的原理是一样的

  • 识别中文可能会出现字符与字符之间存在空格,我的办法是将文字复制粘贴到word,然后将空格全部替换掉。

结束语:

至此,我们已经完成了开始设定的需求,测试效果也令人满意。OneNote 其实是一款非常强大的笔记软件,它的接口也是十分丰富的,除了为本地应用设计的接口外,它还设计了一套REST标准的web接口,如果有时间,我会去研究一下并写一个blog给到大家。最后还要感谢一些先行者,本blog也是参考了网上的一些代码,结合msdn文档所写,如有不足,请留言指出,谢谢。


  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值