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

## Unreleased

- Apple CSV decoding rejects multi-character and multibyte delimiters.
Roots without a text-root interface are exposed as `Element`, not `TextRoot`.

- Apple `Measure(string:)` is now failable. An invalid length returns `nil`
instead of letting a C++ exception escape into Swift.

Expand Down
4 changes: 2 additions & 2 deletions apple/include/OdrCoreObjC/ODRFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,9 @@ NS_SWIFT_NAME(CsvOptions)
@interface ODRCsvOptions : NSObject
/// `nil` to detect.
@property(nonatomic, strong, nullable) NSNumber *encoding;
/// A one-character string, `nil` to detect.
/// One ASCII character, `nil` or empty to detect. Other strings fail decoding.
@property(nonatomic, copy, nullable) NSString *separator;
/// A one-character string, `nil` to detect.
/// One ASCII character, `nil` or empty to detect. Other strings fail decoding.
@property(nonatomic, copy, nullable) NSString *quote;
@end

Expand Down
4 changes: 3 additions & 1 deletion apple/src/ODRDocumentElement.mm
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ + (nullable ODRElement *)elementWithHandle:(odr::Element)handle
Class klass = [ODRElement class];
switch (handle.type()) {
case odr::ElementType::root:
klass = [ODRTextRoot class];
if (handle.as_text_root()) {
klass = [ODRTextRoot class];
}
break;
case odr::ElementType::slide:
klass = [ODRSlide class];
Expand Down
22 changes: 15 additions & 7 deletions apple/src/ODRFile.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <optional>
#include <sstream>
#include <stdexcept>
#include <vector>

using odr::apple::guarded;
Expand Down Expand Up @@ -143,6 +144,18 @@
return result;
}

std::optional<char> csv_delimiter(NSString *const value) {
// an empty string means unset rather than a NUL
if (value.length == 0) {
return std::nullopt;
}
const std::string bytes = to_string(value);
if (bytes.size() != 1) {
throw std::invalid_argument("CSV delimiter must be one UTF-8 byte");
}
return bytes.front();
}

/// `nil` for an unset `std::optional`, the way a Java binding would use -1.
NSString *_Nullable to_nsstring(const std::optional<std::string> &value) {
return value.has_value() ? to_nsstring(*value) : nil;
Expand Down Expand Up @@ -370,13 +383,8 @@ + (nullable instancetype)decodePath:(NSString *)path
native.csv.encoding =
static_cast<odr::TextEncoding>(options.csv.encoding.integerValue);
}
// one character, and an empty string means unset rather than a NUL
if (options.csv.separator.length > 0) {
native.csv.separator = [options.csv.separator characterAtIndex:0];
}
if (options.csv.quote.length > 0) {
native.csv.quote = [options.csv.quote characterAtIndex:0];
}
native.csv.separator = csv_delimiter(options.csv.separator);
native.csv.quote = csv_delimiter(options.csv.quote);
return [ODRDecodedFile
decodedFileWithHandle:odr::open(to_string(path), native)];
});
Expand Down
24 changes: 23 additions & 1 deletion apple/tests/OdrCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,29 @@ final class DecodeTests: XCTestCase {

let csv = try decoded.asCsvFile()
XCTAssertEqual(try csv.textFile().text(), "a,b\n1,2\n")
XCTAssertNotNil(try csv.document().rootElement())
let root = try XCTUnwrap(try csv.document().rootElement())
XCTAssertFalse(root is TextRoot)
XCTAssertNotNil(root.firstDescendant(ofType: Sheet.self))
}

func testCsvDelimitersMustBeSingleBytes() throws {
let path = try write("a;b\nc;d\n", as: "table.csv")
let options = DecodeOptions()
options.csv.separator = ";"
let csv = try DecodedFile.decode(path: path, options: options).asCsvFile()
let root = try XCTUnwrap(try csv.document().rootElement())
let sheet = try XCTUnwrap(root.firstDescendant(ofType: Sheet.self))
XCTAssertEqual(sheet.dimensions.columns, 2)
options.csv.quote = ""
XCTAssertNoThrow(try DecodedFile.decode(path: path, options: options))
for invalid in [";,", "茅", "馃榾"] {
options.csv.separator = invalid
XCTAssertThrowsError(try DecodedFile.decode(path: path, options: options), invalid)
options.csv.separator = ";"
options.csv.quote = invalid
XCTAssertThrowsError(try DecodedFile.decode(path: path, options: options), invalid)
options.csv.quote = nil
}
}

/// `odr::Filesystem::exists("")` throws `std::invalid_argument`. Unguarded,
Expand Down
Loading