| ... | @@ -208,34 +208,51 @@ fn Field(comptime T: type, comptime field_name: string) type { | ... | @@ -208,34 +208,51 @@ fn Field(comptime T: type, comptime field_name: string) type { |
| 208 | } | 208 | } |
| 209 | | 209 | |
| 210 | pub fn writeEscaped(s: string, writer: anytype) !void { | 210 | pub fn writeEscaped(s: string, writer: anytype) !void { |
| 211 | for (s) |c| { | 211 | const view = std.unicode.Utf8View.initUnchecked(s); |
| 212 | if (entityLookupBefore(&[_]u8{c})) |ent| { | 212 | var iter = view.iterator(); |
| | 213 | while (nextCodepointSliceLossy(&iter)) |sl| { |
| | 214 | const cp = std.unicode.utf8Decode(sl) catch unreachable; |
| | 215 | if (isCodepointAnEntity(cp)) |ent| { |
| 213 | try writer.writeAll(ent.entity); | 216 | try writer.writeAll(ent.entity); |
| 214 | } else { | 217 | } else { |
| 215 | try writer.writeAll(&[_]u8{c}); | 218 | try writer.writeAll(sl); |
| 216 | } | 219 | } |
| 217 | } | 220 | } |
| 218 | } | 221 | } |
| 219 | | 222 | |
| 220 | fn entityLookupBefore(in: string) ?htmlentities.Entity { | 223 | fn nextCodepointSliceLossy(it: *std.unicode.Utf8Iterator) ?[]const u8 { |
| | 224 | if (it.i >= it.bytes.len) return null; |
| | 225 | const cp_len = std.unicode.utf8ByteSequenceLength(it.bytes[it.i]) catch { |
| | 226 | it.i += 1; |
| | 227 | return "�"; |
| | 228 | }; |
| | 229 | if (it.i + cp_len > it.bytes.len) return null; |
| | 230 | const maybe = it.bytes[it.i..][0..cp_len]; |
| | 231 | _ = std.unicode.utf8Decode(maybe) catch { |
| | 232 | it.i += 1; |
| | 233 | return "�"; |
| | 234 | }; |
| | 235 | it.i += cp_len; |
| | 236 | return maybe; |
| | 237 | } |
| | 238 | |
| | 239 | fn isCodepointAnEntity(cp: u21) ?htmlentities.Entity { |
| | 240 | switch (cp) { |
| | 241 | '\n', |
| | 242 | '.', |
| | 243 | ':', |
| | 244 | '(', |
| | 245 | ')', |
| | 246 | '%', |
| | 247 | '+', |
| | 248 | => return null, |
| | 249 | else => {}, |
| | 250 | } |
| 221 | for (htmlentities.ENTITIES) |e| { | 251 | for (htmlentities.ENTITIES) |e| { |
| 222 | if (!std.mem.endsWith(u8, e.entity, ";")) { | 252 | if (e.entity.len == 0) continue; |
| 223 | continue; | 253 | if (e.entity[e.entity.len - 1] != ';') continue; |
| 224 | } | 254 | |
| 225 | if (in.len == 1) { | 255 | if (e.codepoints == .Single and e.codepoints.Single == cp) { |
| 226 | switch (in[0]) { | | |
| 227 | '\n', | | |
| 228 | '.', | | |
| 229 | ':', | | |
| 230 | '(', | | |
| 231 | ')', | | |
| 232 | '%', | | |
| 233 | '+', | | |
| 234 | => return null, | | |
| 235 | else => {}, | | |
| 236 | } | | |
| 237 | } | | |
| 238 | if (std.mem.eql(u8, e.characters, in)) { | | |
| 239 | return e; | 256 | return e; |
| 240 | } | 257 | } |
| 241 | } | 258 | } |