← All tasks
javascriptcodex/javascript-t1 #3Not a task: already works

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 &amp; 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;
});