{
  "name": "Учебный экспорт заказов JSON в XML",
  "active": false,
  "nodes": [
    {
      "id": "manual",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        260
      ],
      "parameters": {}
    },
    {
      "id": "sample-orders",
      "name": "Sample Orders",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        260
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "return [\n  {\n    \"order_id\": \"001\",\n    \"product\": \"Чай & кофе\",\n    \"quantity\": 2\n  },\n  {\n    \"order_id\": \"002\",\n    \"product\": \"Блокнот\",\n    \"quantity\": 0\n  },\n  {\n    \"order_id\": \"003\",\n    \"product\": \"Лампа \\\"Старт\\\"\",\n    \"quantity\": 1\n  }\n].map(json => ({ json }));"
      }
    },
    {
      "id": "prepare-xml",
      "name": "Prepare XML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        260
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nif (items.length < 1 || items.length > 20) throw new Error('rows_limit');\nconst ids = new Set();\nconst orders = items.map(({ json: row }) => {\n  if (Object.keys(row).sort().join('|') !== 'order_id|product|quantity') throw new Error('fields_invalid');\n  if (typeof row.order_id !== 'string' || !/^\\d{3}$/.test(row.order_id) || ids.has(row.order_id)) {\n    throw new Error('id_invalid_or_duplicate');\n  }\n  ids.add(row.order_id);\n  if (typeof row.product !== 'string' || !row.product.trim() || row.product.length > 120 ||\n      !row.product.isWellFormed() || /[\\u0000-\\u001f\\u007f\\ufffe\\uffff]/.test(row.product)) {\n    throw new Error('product_invalid');\n  }\n  if (!Number.isSafeInteger(row.quantity) || row.quantity < 0) throw new Error('quantity_invalid');\n  return { id: row.order_id, name: row.product, quantity: String(row.quantity) };\n});\nreturn [{ json: { order: orders }, pairedItem: items.map((_, item) => ({ item })) }];"
      }
    },
    {
      "id": "build",
      "name": "Build XML",
      "type": "n8n-nodes-base.xml",
      "typeVersion": 1,
      "position": [
        660,
        260
      ],
      "parameters": {
        "mode": "jsonToxml",
        "dataPropertyName": "xml",
        "options": {
          "rootName": "orders",
          "headless": false,
          "cdata": false,
          "attrkey": "$",
          "charkey": "_",
          "allowSurrogateChars": false
        }
      }
    },
    {
      "id": "read",
      "name": "Read XML",
      "type": "n8n-nodes-base.xml",
      "typeVersion": 1,
      "position": [
        880,
        260
      ],
      "parameters": {
        "mode": "xmlToJson",
        "dataPropertyName": "xml",
        "options": {
          "explicitArray": true,
          "explicitRoot": true,
          "mergeAttrs": false,
          "ignoreAttrs": false,
          "normalize": false,
          "normalizeTags": false,
          "trim": false
        }
      }
    },
    {
      "id": "verify-xml",
      "name": "Verify XML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        260
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const parsed = $input.first().json;\nconst source = $('Sample Orders').all().map(item => item.json);\nconst orders = parsed.orders?.order;\nif (Object.keys(parsed).join('|') !== 'orders' || !Array.isArray(orders) || orders.length !== source.length) {\n  throw new Error('orders_structure_mismatch');\n}\nfor (let i = 0; i < source.length; i++) {\n  const actual = orders[i];\n  const expected = { id: [source[i].order_id], name: [source[i].product], quantity: [String(source[i].quantity)] };\n  if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error('order_content_mismatch');\n}\nconst xml = $('Build XML').first().json.xml;\nif (typeof xml !== 'string' || !xml.startsWith('<?xml version=\"1.0\" encoding=\"UTF-8\"')) {\n  throw new Error('xml_declaration_required');\n}\nreturn [{ json: { xml, count: source.length, verified: true } }];"
      }
    },
    {
      "id": "file",
      "name": "Make File",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        1320,
        260
      ],
      "parameters": {
        "operation": "toText",
        "sourceProperty": "xml",
        "binaryPropertyName": "export",
        "options": {
          "fileName": "orders-demo.xml",
          "encoding": "utf8",
          "addBOM": false
        }
      }
    },
    {
      "id": "check-file",
      "name": "Check File",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        260
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nif (items.length !== 1 || Object.keys(items[0].binary ?? {}).join('|') !== 'export') {\n  throw new Error('one_export_file_required');\n}\nconst item = items[0];\nif (item.binary.export.fileName !== 'orders-demo.xml') throw new Error('file_name_invalid');\nconst bytes = await this.helpers.getBinaryDataBuffer(0, 'export');\nconst expected = Buffer.from($('Verify XML').first().json.xml, 'utf8');\nif (!bytes.equals(expected)) throw new Error('file_bytes_mismatch');\nreturn [{ json: {\n  file_name: 'orders-demo.xml', count: $('Verify XML').first().json.count,\n  bytes: bytes.length, verified: true, action_performed: false\n}, binary: item.binary }];"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Sample Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Orders": {
      "main": [
        [
          {
            "node": "Prepare XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare XML": {
      "main": [
        [
          {
            "node": "Build XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build XML": {
      "main": [
        [
          {
            "node": "Read XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read XML": {
      "main": [
        [
          {
            "node": "Verify XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify XML": {
      "main": [
        [
          {
            "node": "Make File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Make File": {
      "main": [
        [
          {
            "node": "Check File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
