Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub(crate) type Reader<'b> = &'b mut reader::Reader;

pub use crate::fs::FileSystem;
pub use crate::onenote::Parser;
pub use crate::onenote::file_identity::FileIdentity;

/// The data that represents a OneNote notebook.
pub mod notebook {
Expand Down
26 changes: 26 additions & 0 deletions crates/parser/src/onenote/file_identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::shared::guid::Guid;
use std::fmt;

/// The identity stored in a OneNote file header.
///
/// A file identity remains the same when the corresponding `.one` or
/// `.onetoc2` file is renamed or moved. It is suitable for associating
/// application state with a notebook, section, or section group without
/// depending on its display name or position.
///
/// Treat the value as opaque. OneNote may assign a new identity when it creates
/// a replacement file, even if that file represents the same logical content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct FileIdentity(Guid);

impl FileIdentity {
pub(crate) fn new(value: Guid) -> Self {
Self(value)
}
}

impl fmt::Display for FileIdentity {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0.0.hyphenated(), formatter)
}
}
4 changes: 4 additions & 0 deletions crates/parser/src/onenote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::fs::file_source::BytesSource;
use crate::fs::native_fs::NativeFs;
use crate::fsshttpb::data::exguid::ExGuid;
use crate::fsshttpb::packaging::{OneStorePackaging, embedded_packaging_offset};
use crate::onenote::file_identity::FileIdentity;
use crate::onenote::ink_recognition::InkRecognizedWord;
use crate::onenote::notebook::Notebook;
use crate::onenote::section::{Section, SectionEntry, SectionGroup};
Expand All @@ -28,6 +29,7 @@ use uuid::Uuid;

pub(crate) mod content;
pub(crate) mod embedded_file;
pub(crate) mod file_identity;
pub(crate) mod iframe;
pub(crate) mod image;
pub(crate) mod ink;
Expand Down Expand Up @@ -175,6 +177,7 @@ impl<FS: FileSystem> Parser<FS> {
}

Ok(Notebook {
file_identity: FileIdentity::new(store.as_onestore().file_identity()),
entries: sections,
color,
report,
Expand Down Expand Up @@ -269,6 +272,7 @@ impl<FS: FileSystem> Parser<FS> {
return self
.parse_notebook(entry.to_path())
.map(|group| SectionGroup {
file_identity: group.file_identity,
display_name,
entries: group.entries,
});
Expand Down
7 changes: 7 additions & 0 deletions crates/parser/src/onenote/notebook.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::errors::{ErrorKind, Result};
use crate::fsshttpb::data::exguid::ExGuid;
use crate::one::property_set::toc_container;
use crate::onenote::file_identity::FileIdentity;
use crate::onenote::section::SectionEntry;
use crate::onestore::ObjectSpace;
use crate::property::common::Color;
Expand All @@ -10,12 +11,18 @@ use std::collections::HashSet;
/// A OneNote notebook.
#[derive(Clone, Debug)]
pub struct Notebook {
pub(crate) file_identity: FileIdentity,
pub(crate) entries: Vec<SectionEntry>,
pub(crate) color: Option<Color>,
pub(crate) report: Report,
}

impl Notebook {
/// The identity stored in the notebook table-of-contents file.
pub fn file_identity(&self) -> FileIdentity {
self.file_identity
}

/// The section entries of this notebook.
pub fn entries(&self) -> &[SectionEntry] {
&self.entries
Expand Down
14 changes: 14 additions & 0 deletions crates/parser/src/onenote/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::errors::{ErrorKind, Result};
use crate::one::property::color::Color;
use crate::one::property_set::{section_metadata_node, section_node};
use crate::onenote::ParserContext;
use crate::onenote::file_identity::FileIdentity;
use crate::onenote::page_series::{PageSeries, parse_page_series};
use crate::onestore::{ObjectSpace, OneStore};
use crate::warn::Report;
Expand All @@ -22,13 +23,19 @@ pub enum SectionEntry {
/// [\[MS-ONE\] 2.2.17]: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-one/6913913f-b7d1-4b29-ab09-231ea3835ac2
#[derive(Clone, Debug)]
pub struct Section {
file_identity: FileIdentity,
display_name: String,
page_series: Vec<PageSeries>,
color: Option<Color>,
pub(crate) report: Report,
}

impl Section {
/// The identity stored in the section file.
pub fn file_identity(&self) -> FileIdentity {
self.file_identity
}

/// The section name.
pub fn display_name(&self) -> &str {
&self.display_name
Expand All @@ -53,11 +60,17 @@ impl Section {
/// A group of sections.
#[derive(Clone, Debug)]
pub struct SectionGroup {
pub(crate) file_identity: FileIdentity,
pub(crate) display_name: String,
pub(crate) entries: Vec<SectionEntry>,
}

impl SectionGroup {
/// The identity stored in the section group's table-of-contents file.
pub fn file_identity(&self) -> FileIdentity {
self.file_identity
}

/// The group name.
pub fn display_name(&self) -> &str {
&self.display_name
Expand Down Expand Up @@ -92,6 +105,7 @@ pub(crate) fn parse_section(store: &(impl OneStore + ?Sized), filename: String)
.collect::<Result<_>>()?;

Ok(Section {
file_identity: FileIdentity::new(store.file_identity()),
display_name,
page_series,
color: metadata.color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use onenote_parser_macros::Parse;
})]
pub(crate) struct OneStoreHeader {
pub(crate) file_type: Guid,
_guid_file: Guid,
pub(crate) guid_file: Guid,
pub(crate) legacy_file_version: Guid,
pub(crate) file_format: OneStoreFormatGuid,
pub(crate) ffv_last_code_that_wrote_to_this_file: u32,
Expand Down
4 changes: 4 additions & 0 deletions crates/parser/src/onestore/desktop/one_store_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ impl fmt::Debug for RevisionStore {
}

impl OneStore for RevisionStore {
fn file_identity(&self) -> crate::shared::guid::Guid {
self.header.guid_file
}

fn get_type(&self) -> Result<OneStoreType> {
if self.header.file_type == guid!("{7B5C52E4-D88C-4DA7-AEB1-5378D02996D3}") {
Ok(OneStoreType::Section)
Expand Down
4 changes: 4 additions & 0 deletions crates/parser/src/onestore/fsshttpb/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ pub(crate) struct StoreHeader {
}

impl StoreHeader {
pub(crate) fn file_identity(&self) -> Guid {
self.file_identity
}

pub(crate) fn parse(data: &ObjectGroup) -> Result<StoreHeader> {
let (_, object_data) = data
.declarations
Expand Down
4 changes: 4 additions & 0 deletions crates/parser/src/onestore/fsshttpb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ impl PackagingStore {
}

impl OneStore for PackagingStore {
fn file_identity(&self) -> Guid {
self.header.file_identity()
}

fn get_type(&self) -> Result<OneStoreType> {
if self.is_onestore() {
Ok(OneStoreType::Section)
Expand Down
3 changes: 3 additions & 0 deletions crates/parser/src/onestore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::errors::Result;
use crate::fsshttpb::data::cell_id::CellId;
use crate::fsshttpb::data::exguid::ExGuid;
use crate::one::property_set::PropertySetId;
use crate::shared::guid::Guid;
use shared::compact_id::CompactId;
use shared::file_blob::FileBlob;
use shared::jcid::JcId;
Expand All @@ -16,6 +17,8 @@ pub mod fsshttpb;
pub mod shared;

pub(crate) trait OneStore: fmt::Debug {
fn file_identity(&self) -> Guid;

fn get_type(&self) -> Result<OneStoreType>;

fn data_root(&self) -> &dyn ObjectSpace;
Expand Down
40 changes: 40 additions & 0 deletions crates/parser/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use onenote_parser::Parser;
use onenote_parser::contents::{Content, OutlineElement, OutlineItem, TextHyperlink};
use onenote_parser::fs::native_fs::NativeFs;
use onenote_parser::fs::{FileSource, FileSystem};
use onenote_parser::section::SectionEntry;
use std::io;
use std::sync::Arc;
use typed_path::{TypedPath, TypedPathBuf};
Expand Down Expand Up @@ -68,6 +69,45 @@ fn test_readme_example_parse_notebook() {
assert!(!notebook.entries().is_empty());
}

#[test]
fn file_identity_survives_section_rename() {
let original = "tests/samples/New Section 1.one";
let temp = tempfile::tempdir().unwrap();
let renamed = temp.path().join("Renamed Section.one");
std::fs::copy(original, &renamed).unwrap();

let parser = Parser::new();
let original = parser.parse_section(tp(original)).unwrap();
let renamed = parser.parse_section(tp(renamed.to_str().unwrap())).unwrap();

assert_eq!(original.file_identity(), renamed.file_identity());
assert_eq!(original.file_identity().to_string().len(), 36);
}

#[test]
fn notebook_entries_expose_distinct_file_identities() {
let notebook = Parser::new()
.parse_notebook(tp(
"tests/samples/joplin/Notebook created on OneNote App/Abrir Bloco de Anotações.onetoc2",
))
.unwrap();

let group = notebook
.entries()
.iter()
.find_map(|entry| match entry {
SectionEntry::SectionGroup(group) => Some(group),
SectionEntry::Section(_) => None,
})
.expect("public fixture should contain a section group");

assert_ne!(notebook.file_identity(), group.file_identity());
assert!(group.entries().iter().all(|entry| match entry {
SectionEntry::Section(section) => section.file_identity() != group.file_identity(),
SectionEntry::SectionGroup(child) => child.file_identity() != group.file_identity(),
}));
}

#[test]
fn test_parse_section_handwriting_recognition() {
let path = tp("tests/samples/handwriting_recognition.one");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 70
expression: parser.parse_section(path).unwrap()
---
Section {
file_identity: FileIdentity(
Guid {AF6DE84B-D817-4F07-8BAF-FB6629CB7C2C},
),
display_name: "OneWithFileData",
page_series: [
PageSeries {
Expand Down
7 changes: 6 additions & 1 deletion crates/parser/tests/snapshots/lib__parse_notebook.snap
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 28
expression: parser.parse_notebook(path).unwrap()
---
Notebook {
file_identity: FileIdentity(
Guid {F1DA443F-A65F-4513-B200-78D8A9910B8D},
),
entries: [
Section(
Section {
file_identity: FileIdentity(
Guid {0575DD0A-5612-D746-B82B-983F7A80B282},
),
display_name: "New Section 1",
page_series: [
PageSeries {
Expand Down
13 changes: 12 additions & 1 deletion crates/parser/tests/snapshots/lib__parse_notebook_new.snap
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 36
expression: parser.parse_notebook(path).unwrap()
---
Notebook {
file_identity: FileIdentity(
Guid {F1DA443F-A65F-4513-B200-78D8A9910B8D},
),
entries: [
Section(
Section {
file_identity: FileIdentity(
Guid {26A89915-6861-9B44-856A-B6296CF894AF},
),
display_name: "New Section 1 2",
page_series: [
PageSeries {
Expand Down Expand Up @@ -8079,6 +8084,9 @@ Notebook {
),
Section(
Section {
file_identity: FileIdentity(
Guid {11B448EB-EBF3-4D0D-9347-6A15FDEA7F08},
),
display_name: "New Section 2",
page_series: [
PageSeries {
Expand Down Expand Up @@ -8931,6 +8939,9 @@ Notebook {
),
Section(
Section {
file_identity: FileIdentity(
Guid {760B77AF-CC00-A44A-9F63-8BB5350E12A0},
),
display_name: "New Section 3",
page_series: [
PageSeries {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 49
expression: parser.parse_notebook(path).unwrap()
---
Notebook {
file_identity: FileIdentity(
Guid {9CF59D79-CD9E-4936-9333-44274544B327},
),
entries: [],
color: Some(
Color {
Expand Down
4 changes: 3 additions & 1 deletion crates/parser/tests/snapshots/lib__parse_section.snap
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 20
expression: parser.parse_section(path).unwrap()
---
Section {
file_identity: FileIdentity(
Guid {0575DD0A-5612-D746-B82B-983F7A80B282},
),
display_name: "New Section 1",
page_series: [
PageSeries {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 62
expression: parser.parse_section(path).unwrap()
---
Section {
file_identity: FileIdentity(
Guid {3EF70C2A-1787-854A-A0B4-EF192956C142},
),
display_name: "handwriting_recognition",
page_series: [
PageSeries {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
source: crates/parser/tests/lib.rs
assertion_line: 44
expression: parser.parse_section(path).unwrap()
---
Section {
file_identity: FileIdentity(
Guid {4979BEBE-EEAA-4BD1-AB0D-75EF7CF59533},
),
display_name: "Scribbles",
page_series: [
PageSeries {
Expand Down
Loading
Loading