Something went wrong. Try again.
Builds Windows Installer packages from WiX sources on Linux or Windows, in pure Zig; a clean-room reimplementation of msitools' wixl.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The code pages an installer package's strings can be stored in, and the//! encoding of text into them.//!//! zixl reads WiX sources as UTF-8 and stores their text in the code page//! the WiX source chooses: the database's for the tables, and the summary//! information's for that. The single-byte Windows code pages are the ones//! supported; 0, the neutral code page, holds ASCII alone, and 65001,//! UTF-8, is accepted though Windows Installer does not officially support//! it.
const std = @import("std");const Allocator = std.mem.Allocator;const charset = @import("zxml").charset;
pub const neutral = 0;pub const utf8 = 65001;
/// Whether `cp` is one zixl can store strings in.pub fn isSupported(cp: u16) bool { return cp == neutral or cp == utf8 or encoding(cp) != null;}
fn encoding(cp: u16) ?charset.Encoding { return switch (cp) { 874 => .windows_874, 1250 => .windows_1250, 1251 => .windows_1251, 1252 => .windows_1252, 1253 => .windows_1253, 1254 => .windows_1254, 1255 => .windows_1255, 1256 => .windows_1256, 1257 => .windows_1257, 1258 => .windows_1258, else => null, };}
pub const Error = error{ /// The text holds a character the code page cannot store. Unencodable,} || Allocator.Error;
/// `text`, UTF-8, as it is stored in code page `cp`. ASCII is the same in/// every one of them, and comes back as it is.pub fn encode(gpa: Allocator, cp: u16, text: []const u8) Error![]const u8 { for (text) |c| { if (c >= 0x80) break; } else return text; if (cp == utf8) return text; const e = encoding(cp) orelse return error.Unencodable; return charset.encodeAlloc(gpa, e, text, .{ .error_mode = .fatal }) catch |err| switch (err) { error.OutOfMemory => error.OutOfMemory, else => error.Unencodable, };}
/// `bytes`, stored in code page `cp`, as UTF-8. Reading accepts the/// double-byte East Asian code pages too, which zixl does not write. The/// neutral code page holds ASCII alone, but other tools store more in it:/// text that is valid UTF-8 is read as UTF-8, and anything else as Windows/// reads it on a machine whose ANSI code page is 1252. Bytes/// the code page cannot decode, and every non-ASCII byte of a code page zixl/// does not know, become U+FFFD; `known` says whether it knew it.pub fn decode(gpa: Allocator, cp: u16, bytes: []const u8, known: ?*bool) Allocator.Error![]const u8 { if (known) |k| k.* = true; for (bytes) |c| { if (c >= 0x80) break; } else return bytes; const e: charset.Encoding = if (cp == utf8) .utf8 else if (cp == neutral) (if (std.unicode.utf8ValidateSlice(bytes)) .utf8 else .windows_1252) else encoding(cp) orelse switch (cp) { 932 => .shift_jis, 936 => .gbk, 949 => .euc_kr, 950 => .big5, else => { if (known) |k| k.* = false; var out: std.ArrayList(u8) = .empty; for (bytes) |c| { if (c < 0x80) try out.append(gpa, c) else try out.appendSlice(gpa, "\u{FFFD}"); } return out.toOwnedSlice(gpa); }, }; return charset.decodeAlloc(gpa, e, bytes, .{ .error_mode = .replacement }) catch |err| switch (err) { error.OutOfMemory => error.OutOfMemory, // Replacement never fails otherwise. else => unreachable, };}
test "decoding" { const gpa = std.testing.allocator; const cafe = try decode(gpa, 1252, "Caf\xe9", null); defer gpa.free(cafe); try std.testing.expectEqualStrings("Café", cafe); try std.testing.expectEqualStrings("plain", try decode(gpa, 0, "plain", null)); const neutral_utf8 = try decode(gpa, neutral, "Café", null); defer gpa.free(neutral_utf8); try std.testing.expectEqualStrings("Café", neutral_utf8); const neutral_ansi = try decode(gpa, neutral, "Caf\xe9", null); defer gpa.free(neutral_ansi); try std.testing.expectEqualStrings("Café", neutral_ansi); var known = true; const unknown = try decode(gpa, 437, "a\x80", &known); defer gpa.free(unknown); try std.testing.expect(!known); try std.testing.expectEqualStrings("a\u{FFFD}", unknown); const bad = try decode(gpa, utf8, "a\xff", null); defer gpa.free(bad); try std.testing.expectEqualStrings("a\u{FFFD}", bad);}
test "ASCII is the same everywhere" { try std.testing.expectEqualStrings("plain", try encode(std.testing.allocator, neutral, "plain"));}
test "a single-byte code page" { const got = try encode(std.testing.allocator, 1252, "Café"); defer std.testing.allocator.free(got); try std.testing.expectEqualStrings("Caf\xe9", got); const cyrillic = try encode(std.testing.allocator, 1251, "Привет"); defer std.testing.allocator.free(cyrillic); try std.testing.expectEqualSlices(u8, &.{ 0xCF, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2 }, cyrillic);}
test "what a code page cannot hold" { try std.testing.expectError(error.Unencodable, encode(std.testing.allocator, 1252, "Ω")); try std.testing.expectError(error.Unencodable, encode(std.testing.allocator, neutral, "é"));}