{
  "name": "ezGPT - XML Orders to JSON",
  "active": false,
  "nodes": [
    {
      "id": "trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {}
    },
    {
      "id": "sample",
      "name": "Sample XML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<orders batch_id=\"B1\">\n  <order id=\"001\"><client>Анна &amp; Ко</client><amount_minor>150000</amount_minor><paid>false</paid></order>\n  <order id=\"002\"><client>Борис</client><amount_minor>0</amount_minor><paid>true</paid></order>\n  <order id=\"003\"><client>Альфа</client><amount_minor>49900</amount_minor><paid>false</paid></order>\n</orders>`;\nreturn [{ json: { xml } }];"
      }
    },
    {
      "id": "validate",
      "name": "Check XML Text",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nif (items.length !== 1) throw new Error('one_xml_item_required');\nconst xml = items[0].json.xml;\nif (typeof xml !== 'string' || !xml.trim()) throw new Error('xml_text_required');\nif (Buffer.byteLength(xml, 'utf8') > 16384) throw new Error('xml_too_large');\nif (xml.includes('<!DOCTYPE') || xml.includes('<!ENTITY')) {\n  throw new Error('xml_declarations_not_supported');\n}\nreturn [{ json: { xml } }];"
      }
    },
    {
      "id": "xml",
      "name": "XML to JSON",
      "type": "n8n-nodes-base.xml",
      "typeVersion": 1,
      "position": [
        900,
        300
      ],
      "parameters": {
        "mode": "xmlToJson",
        "dataPropertyName": "xml",
        "options": {
          "attrkey": "$",
          "charkey": "_",
          "explicitArray": true,
          "explicitRoot": true,
          "ignoreAttrs": false,
          "mergeAttrs": false,
          "normalize": false,
          "normalizeTags": false,
          "trim": true
        }
      }
    },
    {
      "id": "check",
      "name": "Check Orders",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nif (items.length !== 1) throw new Error('one_parsed_item_required');\nconst keys = value => Object.keys(value).sort().join('|');\nconst data = items[0].json;\nif (keys(data) !== 'orders' || !data.orders || Array.isArray(data.orders) ||\n    typeof data.orders !== 'object') throw new Error('orders_root_required');\nconst root = data.orders;\nif (keys(root) !== '$|order' || !root.$ || keys(root.$) !== 'batch_id' ||\n    typeof root.$.batch_id !== 'string' || !root.$.batch_id.trim()) {\n  throw new Error('batch_structure_invalid');\n}\nif (!Array.isArray(root.order) || !root.order.length) throw new Error('orders_array_required');\nconst ids = new Set();\nlet total = 0;\nconst text = (row, name) => {\n  const value = row[name];\n  if (!Array.isArray(value) || value.length !== 1 || typeof value[0] !== 'string') {\n    throw new Error(name + '_single_text_required');\n  }\n  return value[0];\n};\nconst orders = root.order.map(row => {\n  if (!row || typeof row !== 'object' || Array.isArray(row) ||\n      keys(row) !== '$|amount_minor|client|paid' || !row.$ || keys(row.$) !== 'id') {\n    throw new Error('order_structure_invalid');\n  }\n  const id = row.$.id;\n  if (typeof id !== 'string' || !/^\\d{3}$/.test(id) || ids.has(id)) {\n    throw new Error('order_id_invalid_or_duplicate');\n  }\n  const client = text(row, 'client');\n  const rawAmount = text(row, 'amount_minor');\n  const rawPaid = text(row, 'paid');\n  if (!client.trim()) throw new Error('client_required');\n  if (!/^\\d+$/.test(rawAmount) || !Number.isSafeInteger(Number(rawAmount))) {\n    throw new Error('amount_minor_invalid');\n  }\n  if (!['true', 'false'].includes(rawPaid)) throw new Error('paid_invalid');\n  const amount = Number(rawAmount);\n  total += amount;\n  if (!Number.isSafeInteger(total)) throw new Error('total_out_of_range');\n  ids.add(id);\n  return { order_id: id, client, amount_minor: amount, paid: rawPaid === 'true' };\n});\nreturn [{ json: {\n  batch_id: root.$.batch_id,\n  orders,\n  count: orders.length,\n  total_amount_minor: total,\n  action_performed: false,\n} }];"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Sample XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample XML": {
      "main": [
        [
          {
            "node": "Check XML Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check XML Text": {
      "main": [
        [
          {
            "node": "XML to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "XML to JSON": {
      "main": [
        [
          {
            "node": "Check Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataSuccessExecution": "all",
    "saveDataErrorExecution": "all"
  },
  "pinData": {}
}
