mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 22:01:47 +00:00
Compare commits
22 Commits
claude/fix
...
claude/deb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8df7389405 | ||
|
|
5c11093747 | ||
|
|
6692829147 | ||
|
|
73c17d31dc | ||
|
|
73e1b51a2b | ||
|
|
1ee834f0b8 | ||
|
|
97ecd6b5cb | ||
|
|
09f63b6dd4 | ||
|
|
3f10758c2a | ||
|
|
d634544cd8 | ||
|
|
226d64a299 | ||
|
|
b5615aa01c | ||
|
|
a96e174326 | ||
|
|
1c2ce12389 | ||
|
|
34f350c849 | ||
|
|
b0500818ec | ||
|
|
c1d69c85d0 | ||
|
|
50a7d6335e | ||
|
|
08ddd0e35e | ||
|
|
6b098fcfd8 | ||
|
|
46732dc156 | ||
|
|
03b2e48600 |
@@ -144,6 +144,7 @@ src/bun.js/api/Timer/TimerObjectInternals.zig
|
||||
src/bun.js/api/Timer/WTFTimer.zig
|
||||
src/bun.js/api/TOMLObject.zig
|
||||
src/bun.js/api/UnsafeObject.zig
|
||||
src/bun.js/api/XMLObject.zig
|
||||
src/bun.js/api/YAMLObject.zig
|
||||
src/bun.js/bindgen_test.zig
|
||||
src/bun.js/bindings/AbortSignal.zig
|
||||
@@ -754,6 +755,7 @@ src/interchange.zig
|
||||
src/interchange/json.zig
|
||||
src/interchange/toml.zig
|
||||
src/interchange/toml/lexer.zig
|
||||
src/interchange/xml.zig
|
||||
src/interchange/yaml.zig
|
||||
src/io/heap.zig
|
||||
src/io/io.zig
|
||||
|
||||
14
packages/bun-types/bun.d.ts
vendored
14
packages/bun-types/bun.d.ts
vendored
@@ -646,6 +646,20 @@ declare module "bun" {
|
||||
export function parse(input: string): unknown;
|
||||
}
|
||||
|
||||
namespace XML {
|
||||
/**
|
||||
* Parse a XML string into a JavaScript value.
|
||||
*/
|
||||
export function parse(input: string): Element;
|
||||
|
||||
interface Element {
|
||||
__name: string;
|
||||
__attrs?: Record<string, string>;
|
||||
__text?: string;
|
||||
__children?: Element[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously resolve a `moduleId` as though it were imported from `parent`
|
||||
*
|
||||
|
||||
@@ -27,6 +27,7 @@ pub const Subprocess = @import("./api/bun/subprocess.zig");
|
||||
pub const HashObject = @import("./api/HashObject.zig");
|
||||
pub const UnsafeObject = @import("./api/UnsafeObject.zig");
|
||||
pub const TOMLObject = @import("./api/TOMLObject.zig");
|
||||
pub const XMLObject = @import("./api/XMLObject.zig");
|
||||
pub const YAMLObject = @import("./api/YAMLObject.zig");
|
||||
pub const Timer = @import("./api/Timer.zig");
|
||||
pub const FFIObject = @import("./api/FFIObject.zig");
|
||||
|
||||
@@ -62,6 +62,7 @@ pub const BunObject = struct {
|
||||
pub const SHA512 = toJSLazyPropertyCallback(Crypto.SHA512.getter);
|
||||
pub const SHA512_256 = toJSLazyPropertyCallback(Crypto.SHA512_256.getter);
|
||||
pub const TOML = toJSLazyPropertyCallback(Bun.getTOMLObject);
|
||||
pub const XML = toJSLazyPropertyCallback(Bun.getXMLObject);
|
||||
pub const YAML = toJSLazyPropertyCallback(Bun.getYAMLObject);
|
||||
pub const Transpiler = toJSLazyPropertyCallback(Bun.getTranspilerConstructor);
|
||||
pub const argv = toJSLazyPropertyCallback(Bun.getArgv);
|
||||
@@ -130,6 +131,7 @@ pub const BunObject = struct {
|
||||
@export(&BunObject.SHA512_256, .{ .name = lazyPropertyCallbackName("SHA512_256") });
|
||||
|
||||
@export(&BunObject.TOML, .{ .name = lazyPropertyCallbackName("TOML") });
|
||||
@export(&BunObject.XML, .{ .name = lazyPropertyCallbackName("XML") });
|
||||
@export(&BunObject.YAML, .{ .name = lazyPropertyCallbackName("YAML") });
|
||||
@export(&BunObject.Glob, .{ .name = lazyPropertyCallbackName("Glob") });
|
||||
@export(&BunObject.Transpiler, .{ .name = lazyPropertyCallbackName("Transpiler") });
|
||||
@@ -1302,6 +1304,10 @@ pub fn getTOMLObject(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSVa
|
||||
return TOMLObject.create(globalThis);
|
||||
}
|
||||
|
||||
pub fn getXMLObject(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
|
||||
return XMLObject.create(globalThis);
|
||||
}
|
||||
|
||||
pub fn getYAMLObject(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
|
||||
return YAMLObject.create(globalThis);
|
||||
}
|
||||
@@ -2093,6 +2099,7 @@ const FFIObject = bun.api.FFIObject;
|
||||
const HashObject = bun.api.HashObject;
|
||||
const TOMLObject = bun.api.TOMLObject;
|
||||
const UnsafeObject = bun.api.UnsafeObject;
|
||||
const XMLObject = bun.api.XMLObject;
|
||||
const YAMLObject = bun.api.YAMLObject;
|
||||
const node = bun.api.node;
|
||||
|
||||
|
||||
138
src/bun.js/api/XMLObject.zig
Normal file
138
src/bun.js/api/XMLObject.zig
Normal file
@@ -0,0 +1,138 @@
|
||||
pub fn create(globalThis: *jsc.JSGlobalObject) jsc.JSValue {
|
||||
const object = JSValue.createEmptyObject(globalThis, 1);
|
||||
object.put(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
jsc.createCallback(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
1,
|
||||
parse,
|
||||
),
|
||||
);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
global: *jsc.JSGlobalObject,
|
||||
callFrame: *jsc.CallFrame,
|
||||
) bun.JSError!jsc.JSValue {
|
||||
var arena: bun.ArenaAllocator = .init(bun.default_allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const args = callFrame.arguments();
|
||||
if (args.len < 1) {
|
||||
return global.throwInvalidArguments("XML.parse() requires 1 argument (xmlString)", .{});
|
||||
}
|
||||
|
||||
const input_value = args.ptr[0];
|
||||
if (!input_value.isString()) {
|
||||
return global.throwInvalidArguments("XML.parse() expects a string as first argument", .{});
|
||||
}
|
||||
|
||||
const input_str = try input_value.toBunString(global);
|
||||
const input = input_str.toSlice(arena.allocator());
|
||||
defer input.deinit();
|
||||
|
||||
var log = logger.Log.init(bun.default_allocator);
|
||||
defer log.deinit();
|
||||
|
||||
const source = &logger.Source.initPathString("input.xml", input.slice());
|
||||
|
||||
const root = bun.interchange.xml.XML.parse(source, &log, arena.allocator()) catch |err| return switch (err) {
|
||||
error.OutOfMemory => |oom| oom,
|
||||
error.StackOverflow => global.throwStackOverflow(),
|
||||
else => global.throwValue(try log.toJS(global, bun.default_allocator, "Failed to parse XML")),
|
||||
};
|
||||
|
||||
var ctx: ParserCtx = .{
|
||||
.stack_check = .init(),
|
||||
.global = global,
|
||||
.root = root,
|
||||
.result = .zero,
|
||||
};
|
||||
|
||||
MarkedArgumentBuffer.run(ParserCtx, &ctx, &ParserCtx.run);
|
||||
|
||||
return ctx.result;
|
||||
}
|
||||
|
||||
const ParserCtx = struct {
|
||||
stack_check: bun.StackCheck,
|
||||
global: *JSGlobalObject,
|
||||
root: Expr,
|
||||
result: JSValue,
|
||||
|
||||
pub fn run(ctx: *ParserCtx, args: *MarkedArgumentBuffer) callconv(.c) void {
|
||||
ctx.result = ctx.toJS(args, ctx.root) catch |err| switch (err) {
|
||||
error.OutOfMemory => {
|
||||
ctx.result = ctx.global.throwOutOfMemoryValue();
|
||||
return;
|
||||
},
|
||||
error.JSError => {
|
||||
ctx.result = .zero;
|
||||
return;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toJS(ctx: *ParserCtx, args: *MarkedArgumentBuffer, expr: Expr) JSError!JSValue {
|
||||
if (!ctx.stack_check.isSafeToRecurse()) {
|
||||
return ctx.global.throwStackOverflow();
|
||||
}
|
||||
|
||||
switch (expr.data) {
|
||||
.e_object => {
|
||||
var obj = JSValue.createEmptyObject(ctx.global, expr.data.e_object.properties.len);
|
||||
args.append(obj);
|
||||
|
||||
for (expr.data.e_object.properties.slice()) |prop| {
|
||||
const key_expr = prop.key.?;
|
||||
const value_expr = prop.value.?;
|
||||
|
||||
const key = try ctx.toJS(args, key_expr);
|
||||
const value = try ctx.toJS(args, value_expr);
|
||||
|
||||
const key_str = try key.toBunString(ctx.global);
|
||||
defer key_str.deref();
|
||||
|
||||
obj.putMayBeIndex(ctx.global, &key_str, value);
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
.e_string => {
|
||||
return ZigString.init(expr.data.e_string.data).withEncoding().toJS(ctx.global);
|
||||
},
|
||||
|
||||
.e_array => {
|
||||
var array = try JSValue.createEmptyArray(ctx.global, @intCast(expr.data.e_array.items.len));
|
||||
args.append(array);
|
||||
|
||||
for (expr.data.e_array.items.slice(), 0..) |item_expr, index| {
|
||||
const item_value = try ctx.toJS(args, item_expr);
|
||||
try array.putIndex(ctx.global, @intCast(index), item_value);
|
||||
}
|
||||
|
||||
return array;
|
||||
},
|
||||
|
||||
else => return ctx.global.throwError(error.TypeError, "XML.parse: unsupported AST node type"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const bun = @import("bun");
|
||||
const JSError = bun.JSError;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const logger = bun.logger;
|
||||
const Expr = bun.ast.Expr;
|
||||
const XML = bun.interchange.xml.XML;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSGlobalObject = jsc.JSGlobalObject;
|
||||
const JSValue = jsc.JSValue;
|
||||
const MarkedArgumentBuffer = jsc.MarkedArgumentBuffer;
|
||||
const ZigString = jsc.ZigString;
|
||||
@@ -18,6 +18,7 @@
|
||||
macro(SHA512) \
|
||||
macro(SHA512_256) \
|
||||
macro(TOML) \
|
||||
macro(XML) \
|
||||
macro(YAML) \
|
||||
macro(Transpiler) \
|
||||
macro(ValkeyClient) \
|
||||
|
||||
@@ -722,6 +722,7 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
SHA512 BunObject_lazyPropCb_wrap_SHA512 DontDelete|PropertyCallback
|
||||
SHA512_256 BunObject_lazyPropCb_wrap_SHA512_256 DontDelete|PropertyCallback
|
||||
TOML BunObject_lazyPropCb_wrap_TOML DontDelete|PropertyCallback
|
||||
XML BunObject_lazyPropCb_wrap_XML DontDelete|PropertyCallback
|
||||
YAML BunObject_lazyPropCb_wrap_YAML DontDelete|PropertyCallback
|
||||
Transpiler BunObject_lazyPropCb_wrap_Transpiler DontDelete|PropertyCallback
|
||||
embeddedFiles BunObject_lazyPropCb_wrap_embeddedFiles DontDelete|PropertyCallback
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub const json = @import("./interchange/json.zig");
|
||||
pub const toml = @import("./interchange/toml.zig");
|
||||
pub const xml = @import("./interchange/xml.zig");
|
||||
pub const yaml = @import("./interchange/yaml.zig");
|
||||
|
||||
1062
src/interchange/xml.zig
Normal file
1062
src/interchange/xml.zig
Normal file
File diff suppressed because it is too large
Load Diff
194
test/js/bun/xml/xml.test.ts
Normal file
194
test/js/bun/xml/xml.test.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("Bun.XML.parse - simple text element", () => {
|
||||
const xml = "<message>Hello World</message>";
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual("Hello World");
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - element with whitespace", () => {
|
||||
const xml = "<test> content </test>";
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual(" content ");
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - empty element", () => {
|
||||
const xml = "<empty></empty>";
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - element with attributes", () => {
|
||||
const xml = '<message id="1" type="info">Hello</message>';
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
id: "1",
|
||||
type: "info",
|
||||
__children: ["Hello"],
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - with XML declaration", () => {
|
||||
const xml = '<?xml version="1.0" encoding="UTF-8"?><root>content</root>';
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual("content");
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - empty string", () => {
|
||||
expect(() => Bun.XML.parse("")).toThrow({
|
||||
name: "Error",
|
||||
message: "TypeError XML.parse: unsupported AST node type",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - self-closing tag with attributes", () => {
|
||||
const xml = '<config debug="true" version="1.0"/>';
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
debug: "true",
|
||||
version: "1.0",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - self-closing tag without attributes", () => {
|
||||
const xml = "<br/>";
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - nested elements", () => {
|
||||
const xml = `<person>
|
||||
<name>John</name>
|
||||
<age>30</age>
|
||||
</person>`;
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
name: "John",
|
||||
age: "30",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - complex nested structure", () => {
|
||||
const xml = `<person name="John">
|
||||
<address type="home">
|
||||
<city>New York</city>
|
||||
</address>
|
||||
</person>`;
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
name: "John",
|
||||
address: {
|
||||
type: "home",
|
||||
city: "New York",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - mixed content (text and children)", () => {
|
||||
const xml = `<doc>
|
||||
Some text
|
||||
<child>value</child>
|
||||
More text
|
||||
</doc>`;
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
__children: ["value"],
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - XML entities", () => {
|
||||
const xml = "<message>Hello <world> & "everyone" 'here'</message>";
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual(`Hello <world> & "everyone" 'here'`);
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - numeric entities", () => {
|
||||
const xml = "<test>ABC</test>";
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual("ABC");
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - entities in attributes", () => {
|
||||
const xml = '<tag attr="<value>">content</tag>';
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
attr: "<value>",
|
||||
__children: ["content"],
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - XML comments are ignored", () => {
|
||||
const xml = `<root>
|
||||
<!-- This is a comment -->
|
||||
<message>Hello</message>
|
||||
<!-- Another comment -->
|
||||
</root>`;
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
message: "Hello",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - duplicate tags become arrays", () => {
|
||||
const xml = "<root><item>1</item><item>2</item></root>";
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual({
|
||||
item: ["1", "2"],
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - CDATA sections", () => {
|
||||
const xml = '<message><![CDATA[Hello <world> & "everyone"]]></message>';
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual(`Hello <world> & "everyone"`);
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - top-level comments are ignored", () => {
|
||||
const xml = `<!-- Top comment -->
|
||||
<root>content</root>
|
||||
<!-- Another top comment -->`;
|
||||
const result = Bun.XML.parse(xml);
|
||||
expect(result).toEqual("content");
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - mismatched closing tag throws error", () => {
|
||||
expect(() => Bun.XML.parse("<root><a></b></root>")).toThrow({
|
||||
name: "BuildMessage",
|
||||
message: "Mismatched closing tag",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - unclosed tag throws error", () => {
|
||||
expect(() => Bun.XML.parse("<root><a>")).toThrow({
|
||||
name: "BuildMessage",
|
||||
message: "Expected closing tag",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - unterminated XML declaration throws error", () => {
|
||||
expect(() => Bun.XML.parse("<?xml version='1.0'")).toThrow({
|
||||
name: "BuildMessage",
|
||||
message: "Unterminated XML declaration",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - unterminated CDATA throws error", () => {
|
||||
expect(() => Bun.XML.parse("<root><![CDATA[unclosed")).toThrow({
|
||||
name: "BuildMessage",
|
||||
message: "Unterminated CDATA section",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - no arguments throws TypeError", () => {
|
||||
expect(() => (Bun.XML.parse as any)()).toThrow({
|
||||
name: "TypeError",
|
||||
message: "XML.parse() requires 1 argument (xmlString)",
|
||||
});
|
||||
});
|
||||
|
||||
test("Bun.XML.parse - non-string argument throws TypeError", () => {
|
||||
expect(() => (Bun.XML.parse as any)(123)).toThrow({
|
||||
name: "TypeError",
|
||||
message: "XML.parse() expects a string as first argument",
|
||||
});
|
||||
});
|
||||
1808
test/js/bun/xml/xmltest.test.ts
Normal file
1808
test/js/bun/xml/xmltest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
1808
test/js/bun/xml/xmltest.test.ts.backup
Normal file
1808
test/js/bun/xml/xmltest.test.ts.backup
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user