{
  "name": "Учебная HTML-таблица заказов",
  "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    \"price_rub\": 1500\n  },\n  {\n    \"order_id\": \"002\",\n    \"product\": \"Блокнот\",\n    \"quantity\": 0,\n    \"price_rub\": 0\n  },\n  {\n    \"order_id\": \"003\",\n    \"product\": \"Лампа \\\"Старт\\\"\",\n    \"quantity\": 1,\n    \"price_rub\": 499\n  }\n].map(json => ({ json }));"
      }
    },
    {
      "id": "prepare-rows",
      "name": "Prepare Rows",
      "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 keys = ['order_id', 'product', 'quantity', 'price_rub'];\nconst escapeText = value => String(value).replace(/[&<>\"']/g, char => ({\n  '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n})[char]);\nreturn items.map((item, index) => {\n  const row = item.json;\n  if (Object.keys(row).length !== keys.length || keys.some(key => !Object.hasOwn(row, key))) {\n    throw new Error('fields_required');\n  }\n  if (typeof row.order_id !== 'string' || !/^\\d{3}$/.test(row.order_id) || ids.has(row.order_id)) {\n    throw new Error('order_id_invalid_or_duplicate');\n  }\n  ids.add(row.order_id);\n  if (typeof row.product !== 'string' || !row.product.trim() || row.product !== row.product.trim() || row.product.length > 120 || /[\\u0000-\\u001f\\u007f]/.test(row.product)) {\n    throw new Error('product_text_required');\n  }\n  for (const key of ['quantity', 'price_rub']) {\n    if (!Number.isSafeInteger(row[key]) || row[key] < 0) throw new Error('nonnegative_integer_required');\n  }\n  return { json: {\n    'Номер': escapeText(row.order_id),\n    'Товар': escapeText(row.product),\n    'Количество': row.quantity,\n    'Цена, руб.': row.price_rub\n  }, pairedItem: { item: index } };\n});"
      }
    },
    {
      "id": "build",
      "name": "Build Table",
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [
        660,
        260
      ],
      "parameters": {
        "operation": "convertToHtmlTable",
        "options": {
          "capitalize": false,
          "customStyling": true,
          "caption": "Учебные заказы",
          "tableAttributes": "aria-label=\"Учебные заказы\"",
          "headerAttributes": "",
          "rowAttributes": "",
          "cellAttributes": ""
        }
      }
    },
    {
      "id": "read",
      "name": "Read Cells",
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [
        880,
        260
      ],
      "parameters": {
        "operation": "extractHtmlContent",
        "sourceData": "json",
        "dataPropertyName": "table",
        "extractionValues": {
          "values": [
            {
              "key": "headers",
              "cssSelector": "table thead th",
              "returnValue": "text",
              "returnArray": true
            },
            {
              "key": "cells",
              "cssSelector": "table tbody td",
              "returnValue": "text",
              "returnArray": true
            },
            {
              "key": "rows",
              "cssSelector": "table tbody tr",
              "returnValue": "html",
              "returnArray": true
            },
            {
              "key": "unsafe",
              "cssSelector": "table script, table iframe, table img, table input, table a, table svg, table object",
              "returnValue": "html",
              "returnArray": true
            }
          ]
        },
        "options": {
          "trimValues": false,
          "cleanUpText": false
        }
      }
    },
    {
      "id": "verify-table",
      "name": "Verify Table",
      "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 headers = ['Номер', 'Товар', 'Количество', 'Цена, руб.'];\nconst same = (a, b) => JSON.stringify(a) === JSON.stringify(b);\nconst expected = source.flatMap(row => [row.order_id, row.product, String(row.quantity), String(row.price_rub)]);\nif (!same(parsed.headers, headers)) throw new Error('headers_mismatch');\nif (!Array.isArray(parsed.cells) || !same(parsed.cells, expected)) throw new Error('cells_mismatch');\nif (!Array.isArray(parsed.rows) || parsed.rows.length !== source.length) throw new Error('rows_mismatch');\nif (!Array.isArray(parsed.unsafe) || parsed.unsafe.length) throw new Error('unexpected_elements');\nconst table = $('Build Table').first().json.table;\nif (typeof table !== 'string' || !table.startsWith('<table ')) throw new Error('table_required');\nconst html = '<!doctype html><html lang=\"ru\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Учебные заказы</title>' +\n  '<style>body{font:16px Arial,sans-serif;margin:0;color:#202324;background:#fff}main{max-width:900px;margin:auto;padding:32px 20px}h1{font-size:28px;letter-spacing:0}.table-wrap{overflow-x:auto}table{width:100%;border-collapse:collapse}caption{text-align:left;padding:12px 0;font-weight:bold}th,td{padding:12px;border:1px solid #d9dfe1;text-align:left}thead{background:#edf4f1}tbody tr:nth-child(even){background:#f6f7f8}</style></head><body><main><h1>Учебные заказы</h1><p>Вымышленные данные для проверки workflow n8n.</p><div class=\"table-wrap\">' +\n  table + '</div></main></body></html>';\nreturn [{ json: { rows: source.length, verified: true, table_html: table, html, action_performed: false } }];"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Sample Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Orders": {
      "main": [
        [
          {
            "node": "Prepare Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Rows": {
      "main": [
        [
          {
            "node": "Build Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Table": {
      "main": [
        [
          {
            "node": "Read Cells",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Cells": {
      "main": [
        [
          {
            "node": "Verify Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}
