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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ The release run heads these entries with the version and opens a fresh
- The zip backend is `miniz/3.1.1`, up from `3.0.2`. Rendered output is
unchanged.

- New `FileType::hypertext_markup_language` for `html`, `htm`, `xhtml`,
`text/html` and `application/xhtml+xml`. Classification only: no `open`, no
`translate_html`, and never detected from its bytes.

## v6.11.0 - 2026-08-29

- No view declares `<base target="_blank">` any more. A link back into what
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ opening one throws:
- wpd (WordPerfect)
- xlsb (Excel binary workbook — an OOXML package whose workbook parts are
binary rather than spreadsheetml)
- html / htm / xhtml (route to a web view; not detected from its bytes)

## Asking what is supported

Expand Down
2 changes: 2 additions & 0 deletions apple/include/OdrCoreObjC/ODRFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ typedef NS_ENUM(NSInteger, ODRFileType) {
ODRFileTypeIworkPages,
ODRFileTypeIworkNumbers,
ODRFileTypeIworkKeynote,

ODRFileTypeHypertextMarkupLanguage,
} NS_SWIFT_NAME(FileType);

typedef NS_ENUM(NSInteger, ODRFileCategory) {
Expand Down
2 changes: 2 additions & 0 deletions apple/src/ODRFile.mm
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
ODR_SAME_ENUM(ODRFileTypeIworkPages, odr::FileType::iwork_pages);
ODR_SAME_ENUM(ODRFileTypeIworkNumbers, odr::FileType::iwork_numbers);
ODR_SAME_ENUM(ODRFileTypeIworkKeynote, odr::FileType::iwork_keynote);
ODR_SAME_ENUM(ODRFileTypeHypertextMarkupLanguage,
odr::FileType::hypertext_markup_language);

ODR_SAME_ENUM(ODRFileCategoryUnknown, odr::FileCategory::unknown);
ODR_SAME_ENUM(ODRFileCategoryText, odr::FileCategory::text);
Expand Down
2 changes: 1 addition & 1 deletion jni/java/app/opendocument/core/FileType.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public enum FileType {
QUICKTIME_VIDEO, THIRD_GENERATION_PARTNERSHIP_VIDEO, MATROSKA_VIDEO,
AUDIO_VIDEO_INTERLEAVE, SCALABLE_VECTOR_GRAPHICS, WINDOWS_ICON, JPEG_XL,
JPEG_2000, PHOTOSHOP_DOCUMENT, WINDOWS_METAFILE, ENHANCED_METAFILE, XML,
IWORK_PAGES, IWORK_NUMBERS, IWORK_KEYNOTE;
IWORK_PAGES, IWORK_NUMBERS, IWORK_KEYNOTE, HYPERTEXT_MARKUP_LANGUAGE;

static FileType fromNative(int code) {
return code < 0 ? null : values()[code];
Expand Down
4 changes: 3 additions & 1 deletion python/src/bind_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ void odr_python::bind_file(py::module_ &m) {
.value("xml", odr::FileType::xml)
.value("iwork_pages", odr::FileType::iwork_pages)
.value("iwork_numbers", odr::FileType::iwork_numbers)
.value("iwork_keynote", odr::FileType::iwork_keynote);
.value("iwork_keynote", odr::FileType::iwork_keynote)
.value("hypertext_markup_language",
odr::FileType::hypertext_markup_language);

py::enum_<odr::FileCategory>(m, "FileCategory")
.value("unknown", odr::FileCategory::unknown)
Expand Down
4 changes: 4 additions & 0 deletions src/odr/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ enum class FileType {
// and no fixture pins those two, so nothing detects or decodes them yet.
iwork_numbers,
iwork_keynote,

// Classification only - never detected from its bytes.
// https://en.wikipedia.org/wiki/HTML
hypertext_markup_language,
};

/// @brief Collection of file categories.
Expand Down
13 changes: 13 additions & 0 deletions src/odr/internal/file_type_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ constexpr std::array keynote_mimetypes{
"application/x-iwork-keynote-sffkey"sv,
};

constexpr std::array html_extensions{"html"sv, "htm"sv, "xhtml"sv};
constexpr std::array html_mimetypes{"text/html"sv, "application/xhtml+xml"sv};

// The single source of truth behind every public format lookup; `odr_test`
// asserts one row per `FileType` and capabilities that match the engines.
//
Expand Down Expand Up @@ -798,6 +801,16 @@ constexpr std::array table{
.open = true,
.translate_html = true,
.color_scheme = true}},

// Named but not decoded. Not `detect_by_content` - html has no dependable
// signature, so the caller routes on the file name.
Row{FileType::hypertext_markup_language,
"html"sv,
html_extensions,
html_mimetypes,
FileCategory::text,
DocumentType::unknown,
{}},
};

/// Finds the row whose list, selected by @p list, contains @p needle.
Expand Down
25 changes: 24 additions & 1 deletion test/src/odr_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ namespace {
std::vector<FileType> every_file_type() {
std::vector<FileType> result;
for (auto i = static_cast<std::size_t>(FileType::unknown);
i <= static_cast<std::size_t>(FileType::iwork_keynote); ++i) {
i <= static_cast<std::size_t>(FileType::hypertext_markup_language);
++i) {
result.push_back(static_cast<FileType>(i));
}
return result;
Expand Down Expand Up @@ -129,6 +130,28 @@ TEST(FileTypeTable, canonical_alias_is_the_first_one) {
}
}

/// Without the row an `.html` decodes as xml and shows its own source.
TEST(FileTypeTable, html_is_named_but_not_decoded) {
const FileType html = FileType::hypertext_markup_language;

EXPECT_EQ(file_type_by_file_extension("html"), html);
EXPECT_EQ(file_type_by_file_extension("htm"), html);
EXPECT_EQ(file_type_by_file_extension("xhtml"), html);
EXPECT_EQ(file_type_by_mimetype("text/html"), html);
EXPECT_EQ(mimetype_by_file_type(html), "text/html");
EXPECT_EQ(file_category_by_file_type(html), FileCategory::text);

const FileTypeCapabilities capabilities = capabilities_by_file_type(html);
EXPECT_FALSE(capabilities.detect_by_content);
EXPECT_FALSE(capabilities.open);
EXPECT_FALSE(capabilities.translate_html);

const std::string page = "<!DOCTYPE html><html><body><p>hi</p></body></html>";
EXPECT_THROW(std::ignore =
open(File::from_memory(page), html, Logger::null()),
UnknownFileType);
}

/// `FileType::unknown` is the only type we refuse to name a MIME type for.
TEST(FileTypeTable, only_unknown_has_no_mimetype) {
for (const FileType type : every_file_type()) {
Expand Down
Loading