Microsoft Office OneNote — мощная цифровая записная книжка для сбора, организации, поиска и обмена информацией. С выходом новой версии — Microsoft Office 2010 — не только улучшено удобство использования OneNote, но и записные книжки OneNote стали более универсальными. Пользователи могут синхронизировать контент между компьютерами через Windows Live, искать, редактировать и обмениваться записями через любой веб-браузер, а также получать полный доступ к записным книжкам с Windows Mobile (а вскоре и с Windows Phone 7). Более того, OneNote ранее включался лишь в некоторые редакции Office, но теперь он поставляется с Office 2010 любой редакции. Все эти факторы в еще большей мере способствуют интеграции OneNote в решения, связанные с управлением информацией.
В этой статье я представлю обзор по разработке приложений, взаимодействующих с данными из Microsoft OneNote 2010 и 2007. Попутно мы познакомимся с проектом библиотеки модели объектов для OneNote, доступный бесплатно на CodePlex. Этот проект демонстрирует, как с помощью такой библиотеки упростить интеграцию информации из записных книжек OneNote в клиентские программы.
Эволюция разработки для OneNote
Первоначальный выпуск OneNote 2003 не предоставлял API внешним приложениям. Однако вскоре после этого был выпущен OneNote 2003 SP 1, в который добавили COM-библиотеку OneNote 1.1 Type Library; она поддерживала программный импорт изображений, рукописного ввода (ink) и HTML в OneNote с помощью простого класса CSimpleImporter. Увы, этот класс обеспечивал лишь импорт данных — вы могли использовать его для передачи данных в записные книжки OneNote, но получить данные из них в другое приложение программным способом было нельзя.
В OneNote 2007 появились гораздо более мощные средства программирования с применением нового COM API, который позволяет программно импортировать, экспортировать и модифицировать контент OneNote 2007. OneNote-класс Application в этой библиотеке предоставляет богатый набор методов для работы с перечисленными ниже элементами и функциями.
- Структура записной книжки —обнаружение, открытие, модификация, закрытие и удаление записных книжек, групп раздела и разделов.
- Содержимое страницы —обнаружение, открытие, модификация, сохранение и удаление содержимого страницы.
- Навигация —поиск, переход по ссылкам и просмотр страниц и объектов.
Большинство этих методов возвращает или принимает XML-документы, представляющие как структуру записной книжки, так и содержимое страницы. Сол Кандиб (Saul Candib) написал статью из двух частей «What’s New for Developers in OneNote 2007», в которой документировал этот API и которую можно найти по ссылке msdn.microsoft.com/library/ms788684(v=office.12), а детальное описание XML-схемы см. по ссылке msdn.microsoft.com/library/aa286798(office.12).
XML-схема OneNote 2010 в значительной мере сходна с таковой в OneNote 2007. В OneNote 2010 изменен формат файлов для поддержки некоторых новых средств (например, управление версиями, обмен через Интернет, многоуровневые вложенные страницы и поддержка уравнений). Однако OneNote 2010 позволяет по-прежнему работать с записными книжками OneNote 2007 без изменения формата файлов. В OneNote 2010 выборка данных из разделов, хранящихся в файловом формате OneNote 2007, приведет к получению XML-документов, аналогичных таковым в OneNote 2007. Основные различия в XML-схемах разделов OneNote 2010 — дополнительные изменения для поддержки новых средств, упомянутых выше. Для представления версии схемы OneNote введено новое перечисление XMLSchema; у многих методов OneNote появились новые перегруженные версии, которые принимают параметр XMLSchema и тем самым дают возможность указывать нужную версию схемы.
Заметьте, что класс CSimpleImporter, введенный в OneNote 2003 и по-прежнему доступный в OneNote 2007, в OneNote 2010 был удален, поэтому приложения, использующие этот класс, придется переписывать под новые интерфейсы — иначе они не смогут работать с OneNote 2010.
Доступ к данным OneNote через COM API
Использовать OneNote COM API для доступа к данным в записных книжках OneNote сравнительно несложно. Начнем с создания нового консольного приложения в Visual Studio, а затем добавим ссылку на COM-компонент Microsoft OneNote 14.0 Type Library (для OneNote 2010) или Microsoft OneNote 12.0 Type Library (для OneNote 2007).
Если вы используете Visual Studio 2010 для разработки приложений OneNote 2010, обратите внимание на несколько мелких проблем совместимости. Во-первых, из-за неподходящей interop-сборки OneNote, поставляемой с Visual Studio 2010, не ссылайтесь напрямую на компонент Microsoft.Office.Interop.OneNote на вкладке .NET диалога Add Reference, а указывайте ссылку на компонент Microsoft OneNote 14.0 Type Library на вкладке COM. Это позволит добавить в ссылки вашего проекта корректную interop-сборку OneNote.
Во-вторых, OneNote 14.0 Type Library несовместима с функциональностью Visual Studio 2010 под названием «NOPIA» (благодаря которой основные interop-сборки не встраиваются в приложение по умолчанию). Поэтому нужно обязательно установить свойство Embed Interop Types в False для ссылки на interop-сборку OneNote. [Обе эти проблемы более подробно описываются в блоге Дэниеля Эскапы (Daniel Escapa), менеджера программ из группы OneNote, см. blogs.msdn.com/descapa/archive/2010/04/27/onenote-2010-and-visual-studio-2010-compatibility-issues.aspx.) Добавив ссылку на библиотеку OneNote, вы готовы вызывать OneNote API. Код на рис. 1с помощью метода GetHierarchy получает XML-документ, содержащий список записных книжек OneNote, а затем, используя LINQ to XML, извлекает и выводит в консоль их названия.
Рис. 1. Перечисление записных книжек
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;
class Program
{
static void Main(string[] args)
{
var onenoteApp = new Application();
string notebookXml;
onenoteApp.GetHierarchy(null, HierarchyScope.hsNotebooks, out notebookXml);
var doc = XDocument.Parse(notebookXml);
var ns = doc.Root.Name.Namespace;
foreach (var notebookNode in
from node in doc.Descendants(ns + "Notebook") select node)
{
Console.WriteLine(notebookNode.Attribute("name").Value);
}
}
}
Перечисление HierarchyScope, переданное как второй параметр в метод GetHierarchy, указывает нужную вам глубину структуры записной книжки. Чтобы получить еще и разделы, просто измените значение этого перечисления на HierarchyScope.hsSections и обрабатывайте дополнительные дочерние XML-узлы, как показано на рис. 2.
Рис. 2. Перечисление разделов
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;
class Program
{
static void Main(string[] args)
{
var onenoteApp = new Application();
string notebookXml;
onenoteApp.GetHierarchy(null, HierarchyScope.hsSections, out notebookXml);
var doc = XDocument.Parse(notebookXml);
var ns = doc.Root.Name.Namespace;
foreach (var notebookNode in from node in doc.Descendants(ns +
"Notebook") select node)
{
Console.WriteLine(notebookNode.Attribute("name").Value);
foreach (var sectionNode in from node in
notebookNode.Descendants(ns + "Section") select node)
{
Console.WriteLine(" " + sectionNode.Attribute("name").Value);
}
}
}
}
Получение и обновление содержимого страницы
Метод GetPageContent вернет XML-документ, содержащий весь контент указанной страницы. Нужная страница задается уникальным идентификатором объекта на основе строки, который имеется у каждого объекта в иерархии записной книжки OneNote. Этот идентификатор включается как атрибут в XML-узлы, возвращаемые методом GetHierarchy.
На рис. 3 показан код на основе предыдущих примеров, в котором используется метод GetHierarchy для получения иерархии записной книжки OneNote вплоть до страницы. Затем с помощью LINQ to XML выбирается узел страницы с названием «Test page» и идентификатор объекта этой страницы передается в метод GetPageContent. После этого в консоли выводится содержимое страницы, представленное XML-документом.
Рис. 3. Получение содержимого страницы
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;
class Program
{
static void Main(string[] args)
{
var onenoteApp = new Application();
string notebookXml;
onenoteApp.GetHierarchy(null, HierarchyScope.hsPages, out notebookXml);
var doc = XDocument.Parse(notebookXml);
var ns = doc.Root.Name.Namespace;
var pageNode = doc.Descendants(ns + "Page").Where(n =>
n.Attribute("name").Value == "Test page").FirstOrDefault();
if (pageNode != null)
{
string pageXml;
onenoteApp.GetPageContent(pageNode.Attribute("ID").Value, out pageXml);
Console.WriteLine(XDocument.Parse(pageXml));
}
}
}
Вносить изменения в страницу можно методом UpdatePageContent. Содержимое страницы определяется той же схемой XML-документа, что и получаемой кодом на рис. 3; она может содержать различные элементы контента, определяющие обтекание текста, вставленные файлы, изображение, рукописный ввод, аудио- и видеофайлы.
Метод UpdatePageContent обрабатывает элементы в предоставленном XML-документе как набор контента, который можно изменять; при этом указанный контент проверяется на совпадение с существующим по его идентификатору объекта в OneNote. Благодаря этому вы можете изменять существующий контент: вы вызываете метод GetPageContent, вносите нужные изменения в возвращенный XML, а затем передаете этот XML методу UpdatePageContent. Кроме того, вы можете указать новые элементы контента для добавления к странице.
Для иллюстрации сказанного код на рис. 4 добавляет в нижнюю часть нашей тестовой страницы метку с датой. Чтобы определить идентификатор объекта в OneNote на странице, используется подход, представленный на рис. 3, а затем с помощью классов XDocument и XElement из System.Xml.Linq формируется XML-документ, содержащий новый контент. Поскольку идентификатор объекта Page, указанный в документе, соответствует идентификатору объекта существующей страницы, метод UpdatePageContent добавит новый контент к существующей странице.
Рис. 4. Модификация содержимого страницы
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;
class Program
{
static void Main(string[] args)
{
var onenoteApp = new Application();
string notebookXml;
onenoteApp.GetHierarchy(null, HierarchyScope.hsPages, out notebookXml);
var doc = XDocument.Parse(notebookXml);
var ns = doc.Root.Name.Namespace;
var pageNode = doc.Descendants(ns + "Page").Where(n =>
n.Attribute("name").Value == "Test page").FirstOrDefault();
var existingPageId = pageNode.Attribute("ID").Value;
if (pageNode != null)
{
var page = new XDocument(new XElement(ns + "Page",
new XElement(ns + "Outline",
new XElement(ns + "OEChildren",
new XElement(ns + "OE",
new XElement(ns + "T",
new XCData("Current date: " +
DateTime.Now.
ToLongDateString())))))));
page.Root.SetAttributeValue("ID", existingPageId);
onenoteApp.UpdatePageContent(page.ToString(), DateTime.MinValue);
}
}
}
Библиотека OneNote Object Model
Взаимодействовать с данными в OneNote таким способом не особенно сложно, но разбирать и заново конструировать XML-документы только для того, чтобы выполнять базовые операции над данными, весьма неудобно. И здесь на сцену выходит OneNote Object Model. Это библиотека управляемого кода, которая предоставляет объектно-ориентированные абстракции поверх OneNote API на основе COM. Эта библиотека имеет открытый исходный код и лицензируется по лицензии Microsoft Public License (Ms-PL).
OneNote Object Model доступна для скачивания на сайте CodePlex по ссылке onom.codeplex.com. Эта библиотека была создана для OneNote 2007, и к тому времени, когда вы будете читать эту статью, она должна будет обновлена для совместимости с OneNote 2010. Если же этого не произойдет, вы все равно сможете использовать ее с разделами OneNote 2007 в OneNote 2010, но для этого вам придется скачать ее исходный код, удалить существующую ссылку на сборку Microsoft.Office.Interop.OneNote в проекте OneNoteCore и добавить ссылку на Microsoft OneNote 14.0 Type Library, как уже описывалось ранее.
Помимо проектов для модульного тестирования и примеров кода, в решение также входят два проекта библиотек классов: OneNoteCore и OneNoteFramework. Библиотека OneNoteCore служит низкоуровневым связующим звеном между OneNote COM API и привычными метафорами Microsoft .NET Framework; она предоставляет реальные возвращаемые значения вместо выходныхCOM-параметров, преобразует коды ошибок COM в .NET-исключения, обеспечивает доступ к структуре OneNoteObjectId и экземплярам XDocument вместо неструктурированных строк и т. д. Изучение этого кода поможет вам понять, как работает OneNote API, но в большинстве случаев вам не понадобится напрямую взаимодействовать с библиотекой OneNoteCore.
Библиотека OneNoteFramework предоставляет более высокоуровневые абстракции концепций OneNote. Здесь вы найдете классы с интуитивно понятными именами вроде OneNoteNotebook, OneNoteSection и OneNotePage. Основная точка входа для взаимодействия с иерархией OneNote — класс OneNoteHierarchy, содержащий статический член Current. Добавив ссылку на сборку библиотеки OneNoteFramework, можно переписать нашу программу, которая перечисляет названия записных книжек (рис. 1), и сделать ее гораздо более лаконичной:
using Microsoft.Office.OneNote;
class Program
{
static void Main(string[] args)
{
foreach (var notebook in OneNoteHierarchy.Current.Notebooks)
System.Console.WriteLine(notebook.Name);
}
}
Как и следовало ожидать, у класса OneNoteNotebook есть свойство Sections. Поэтому вы можете перечислить названия разделов (рис. 2) намного проще:
using Microsoft.Office.OneNote;
class Program
{
static void Main(string[] args)
{
foreach (var notebook in OneNoteHierarchy.Current.Notebooks)
{
System.Console.WriteLine(notebook.Name);
foreach (var section in notebook.Sections)
{
System.Console.WriteLine(" " + section.Name);
}
}
}
}
Наборы, предоставляемые свойствами OneNote Object Model, управляются специализированным обобщенным классом-набором OneNoteObjectCollection<T>. Так как OneNoteObjectCollection<T> реализует IList<T> и IEnumerable<T>, эти наборы можно запрашивать, используя LINQ.
Например, при наличии ссылки на экземпляр OneNoteSection в переменной section, мы могли бы определить все страницы, модифицированные сегодня, с помощью простого LINQ-выражения:
var pagesModifiedToday = from page in section.Pages
where page.LastModifiedTime >= DateTime.Today
select page;
Привязка к данным с помощью библиотеки OneNote Object Model
Тот факт, что OneNote Object Model предоставляет IEnumerable-наборы, также означает, что библиотека поддерживает привязку данных на основе XAML к элементам управления Windows Presentation Foundation (WPF). На рис. 5 демонстрируется применение привязки данных для отображения в WPF-элементе TreeView иерархии записной книжки OneNote; при этом используется исключительно XAML-разметка — никакого отделенного кода не требуется.
Рис. 5. Привязка данных к элементам управления WPF
<Window x:Class="NotebookTree.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:onf="clr-namespace:Microsoft.Office.OneNote;assembly=
OneNoteFramework"
Title="OneNote Notebook Hierarchy" >
<Grid>
<Grid.Resources>
<DataTemplate x:Key="PageTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="Images\Page16.png" Margin="0,0,2,0"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="SectionTemplate"
ItemsSource="{Binding Pages}"
ItemTemplate="{StaticResource PageTemplate}">
<StackPanel Orientation="Horizontal">
<Image Source="Images\Section16.png" Margin="0,0,2,0"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="NotebookTemplate"
ItemsSource="{Binding Sections}"
ItemTemplate="{StaticResource SectionTemplate}">
<StackPanel Orientation="Horizontal">
<Image Source="Images\Book16.png" Margin="0,0,2,0"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView Name="NotebookTree" BorderThickness="0"
HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{Binding Notebooks}"
ItemTemplate="{StaticResource NotebookTemplate}"
DataContext="{Binding Source={x:Static
onf:OneNoteHierarchy.Current}}" />
</Grid>
</Window>
Этот XAML сначала ссылается на сборку OneNoteFramework, присваивая ей XML-префикс пространства имен onf. После этого DataContext для TreeView может быть присвоен статическому свойству Current класса OneNoteHierarchy, что дает элементу управления корень иерархии OneNote. Затем используется HierarchicalDataTemplates для привязки данных каждого уровня дерева к соответствующему набору, предоставляемому OneNote Object Model (рис. 6).
Рис. 6. Привязка данных иерархии к элементу управления TreeView
Упрощенный доступ к данным
В заключение отметим, что библиотека OneNote Object Model значительно упрощает доступ к данным в записных книжках Microsoft OneNote, предоставляя весьма функциональные объекты-наборы, которые можно запрашивать и которыми можно манипулировать с помощью LINQ-выражений и использования механизма связывания с данными в WPF. В следующей статье мы подробно раскроем все эти концепции и посмотрим, как работать с записными книжками OneNote в приложениях Silverlight и Windows Phone, а также обращаться к данным OneNote в облаке.