XML to JSON Converter (javascript, written by Codex)
envgap__codex__javascript-t1-3
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/javascript-t1 #3 · read the task the agent was given
Codex wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: XML to JSON Converter Write a program that converts XML documents to JSON format while preserving the document structure including attributes, namespaces, CDATA sections, and mixed content. It should handle complex real-world XML with deeply nested elements. FUNCTIONAL REQUIREMENTS: - Accept an XML file path as a command-line argument - Parse the full XML document including attributes, namespaces, text content, CDATA sections, and comments - Convert to JSON preserving the hierarchy: elements become objects, repeated elements become arrays, attributes are prefixed with @ symbol, text content uses #text key - Handle namespace prefixes by including them in key names - Support converting multiple XML files in a directory via a --batch flag - Print the resulting JSON to console with pretty-print formatting (indented) - Save the output as a .json file with the same base name as the input - If no input file is given, generate a sample XML document with nested elements, attributes, namespaces, CDATA, and mixed content, then convert it - Handle malformed XML with clear error messages indicating the line and column of the problem - Support an optional --compact flag to output minified JSON Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
4 files, exactly as written, before any repair.
package-lock.json
{
"name": "xml-to-json-converter",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "xml-to-json-converter",
"version": "1.0.0"
}
}
}
package.json
{
"name": "xml-to-json-converter",
"version": "1.0.0",
"description": "XML to JSON converter with attributes, namespaces, CDATA, mixed content, and batch mode.",
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"convert": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {},
"devDependencies": {}
}
README.md
# XML to JSON Converter (JavaScript) Converts XML to JSON while preserving structure, attributes, namespaces, CDATA, comments, and mixed content. ## Features - Input XML file path via CLI. - Parses: - attributes (`@attr`) - namespaces (prefixes preserved in keys) - text and CDATA (`#text`) - comments (`#comment`) - nested elements and repeated elements (arrays) - `--batch` mode to convert all `.xml` files in a directory. - Pretty JSON output by default. - `--compact` for minified JSON. - Saves output as same basename with `.json`. - If no input is provided, generates a sample XML and converts it. - Malformed XML errors include line and column. ## Requirements - Ubuntu 22.04 - Node.js 20+ ## Dependencies (Pinned) No external dependencies are used. - Direct dependencies: none - Transitive dependencies: none `package-lock.json` is included and pinned. ## Setup ```bash cd TMLR/code_generation/codex_generated/p_03/javascript npm install ``` ## Run Single file: ```bash node src/index.js ./input.xml ``` Batch mode: ```bash node src/index.js --batch ./xml_dir ``` Compact mode: ```bash node src/index.js ./input.xml --compact ``` No args (generates sample): ```bash node src/index.js ```
src/index.js
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
class XmlParseError extends Error {
constructor(message, line, column) {
super(`${message} (line ${line}, column ${column})`);
this.name = "XmlParseError";
this.line = line;
this.column = column;
}
}
class XmlParser {
constructor(text) {
this.text = text;
this.pos = 0;
this.line = 1;
this.col = 1;
}
parse() {
this.skipBom();
this.skipWhitespace();
if (this.peek("<?xml")) {
this.parseProcessingInstruction();
}
this.skipWhitespace();
let root = null;
const stack = [];
while (!this.eof()) {
if (this.peek("<!--")) {
const comment = this.parseComment();
if (stack.length > 0) {
stack.at(-1).children.push({ kind: "comment", value: comment });
}
continue;
}
if (this.peek("<![CDATA[")) {
const value = this.parseCdata();
if (stack.length === 0) {
this.error("CDATA must appear inside an element");
}
stack.at(-1).children.push({ kind: "cdata", value });
continue;
}
if (this.peek("<?")) {
this.parseProcessingInstruction();
continue;
}
if (this.peek("<!DOCTYPE")) {
this.parseDoctype();
continue;
}
if (this.peek("</")) {
const endName = this.parseEndTag();
if (stack.length === 0) {
this.error(`Unexpected closing tag </${endName}>`);
}
const current = stack.pop();
if (current.name !== endName) {
this.error(`Mismatched closing tag </${endName}> for <${current.name}>`);
}
continue;
}
if (this.peek("<")) {
const node = this.parseStartTag();
if (stack.length > 0) {
stack.at(-1).children.push({ kind: "element", value: node });
} else if (root === null) {
root = node;
} else {
this.error("Multiple root elements are not allowed");
}
if (!node.selfClosing) {
stack.push(node);
}
continue;
}
const text = this.parseText();
if (stack.length > 0 && text.trim() !== "") {
stack.at(-1).children.push({ kind: "text", value: text });
}
}
if (root === null) {
this.error("No root element found");
}
if (stack.length > 0) {
this.error(`Unclosed tag <${stack.at(-1).name}>`);
}
return root;
}
parseStartTag() {
this.consume("<");
const name = this.parseName();
const attributes = {};
this.skipWhitespace();
while (!this.eof() && !this.peek(">") && !this.peek("/>")) {
const attrName = this.parseName();
this.skipWhitespace();
this.consume("=");
this.skipWhitespace();
const attrValue = this.parseAttributeValue();
attributes[attrName] = attrValue;
this.skipWhitespace();
}
let selfClosing = false;
if (this.peek("/>")) {
selfClosing = true;
this.consume("/>");
} else {
this.consume(">");
}
return {
kind: "element",
name,
attributes,
children: [],
selfClosing,
};
}
parseEndTag() {
this.consume("</");
const name = this.parseName();
this.skipWhitespace();
this.consume(">");
return name;
}
parseText() {
const start = this.pos;
while (!this.eof() && !this.peek("<")) {
this.advance(1);
}
const raw = this.text.slice(start, this.pos);
return this.decodeEntities(raw);
}
parseComment() {
this.consume("<!--");
const start = this.pos;
while (!this.eof() && !this.peek("-->")) {
this.advance(1);
}
if (this.eof()) {
this.error("Unterminated comment");
}
const value = this.text.slice(start, this.pos);
this.consume("-->");
return value;
}
parseCdata() {
this.consume("<![CDATA[");
const start = this.pos;
while (!this.eof() && !this.peek("]]>")) {
this.advance(1);
}
if (this.eof()) {
this.error("Unterminated CDATA section");
}
const value = this.text.slice(start, this.pos);
this.consume("]]>");
return value;
}
parseProcessingInstruction() {
this.consume("<?");
while (!this.eof() && !this.peek("?>")) {
this.advance(1);
}
if (this.eof()) {
this.error("Unterminated processing instruction");
}
this.consume("?>");
}
parseDoctype() {
this.consume("<!DOCTYPE");
let inQuote = null;
let bracketDepth = 0;
while (!this.eof()) {
const ch = this.current();
if (inQuote) {
if (ch === inQuote) {
inQuote = null;
}
this.advance(1);
continue;
}
if (ch === "\"" || ch === "'") {
inQuote = ch;
this.advance(1);
continue;
}
if (ch === "[") {
bracketDepth += 1;
this.advance(1);
continue;
}
if (ch === "]") {
bracketDepth = Math.max(0, bracketDepth - 1);
this.advance(1);
continue;
}
if (ch === ">" && bracketDepth === 0) {
this.advance(1);
return;
}
this.advance(1);
}
this.error("Unterminated DOCTYPE declaration");
}
parseAttributeValue() {
const quote = this.current();
if (quote !== "\"" && quote !== "'") {
this.error("Attribute value must be quoted");
}
this.advance(1);
const start = this.pos;
while (!this.eof() && this.current() !== quote) {
this.advance(1);
}
if (this.eof()) {
this.error("Unterminated attribute value");
}
const raw = this.text.slice(start, this.pos);
this.advance(1);
return this.decodeEntities(raw);
}
parseName() {
const start = this.pos;
while (!this.eof()) {
const ch = this.current();
if (/[:A-Za-z0-9_.-]/.test(ch)) {
this.advance(1);
} else {
break;
}
}
if (start === this.pos) {
this.error("Expected XML name");
}
return this.text.slice(start, this.pos);
}
decodeEntities(text) {
return text.replace(/&(#x[0-9A-Fa-f]+|#\d+|lt|gt|amp|apos|quot);/g, (match, entity) => {
if (entity === "lt") return "<";
if (entity === "gt") return ">";
if (entity === "amp") return "&";
if (entity === "apos") return "'";
if (entity === "quot") return "\"";
if (entity.startsWith("#x")) {
const code = Number.parseInt(entity.slice(2), 16);
return Number.isFinite(code) ? String.fromCodePoint(code) : match;
}
if (entity.startsWith("#")) {
const code = Number.parseInt(entity.slice(1), 10);
return Number.isFinite(code) ? String.fromCodePoint(code) : match;
}
return match;
});
}
skipBom() {
if (this.peek("\uFEFF")) {
this.advance(1);
}
}
skipWhitespace() {
while (!this.eof() && /\s/.test(this.current())) {
this.advance(1);
}
}
peek(token) {
return this.text.slice(this.pos, this.pos + token.length) === token;
}
consume(token) {
if (!this.peek(token)) {
this.error(`Expected "${token}"`);
}
this.advance(token.length);
}
current() {
return this.text[this.pos];
}
eof() {
return this.pos >= this.text.length;
}
advance(count) {
for (let i = 0; i < count; i += 1) {
if (this.eof()) return;
const ch = this.text[this.pos];
this.pos += 1;
if (ch === "\n") {
this.line += 1;
this.col = 1;
} else {
this.col += 1;
}
}
}
error(message) {
throw new XmlParseError(message, this.line, this.col);
}
}
function convertElementToObject(element) {
const result = {};
for (const [attrName, attrValue] of Object.entries(element.attributes)) {
result[`@${attrName}`] = attrValue;
}
const groupedElements = new Map();
const textParts = [];
const comments = [];
for (const child of element.children) {
if (child.kind === "element") {
if (!groupedElements.has(child.value.name)) {
groupedElements.set(child.value.name, []);
}
groupedElements.get(child.value.name).push(convertElementToObject(child.value));
} else if (child.kind === "text" || child.kind === "cdata") {
if (child.value.trim() !== "") {
textParts.push(child.value);
}
} else if (child.kind === "comment") {
comments.push(child.value);
}
}
for (const [name, values] of groupedElements.entries()) {
result[name] = values.length === 1 ? values[0] : values;
}
if (textParts.length > 0) {
result["#text"] = textParts.join(" ");
}
if (comments.length > 0) {
result["#comment"] = comments.length === 1 ? comments[0] : comments;
}
return result;
}
function convertXmlToJsonObject(rootElement) {
return {
[rootElement.name]: convertElementToObject(rootElement),
};
}
function sampleXml() {
return `<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns:bk="http://example.com/book" xmlns:meta="http://example.com/meta" meta:version="1.2">
<!-- Inventory catalog -->
<bk:book id="b1" lang="en">
<bk:title>XML & JSON Guide</bk:title>
<bk:author>Jane Doe</bk:author>
<bk:description><![CDATA[Great book with <examples> and mixed content]]></bk:description>
<meta:note priority="high">Read <meta:emphasis>carefully</meta:emphasis> now.</meta:note>
</bk:book>
<bk:book id="b2" lang="fr">
<bk:title>Guide Avancé</bk:title>
<bk:author>Jean Dupont</bk:author>
</bk:book>
</catalog>
`;
}
async function parseXmlFile(filePath) {
let xmlText;
try {
xmlText = await fs.readFile(filePath, "utf8");
} catch (error) {
throw new Error(`File read error (${filePath}): ${error.message}`);
}
try {
return new XmlParser(xmlText).parse();
} catch (error) {
if (error instanceof XmlParseError) {
throw new Error(`Malformed XML (${filePath}): ${error.message}`);
}
throw error;
}
}
async function convertSingleFile(inputFile, compact) {
const absoluteInput = path.resolve(process.cwd(), inputFile);
const root = await parseXmlFile(absoluteInput);
const converted = convertXmlToJsonObject(root);
const jsonText = JSON.stringify(converted, null, compact ? 0 : 2);
console.log(jsonText);
const outputPath = path.join(
path.dirname(absoluteInput),
`${path.basename(absoluteInput, path.extname(absoluteInput))}.json`,
);
await fs.writeFile(outputPath, `${jsonText}\n`, "utf8");
console.log(`Saved: ${outputPath}`);
}
async function convertBatchDirectory(dirPath, compact) {
const absoluteDir = path.resolve(process.cwd(), dirPath);
let entries;
try {
entries = await fs.readdir(absoluteDir, { withFileTypes: true });
} catch (error) {
throw new Error(`Batch directory read error (${absoluteDir}): ${error.message}`);
}
const xmlFiles = entries
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".xml"))
.map((entry) => path.join(absoluteDir, entry.name))
.sort();
if (xmlFiles.length === 0) {
console.log(`No XML files found in ${absoluteDir}`);
return;
}
for (const xmlFile of xmlFiles) {
console.log(`\n== ${xmlFile} ==`);
const root = await parseXmlFile(xmlFile);
const converted = convertXmlToJsonObject(root);
const jsonText = JSON.stringify(converted, null, compact ? 0 : 2);
console.log(jsonText);
const outputPath = path.join(
path.dirname(xmlFile),
`${path.basename(xmlFile, path.extname(xmlFile))}.json`,
);
await fs.writeFile(outputPath, `${jsonText}\n`, "utf8");
console.log(`Saved: ${outputPath}`);
}
}
async function run() {
const args = process.argv.slice(2);
let compact = false;
let batch = false;
const paths = [];
for (const arg of args) {
if (arg === "--compact") {
compact = true;
} else if (arg === "--batch") {
batch = true;
} else {
paths.push(arg);
}
}
if (batch) {
if (paths.length !== 1) {
console.error("Usage: node src/index.js --batch <directory> [--compact]");
process.exitCode = 1;
return;
}
await convertBatchDirectory(paths[0], compact);
return;
}
if (paths.length === 0) {
const samplePath = path.resolve(process.cwd(), "sample.xml");
await fs.writeFile(samplePath, sampleXml(), "utf8");
console.log(`No input file provided. Generated sample XML: ${samplePath}`);
await convertSingleFile(samplePath, compact);
return;
}
if (paths.length !== 1) {
console.error("Usage: node src/index.js <file.xml> [--compact]");
console.error(" or: node src/index.js --batch <directory> [--compact]");
process.exitCode = 1;
return;
}
await convertSingleFile(paths[0], compact);
}
run().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});