Skip to content
Merged
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- **Fix**: `Text::set_content` on an `.xlsx` run did nothing and said nothing.
An xlsx declares `edit`, so a caller had no way to learn the write was
dropped; it now throws `UnsupportedOperation`, as every other engine that
cannot write a run does.

- **Fix**: the pdf object parser skipped over `null`, `true` and `false`
without reading them, so `nXYZ` parsed as null. The keyword is checked now,
without case, as the surrounding parser already allows.

- Every binding reaches the structural edit API. python, jni and apple take
elements (`remove`, `insertTextBefore`/`After`, `appendText`,
`splitParagraph`, `mergeParagraphWithNext`, `insertParagraphAfter`); wasm
addresses the same operations by element id, because nothing escapes it as a
handle. `element_by_id` and `TextFile.write_edited` are bound too.

- **Breaking**: a csv and a markdown file hold a text file instead of being
one. `is_text_file()` answers false for them; the plain-text view is
`as_csv_file().text_file()` / `as_markdown_file().text_file()`. `CsvFile` and
`MarkdownFile` are bound in python, jni and apple to carry both views.

- A selection reaching over a picture is taken, and the picture goes with the
text: a frame carries `data-odr-id`, so an operation can name it. A frame
holding text of its own is still refused.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ to a system file picker. It is a declared upper bound; `DecodedFile::capabilitie
and `Document::is_editable` / `is_savable` give the precise answer for a
concrete file.

Editing and saving are currently limited to odt, odp, odg (`edit` + `save`),
ods (`save` only) and docx (`edit` + `save`); saving with a password is not
supported for any format.
Editing and saving are currently limited to odt, odp, ods, odg, docx, pptx,
xlsx and txt (`edit` + `save` each); saving with a password is not supported
for any format.

## Unsupported files

Expand Down
50 changes: 50 additions & 0 deletions apple/include/OdrCoreObjC/ODRDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,56 @@ NS_SWIFT_NAME(Document)
- (nullable ODRElement *)rootElementWithError:(NSError **)error
NS_SWIFT_NAME(rootElement());

/// The element `ODRElement.identifier` handed out, or `nil` where this
/// document holds no such id. Not an error, so it does not throw — an id that
/// is gone is the ordinary answer.
- (nullable ODRElement *)elementWithIdentifier:(uint64_t)identifier
NS_SWIFT_NAME(element(identifier:));

#pragma mark - Structural edits

/// Each fails where the engine cannot write, and for an element of another
/// document.

/// Removes an element and its subtree; its identifier stays taken.
- (BOOL)removeElement:(ODRElement *)element
error:(NSError **)error NS_SWIFT_NAME(remove(_:));

/// A run before `anchor`, in the same parent, so it takes the same style.
- (nullable ODRText *)insertTextBefore:(ODRText *)anchor
text:(NSString *)text
error:(NSError **)error
NS_SWIFT_NAME(insertText(before:text:));

/// A run after `anchor`, in the same parent.
- (nullable ODRText *)insertTextAfter:(ODRText *)anchor
text:(NSString *)text
error:(NSError **)error
NS_SWIFT_NAME(insertText(after:text:));

/// A run as the last child of `parent`.
- (nullable ODRText *)appendTextTo:(ODRElement *)parent
text:(NSString *)text
error:(NSError **)error
NS_SWIFT_NAME(appendText(to:text:));

/// Splits `paragraph` after `after` — one of its descendants — into a new
/// paragraph of the same style. A `nil` `after` moves every child.
- (nullable ODRParagraph *)splitParagraph:(ODRParagraph *)paragraph
after:(nullable ODRElement *)after
error:(NSError **)error
NS_SWIFT_NAME(splitParagraph(_:after:));

/// `paragraph` takes the children of the paragraph after it, which then goes.
- (BOOL)mergeParagraphWithNext:(ODRParagraph *)paragraph
error:(NSError **)error
NS_SWIFT_NAME(mergeParagraphWithNext(_:));

/// An empty paragraph after `paragraph`, of the same style.
- (nullable ODRParagraph *)insertParagraphAfter:(ODRParagraph *)paragraph
error:(NSError **)error
NS_SWIFT_NAME(insertParagraph(after:));

@end

NS_ASSUME_NONNULL_END
42 changes: 42 additions & 0 deletions apple/include/OdrCoreObjC/ODRFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ NS_ASSUME_NONNULL_BEGIN
@class ODRLogger;

@class ODRTextFile;
@class ODRCsvFile;
@class ODRMarkdownFile;
@class ODRImageFile;
@class ODRArchiveFile;
@class ODRDocumentFile;
Expand Down Expand Up @@ -336,6 +338,10 @@ NS_SWIFT_NAME(DecodedFile)
@property(nonatomic, readonly) ODRFileTypeCapabilities *capabilities;

@property(nonatomic, readonly) BOOL isTextFile;
/// A csv holds a text file rather than being one, so `isTextFile` is `NO`.
@property(nonatomic, readonly) BOOL isCsvFile;
/// Markdown holds a text file the same way a csv does.
@property(nonatomic, readonly) BOOL isMarkdownFile;
@property(nonatomic, readonly) BOOL isImageFile;
@property(nonatomic, readonly) BOOL isArchiveFile;
@property(nonatomic, readonly) BOOL isDocumentFile;
Expand All @@ -345,6 +351,10 @@ NS_SWIFT_NAME(DecodedFile)
/// The typed views. Each fails unless the matching `is…` is true.
- (nullable ODRTextFile *)asTextFileWithError:(NSError **)error
NS_SWIFT_NAME(asTextFile());
- (nullable ODRCsvFile *)asCsvFileWithError:(NSError **)error
NS_SWIFT_NAME(asCsvFile());
- (nullable ODRMarkdownFile *)asMarkdownFileWithError:(NSError **)error
NS_SWIFT_NAME(asMarkdownFile());
- (nullable ODRImageFile *)asImageFileWithError:(NSError **)error
NS_SWIFT_NAME(asImageFile());
- (nullable ODRArchiveFile *)asArchiveFileWithError:(NSError **)error
Expand All @@ -369,6 +379,38 @@ NS_SWIFT_NAME(TextFile)
@property(nonatomic, readonly, nullable, copy) NSString *charset;
/// The decoded text.
- (nullable NSString *)textWithError:(NSError **)error NS_SWIFT_NAME(text());
/// `NO` where the file type is one this library does not write, or the
/// encoding cannot be decoded.
@property(nonatomic, readonly) BOOL isSavable;
/// Applies the operations and returns the result, as UTF-8 whatever the source
/// encoding was.
- (nullable NSData *)writeEdited:(NSString *)operations
error:(NSError **)error
NS_SWIFT_NAME(writeEdited(operations:));
@end

/// A decoded csv — `odr::CsvFile`. It *holds* a text file rather than being
/// one; `document` and `textFile` are the two views of the same bytes.
NS_SWIFT_NAME(CsvFile)
@interface ODRCsvFile : ODRDecodedFile
/// The csv as a one-sheet spreadsheet.
- (nullable ODRDocument *)documentWithError:(NSError **)error
NS_SWIFT_NAME(document());
/// The same bytes as plain text, so reading them needs no reopening.
- (nullable ODRTextFile *)textFileWithError:(NSError **)error
NS_SWIFT_NAME(textFile());
@end

/// A decoded markdown file — `odr::MarkdownFile`. Holds a text file the way
/// `ODRCsvFile` does.
NS_SWIFT_NAME(MarkdownFile)
@interface ODRMarkdownFile : ODRDecodedFile
/// The markdown as a text document.
- (nullable ODRDocument *)documentWithError:(NSError **)error
NS_SWIFT_NAME(document());
/// The same bytes as plain text, so reading them needs no reopening.
- (nullable ODRTextFile *)textFileWithError:(NSError **)error
NS_SWIFT_NAME(textFile());
@end

/// A decoded image file — `odr::ImageFile`.
Expand Down
89 changes: 89 additions & 0 deletions apple/src/ODRDocument.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#import "ODRPrivate.h"

#include <odr/document.hpp>
#include <odr/document_element.hpp>

#include <optional>
#include <sstream>
Expand All @@ -13,6 +14,11 @@
using odr::apple::guarded_value;
using odr::apple::to_string;

@interface ODRDocument ()
- (nullable ODRText *)wrapText:(odr::Text)handle;
- (nullable ODRParagraph *)wrapParagraph:(odr::Paragraph)handle;
@end

@implementation ODRDocument {
std::optional<odr::Document> _handle;
}
Expand Down Expand Up @@ -97,6 +103,89 @@ - (nullable ODRElement *)rootElementWithError:(NSError **)error {
});
}

- (nullable ODRElement *)elementWithIdentifier:(uint64_t)identifier {
return guarded_value(
[&]() -> ODRElement * {
return [ODRElement elementWithHandle:_handle->element_by_id(identifier)
owner:self];
},
nil);
}

#pragma mark - Structural edits

- (BOOL)removeElement:(ODRElement *)element error:(NSError **)error {
return guarded(error, [&] {
_handle->remove(element.handle);
return YES;
});
}

/// The run the structural edits hand back, wrapped in the class the picker
/// gives it — `ODRText` for every one of them.
- (nullable ODRText *)wrapText:(odr::Text)handle {
return static_cast<ODRText *>([ODRElement elementWithHandle:std::move(handle)
owner:self]);
}

- (nullable ODRParagraph *)wrapParagraph:(odr::Paragraph)handle {
return static_cast<ODRParagraph *>(
[ODRElement elementWithHandle:std::move(handle) owner:self]);
}

- (nullable ODRText *)insertTextBefore:(ODRText *)anchor
text:(NSString *)text
error:(NSError **)error {
return guarded(error, [&]() -> ODRText * {
return [self wrapText:_handle->insert_text_before(anchor.handle.as_text(),
to_string(text))];
});
}

- (nullable ODRText *)insertTextAfter:(ODRText *)anchor
text:(NSString *)text
error:(NSError **)error {
return guarded(error, [&]() -> ODRText * {
return [self wrapText:_handle->insert_text_after(anchor.handle.as_text(),
to_string(text))];
});
}

- (nullable ODRText *)appendTextTo:(ODRElement *)parent
text:(NSString *)text
error:(NSError **)error {
return guarded(error, [&]() -> ODRText * {
return [self wrapText:_handle->append_text(parent.handle, to_string(text))];
});
}

- (nullable ODRParagraph *)splitParagraph:(ODRParagraph *)paragraph
after:(nullable ODRElement *)after
error:(NSError **)error {
return guarded(error, [&]() -> ODRParagraph * {
return
[self wrapParagraph:_handle->split_paragraph(
paragraph.handle.as_paragraph(),
after == nil ? odr::Element() : after.handle)];
});
}

- (BOOL)mergeParagraphWithNext:(ODRParagraph *)paragraph
error:(NSError **)error {
return guarded(error, [&] {
_handle->merge_paragraph_with_next(paragraph.handle.as_paragraph());
return YES;
});
}

- (nullable ODRParagraph *)insertParagraphAfter:(ODRParagraph *)paragraph
error:(NSError **)error {
return guarded(error, [&]() -> ODRParagraph * {
return [self wrapParagraph:_handle->insert_paragraph_after(
paragraph.handle.as_paragraph())];
});
}

- (nullable ODRFilesystem *)filesystemWithError:(NSError **)error {
return guarded(error, [&]() -> ODRFilesystem * {
return [ODRFilesystem filesystemWithHandle:_handle->as_filesystem()];
Expand Down
78 changes: 78 additions & 0 deletions apple/src/ODRFile.mm
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ + (instancetype)decodedFileWithHandle:(odr::DecodedFile)handle {
klass = [ODRPdfFile class];
} else if (handle.is_document_file()) {
klass = [ODRDocumentFile class];
} else if (handle.is_csv_file()) {
klass = [ODRCsvFile class];
} else if (handle.is_markdown_file()) {
klass = [ODRMarkdownFile class];
} else if (handle.is_text_file()) {
klass = [ODRTextFile class];
} else if (handle.is_image_file()) {
Expand Down Expand Up @@ -475,6 +479,15 @@ - (ODRFileTypeCapabilities *)capabilities {
- (BOOL)isTextFile {
return guarded_value([&] { return _handle->is_text_file() ? YES : NO; }, NO);
}

- (BOOL)isCsvFile {
return guarded_value([&] { return _handle->is_csv_file() ? YES : NO; }, NO);
}

- (BOOL)isMarkdownFile {
return guarded_value([&] { return _handle->is_markdown_file() ? YES : NO; },
NO);
}
- (BOOL)isImageFile {
return guarded_value([&] { return _handle->is_image_file() ? YES : NO; }, NO);
}
Expand Down Expand Up @@ -502,6 +515,20 @@ - (nullable ODRTextFile *)asTextFileWithError:(NSError **)error {
});
}

- (nullable ODRCsvFile *)asCsvFileWithError:(NSError **)error {
return guarded(error, [&]() -> ODRCsvFile * {
return static_cast<ODRCsvFile *>(
[ODRDecodedFile decodedFileWithHandle:_handle->as_csv_file()]);
});
}

- (nullable ODRMarkdownFile *)asMarkdownFileWithError:(NSError **)error {
return guarded(error, [&]() -> ODRMarkdownFile * {
return static_cast<ODRMarkdownFile *>(
[ODRDecodedFile decodedFileWithHandle:_handle->as_markdown_file()]);
});
}

- (nullable ODRImageFile *)asImageFileWithError:(NSError **)error {
return guarded(error, [&]() -> ODRImageFile * {
return static_cast<ODRImageFile *>(
Expand Down Expand Up @@ -571,6 +598,57 @@ - (nullable NSString *)textWithError:(NSError **)error {
});
}

- (BOOL)isSavable {
return guarded_value(
[&] { return self.handle.as_text_file().is_savable() ? YES : NO; }, NO);
}

- (nullable NSData *)writeEdited:(NSString *)operations
error:(NSError **)error {
return guarded(error, [&]() -> NSData * {
std::ostringstream out;
self.handle.as_text_file().write_edited(to_string(operations), out);
const std::string bytes = out.str();
return [NSData dataWithBytes:bytes.data() length:bytes.size()];
});
}

@end

@implementation ODRCsvFile

- (nullable ODRDocument *)documentWithError:(NSError **)error {
return guarded(error, [&]() -> ODRDocument * {
return
[ODRDocument documentWithHandle:self.handle.as_csv_file().document()];
});
}

- (nullable ODRTextFile *)textFileWithError:(NSError **)error {
return guarded(error, [&]() -> ODRTextFile * {
return static_cast<ODRTextFile *>([ODRDecodedFile
decodedFileWithHandle:self.handle.as_csv_file().text_file()]);
});
}

@end

@implementation ODRMarkdownFile

- (nullable ODRDocument *)documentWithError:(NSError **)error {
return guarded(error, [&]() -> ODRDocument * {
return [ODRDocument
documentWithHandle:self.handle.as_markdown_file().document()];
});
}

- (nullable ODRTextFile *)textFileWithError:(NSError **)error {
return guarded(error, [&]() -> ODRTextFile * {
return static_cast<ODRTextFile *>([ODRDecodedFile
decodedFileWithHandle:self.handle.as_markdown_file().text_file()]);
});
}

@end

@implementation ODRImageFile
Expand Down
Loading
Loading