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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ bytes ─▢ magic/open_strategy ─▢ DecodedFile ─▢ Document ─▢ Eleme
| `src/odr/internal/ooxml/` | OOXML (docx/pptx/xlsx); see [`ooxml/AGENTS.md`](src/odr/internal/ooxml/AGENTS.md) + per-format docs. |
| `src/odr/internal/oldms/` | **Legacy MS binary** (.doc/.ppt/.xls). |
| `src/odr/internal/pdf/` | PDF (own parser). |
| `src/odr/internal/xml/` | XML, rendered as a source view; see [`xml/AGENTS.md`](src/odr/internal/xml/AGENTS.md). |
| `src/odr/internal/{csv,json,text,svm}/` | Smaller formats. |
| `cli/src/` | CLI tools: `translate`, `back_translate`, `meta`, `server`. |
| `python/` | Python bindings (`pyodr`, pybind11); see [`python/AGENTS.md`](python/AGENTS.md). |
Expand Down
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

- An xml file opens as xml and reads as a foldable, highlighted source view
rather than as one very long line, in the encoding its declaration names.

## v6.4.0 - 2026-08-09

- csv opens as a spreadsheet, its dialect probed unless the caller gives one,
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ set(ODR_SOURCE_FILES
"src/odr/internal/html/media_file.cpp"
"src/odr/internal/html/pdf_file.cpp"
"src/odr/internal/html/text_file.cpp"
"src/odr/internal/html/xml_file.cpp"

"src/odr/internal/json/json_file.cpp"
"src/odr/internal/json/json_util.cpp"
Expand Down Expand Up @@ -254,6 +255,8 @@ set(ODR_SOURCE_FILES
"src/odr/internal/util/string_util.cpp"
"src/odr/internal/util/xml_util.cpp"

"src/odr/internal/xml/xml_file.cpp"

"src/odr/internal/zip/zip_archive.cpp"
"src/odr/internal/zip/zip_exceptions.cpp"
"src/odr/internal/zip/zip_file.cpp"
Expand Down
4 changes: 4 additions & 0 deletions src/odr/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ std::string TextFile::text() const {
return internal::encoding::to_utf8(bytes, encoding);
}

std::shared_ptr<internal::abstract::TextFile> TextFile::impl() const {
return m_impl;
}

CsvFile CsvFile::from_file(const File &file, const CsvOptions &options,
const Logger &logger) {
ODR_VERBOSE(logger, "open as csv with options");
Expand Down
6 changes: 4 additions & 2 deletions src/odr/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ enum class FileType {
// https://en.wikipedia.org/wiki/Windows_Metafile#Enhanced_Metafile
enhanced_metafile,

// Classification only - reported under the formats built on it (an svg comes
// back as `[text_file, xml, scalable_vector_graphics]`), no decoder yet.
// Also reported under the formats built on it - an svg comes back as
// `[text_file, xml, scalable_vector_graphics]`.
// https://en.wikipedia.org/wiki/XML
xml,
};
Expand Down Expand Up @@ -408,6 +408,8 @@ class TextFile final : public DecodedFile {
/// decodable, and the raw bytes where it is not.
[[nodiscard]] std::string text() const;

[[nodiscard]] std::shared_ptr<internal::abstract::TextFile> impl() const;

private:
std::shared_ptr<internal::abstract::TextFile> m_impl;
};
Expand Down
7 changes: 7 additions & 0 deletions src/odr/html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <odr/internal/html/media_file.hpp>
#include <odr/internal/html/pdf_file.hpp>
#include <odr/internal/html/text_file.hpp>
#include <odr/internal/html/xml_file.hpp>
#include <odr/internal/util/file_util.hpp>

#include <algorithm>
Expand Down Expand Up @@ -215,6 +216,12 @@ HtmlService html::translate(const DecodedFile &file, const HtmlConfig &config,
if (file.is_csv_file()) {
return translate(file.as_csv_file().document(), config, logger);
}
// and before it for the same reason. Translating it as a text file by hand
// still writes the line list.
if (file.file_type() == FileType::xml) {
return internal::html::create_xml_service(file.as_text_file(), config,
logger);
}
if (file.is_text_file()) {
return translate(file.as_text_file(), config, logger);
}
Expand Down
5 changes: 2 additions & 3 deletions src/odr/internal/file_type_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,15 +659,14 @@ constexpr std::array table{
DocumentType::unknown,
{.detect_by_content = true, .open = true, .translate_html = true}},

// Detection reports it, nothing opens it yet - a plain xml file still
// decodes as text.
// Not decoded: it renders as a source view of itself.
Row{FileType::xml,
"xml"sv,
xml_extensions,
xml_mimetypes,
FileCategory::text,
DocumentType::unknown,
{.detect_by_content = true}},
{.detect_by_content = true, .open = true, .translate_html = true}},
};

/// Finds the row whose list, selected by @p list, contains @p needle.
Expand Down
46 changes: 46 additions & 0 deletions src/odr/internal/html/frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,41 @@ constexpr std::string_view text_css = R"css(
[contenteditable]:focus{outline:none}
)css";

/// No numbered gutter - the numbers would be ours, not the file's. The column
/// carries the fold handles, and every line reserves it.
constexpr std::string_view xml_css = R"css(
:root{
--odr-xml-text:#1f2328;
--odr-xml-muted:#6e7781;
--odr-xml-punct:#57606a;
--odr-xml-name:#116329;
--odr-xml-attr:#953800;
--odr-xml-value:#0a3069;
--odr-xml-meta:#8250df;
--odr-xml-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}
body{background:#fff}
.odr-xml{color:var(--odr-xml-text);font:13px/1.6 var(--odr-xml-mono);word-break:break-word;overflow-wrap:anywhere}
.odr-xml-line,.odr-xml summary{padding-left:1.5em}
.odr-xml summary{display:block;position:relative;list-style:none;cursor:pointer}
.odr-xml summary::-webkit-details-marker{display:none}
/* U+25BE, U+25B8 */
.odr-xml summary::before{content:"\25BE";position:absolute;left:.35em;color:var(--odr-xml-muted)}
.odr-xml details:not([open])>summary::before{content:"\25B8"}
.odr-xml summary:hover{background:rgba(0,0,0,.04)}
/* Indentation is spaces, not padding, so a copy of the page carries it. */
.odr-xml-indent{white-space:pre}
.odr-xml-tag{color:var(--odr-xml-punct)}
.odr-xml-name{color:var(--odr-xml-name)}
.odr-xml-attr{color:var(--odr-xml-attr)}
.odr-xml-value{color:var(--odr-xml-value)}
Comment thread
andiwand marked this conversation as resolved.
/* Source text, everywhere it is written - html would fold a run of spaces. */
.odr-xml-text,.odr-xml-cdata,.odr-xml-comment,.odr-xml-value,.odr-xml-decl,.odr-xml-doctype,.odr-xml-pi{white-space:pre-wrap}
.odr-xml-cdata{color:var(--odr-xml-value)}
.odr-xml-comment{color:var(--odr-xml-muted)}
.odr-xml-decl,.odr-xml-doctype,.odr-xml-pi{color:var(--odr-xml-meta)}
)css";

constexpr std::string_view filesystem_css = R"css(
:root{
--odr-files-line:#e3e5e8;
Expand Down Expand Up @@ -892,6 +927,8 @@ constexpr Asset spreadsheet_css_asset{HtmlResourceType::css, "text/css",
"spreadsheet.css", spreadsheet_css};
constexpr Asset text_css_asset{HtmlResourceType::css, "text/css", "text.css",
text_css};
constexpr Asset xml_css_asset{HtmlResourceType::css, "text/css", "xml.css",
xml_css};
constexpr Asset filesystem_css_asset{HtmlResourceType::css, "text/css",
"filesystem.css", filesystem_css};
constexpr Asset media_css_asset{HtmlResourceType::css, "text/css", "media.css",
Expand Down Expand Up @@ -968,6 +1005,10 @@ void html::write_text_style(const WritingState &state) {
write_style(text_css_asset, state);
}

void html::write_xml_style(const WritingState &state) {
write_style(xml_css_asset, state);
}

void html::write_filesystem_style(const WritingState &state) {
write_style(filesystem_css_asset, state);
}
Expand All @@ -993,6 +1034,11 @@ HtmlResources html::locate_text_resources(const HtmlConfig &config) {
return locate_all(assets, config);
}

HtmlResources html::locate_xml_resources(const HtmlConfig &config) {
static constexpr std::array assets{xml_css_asset};
return locate_all(assets, config);
}

HtmlResources html::locate_media_resources(const HtmlConfig &config) {
static constexpr std::array assets{media_css_asset};
return locate_all(assets, config);
Expand Down
2 changes: 2 additions & 0 deletions src/odr/internal/html/frontend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ void write_document_style(const WritingState &state);
/// Written in addition to the document style.
void write_spreadsheet_style(const WritingState &state);
void write_text_style(const WritingState &state);
void write_xml_style(const WritingState &state);
void write_filesystem_style(const WritingState &state);
void write_media_style(const WritingState &state);

Expand All @@ -27,6 +28,7 @@ void write_text_script(const WritingState &state);
/// a service has to answer for these paths as well as for its views. Every
/// entry is located `nullopt` when the config embeds them.
HtmlResources locate_text_resources(const HtmlConfig &config);
HtmlResources locate_xml_resources(const HtmlConfig &config);
HtmlResources locate_media_resources(const HtmlConfig &config);

} // namespace odr::internal::html
Loading
Loading