{"id":349,"date":"2025-08-31T04:49:52","date_gmt":"2025-08-31T04:49:52","guid":{"rendered":"https:\/\/bioskinetics.com.au\/?page_id=349"},"modified":"2025-08-31T04:49:53","modified_gmt":"2025-08-31T04:49:53","slug":"invoice-csv-extractor","status":"publish","type":"page","link":"https:\/\/bioskinetics.com.au\/?page_id=349","title":{"rendered":"Invoice -> CSV Extractor"},"content":{"rendered":"\n<!-- ===== Invoice \u2192 CSV Extractor (drop into a WordPress Custom HTML block) ===== -->\n<div id=\"invoice-csv-extractor\" class=\"ice-wrap\">\n  <style>\n    .ice-wrap { --bg:#0b1020; --card:#121a33; --muted:#9fb3ff; --text:#eaf0ff; --accent:#8ab4ff; --accent2:#7df0c5; --warn:#ffd166;\n      font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, \"Helvetica Neue\", Arial, \"Apple Color Emoji\",\"Segoe UI Emoji\";\n      color: var(--text); background: var(--bg); padding: 28px 18px; border-radius: 18px; box-shadow: 0 20px 60px rgb(0 0 0 \/ 30%);\n      max-width: 1100px; margin: 24px auto; }\n    .ice-header { display:flex; align-items:center; gap:14px; margin-bottom:16px; flex-wrap:wrap; }\n    .ice-badge { font-weight:700; font-size:12px; letter-spacing:.08em; color:#0b1020; background:linear-gradient(90deg,var(--accent2),#bdf8e3);\n      padding:6px 10px; border-radius:999px; text-transform:uppercase; }\n    .ice-title { font-size: clamp(20px, 3.6vw, 36px); line-height:1.15; font-weight:800; margin:0; }\n    .ice-sub { color: var(--muted); margin:6px 0 0; font-size:14px }\n    .ice-card { background: var(--card); border:1px solid rgba(255,255,255,.08); border-radius:16px; padding:16px; margin:14px 0; }\n    .ice-row { display:grid; gap:14px; grid-template-columns: 1fr; }\n    @media (min-width: 880px) { .ice-row { grid-template-columns: 1.2fr .8fr; } }\n    .drop { border:1.5px dashed rgba(255,255,255,.18); border-radius:14px; padding:18px; text-align:center; cursor:pointer; }\n    .drop.hover { background: rgba(138,180,255,.07); border-color: var(--accent); }\n    .drop h3 { margin:4px 0 6px; font-size:18px }\n    .drop p { margin:0; color:var(--muted); font-size:13px }\n    .btns { display:flex; gap:10px; flex-wrap:wrap; margin-top:10px; }\n    .btn { appearance:none; border:1px solid rgba(255,255,255,.12); background:#0e1530; color:var(--text);\n      padding:10px 14px; border-radius:10px; font-weight:700; font-size:14px; cursor:pointer; transition:.2s; }\n    .btn.primary { background: linear-gradient(90deg, var(--accent), #c3d6ff); color:#0b1020; border:0; }\n    .btn.ghost { background: transparent; }\n    .btn:disabled{ opacity:.5; cursor:not-allowed; }\n    .mini { font-size:12px; color: var(--muted); }\n    .note { background: #0c132b; border:1px solid rgba(255,255,255,.08); border-radius:12px; padding:10px 12px; font-size:12.5px; color: #cfe0ff }\n    .grid-2 { display:grid; gap:12px; grid-template-columns: 1fr 1fr; }\n    .field { display:flex; gap:8px; align-items:center; }\n    .field label { font-size:12px; color: var(--muted); }\n    .field input[type=\"number\"]{ width:80px; }\n    .switch { display:flex; align-items:center; gap:8px; }\n    .switch input { transform: translateY(1px); }\n    table { width:100%; border-collapse: collapse; font-size:14px; }\n    thead th { position:sticky; top:0; background:#0b1229; z-index:1; }\n    th, td { border:1px solid rgba(255,255,255,.08); padding:8px 10px; vertical-align: top; }\n    th { text-align:left; font-weight:800; color:#e3ebff; }\n    td { color:#dfe8ff }\n    td[contenteditable=\"true\"], th[contenteditable=\"true\"] { outline: none; }\n    .pill { font-size:11px; padding:3px 8px; border-radius:999px; background:#0d1737; border:1px solid rgba(255,255,255,.08); color:#cfe0ff }\n    .loading { display:none; gap:8px; align-items:center; }\n    .loading.show { display:flex; }\n    .spinner { width:16px; height:16px; border-radius:50%; border:2px solid rgba(255,255,255,.25); border-top-color:#fff; animation:spin .9s linear infinite }\n    @keyframes spin { to { transform: rotate(360deg) } }\n    .warn { color: var(--warn); }\n    .sr { border:0!important; clip:rect(0 0 0 0)!important; clip-path:inset(50%)!important; height:1px!important; margin:-1px!important; overflow:hidden!important; padding:0!important; position:absolute!important; width:1px!important; white-space:nowrap!important; }\n  <\/style>\n\n  <div class=\"ice-header\">\n    <span class=\"ice-badge\">Invoice \u2192 CSV<\/span>\n    <h1 class=\"ice-title\">Upload a PDF invoice, get clean <span style=\"color:var(--accent2)\">CSV<\/span> or <span style=\"color:var(--accent)\">Excel<\/span><\/h1>\n  <\/div>\n  <p class=\"ice-sub\">Private by default \u2014 files never leave your browser. Edit cells inline, then export.<\/p>\n\n  <div class=\"ice-row\">\n    <!-- LEFT: Upload & Controls -->\n    <div class=\"ice-card\">\n      <div id=\"dropzone\" class=\"drop\" tabindex=\"0\" role=\"button\" aria-label=\"Upload PDF Invoice\">\n        <input id=\"file\" type=\"file\" accept=\"application\/pdf\" class=\"sr\">\n        <h3>Drop PDF here or <u>click to choose<\/u><\/h3>\n        <p>We\u2019ll auto-detect the line-items table. You can fine-tune if needed.<\/p>\n      <\/div>\n\n      <div class=\"btns\">\n        <button id=\"parseBtn\" class=\"btn primary\" disabled>Extract Line Items<\/button>\n        <button id=\"resetBtn\" class=\"btn ghost\" disabled>Reset<\/button>\n        <div class=\"loading\" id=\"loading\"><div class=\"spinner\"><\/div><span class=\"mini\">Parsing PDF\u2026<\/span><\/div>\n      <\/div>\n\n      <div class=\"ice-card\" style=\"margin-top:14px\">\n        <div class=\"grid-2\">\n          <div class=\"field\">\n            <label for=\"yTol\">Line merge tolerance<\/label>\n            <input id=\"yTol\" type=\"number\" min=\"1\" max=\"10\" step=\"1\" value=\"3\">\n            <span class=\"mini\">pixels<\/span>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"splitSpaces\">Split on \u2265 spaces<\/label>\n            <input id=\"splitSpaces\" type=\"number\" min=\"2\" max=\"10\" step=\"1\" value=\"2\">\n          <\/div>\n        <\/div>\n        <div class=\"grid-2\" style=\"margin-top:10px\">\n          <label class=\"switch\"><input id=\"stopOnTotals\" type=\"checkbox\" checked> Stop at \u201cSubtotal\/Tax\/Total\u201d<\/label>\n          <label class=\"switch\"><input id=\"hasHeader\" type=\"checkbox\" checked> Try to detect headers<\/label>\n        <\/div>\n        <p class=\"note\" style=\"margin-top:10px\">\n          Tips: If columns look jumbled, raise <b>Split on \u2265 spaces<\/b>. If lines merge incorrectly, lower or raise <b>Line merge tolerance<\/b>.\n        <\/p>\n      <\/div>\n\n      <div class=\"ice-card\">\n        <div class=\"btns\">\n          <button id=\"toCSV\" class=\"btn\" disabled>Download CSV<\/button>\n          <button id=\"toXLSX\" class=\"btn\" disabled>Download Excel<\/button>\n          <button id=\"copyCSV\" class=\"btn ghost\" disabled>Copy CSV<\/button>\n        <\/div>\n        <p class=\"mini\" style=\"margin-top:8px\">Exports use whatever\u2019s currently in the table (including your edits).<\/p>\n      <\/div>\n\n      <p class=\"mini\">Need better accuracy? Ensure the invoice has clear column headers like <span class=\"pill\">Description<\/span> <span class=\"pill\">Qty<\/span> <span class=\"pill\">Unit Price<\/span> <span class=\"pill\">Amount<\/span>.<\/p>\n    <\/div>\n\n    <!-- RIGHT: Preview -->\n    <div class=\"ice-card\">\n      <div style=\"display:flex; justify-content:space-between; align-items:center; margin-bottom:10px\">\n        <h3 style=\"margin:0\">Preview<\/h3>\n        <span id=\"pagesInfo\" class=\"mini\"><\/span>\n      <\/div>\n      <div id=\"tableWrap\" style=\"max-height:520px; overflow:auto; border-radius:10px; border:1px solid rgba(255,255,255,.06);\">\n        <table id=\"previewTable\" aria-label=\"Extracted line items\">\n          <thead><tr><th>Column A<\/th><th>Column B<\/th><th>Column C<\/th><th>Column D<\/th><\/tr><\/thead>\n          <tbody><tr><td colspan=\"4\" class=\"mini\">Upload a PDF to begin.<\/td><\/tr><\/tbody>\n        <\/table>\n      <\/div>\n      <p id=\"status\" class=\"mini\" style=\"margin-top:10px\"><\/p>\n    <\/div>\n  <\/div>\n\n  <!-- Scripts (CDN) -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.min.js\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"><\/script>\n  <script>\n    \/\/ Configure pdf.js worker (must match the version above)\n    pdfjsLib.GlobalWorkerOptions.workerSrc = \"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.worker.min.js\";\n  <\/script>\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/xlsx\/0.18.5\/xlsx.full.min.js\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"><\/script>\n\n  <!-- App Logic -->\n  <script>\n  (function(){\n    const els = {\n      file: document.getElementById('file'),\n      drop: document.getElementById('dropzone'),\n      parseBtn: document.getElementById('parseBtn'),\n      resetBtn: document.getElementById('resetBtn'),\n      loading: document.getElementById('loading'),\n      yTol: document.getElementById('yTol'),\n      splitSpaces: document.getElementById('splitSpaces'),\n      stopOnTotals: document.getElementById('stopOnTotals'),\n      hasHeader: document.getElementById('hasHeader'),\n      toCSV: document.getElementById('toCSV'),\n      toXLSX: document.getElementById('toXLSX'),\n      copyCSV: document.getElementById('copyCSV'),\n      table: document.getElementById('previewTable'),\n      tableWrap: document.getElementById('tableWrap'),\n      status: document.getElementById('status'),\n      pagesInfo: document.getElementById('pagesInfo'),\n    };\n\n    let rawItems = [];     \/\/ {page, x, y, str}\n    let pdfMeta = { pages: 0, name: \"\" };\n\n    \/\/ --- Utilities ---\n    const setLoading = (on)=> els.loading.classList.toggle('show', !!on);\n    const setStatus = (msg, isWarn=false)=> {\n      els.status.innerHTML = msg || '';\n      els.status.classList.toggle('warn', !!isWarn);\n    };\n    const sanitize = (s)=> (s ?? \"\").toString().replace(\/\\s+\/g,' ').trim();\n    const by = (k, dir=1) => (a,b)=> (a[k]===b[k]?0:(a[k]>b[k]?dir:-dir));\n    const csvEscape = (v)=> {\n      const s = (v ?? '').toString().replace(\/\\r\\n|\\n|\\r\/g, ' ');\n      return \/[\",\\n]\/.test(s) ? '\"' + s.replace(\/\"\/g, '\"\"') + '\"' : s;\n    };\n    const download = (blob, filename) => {\n      const url = URL.createObjectURL(blob);\n      const a = document.createElement('a');\n      a.href = url; a.download = filename; document.body.appendChild(a); a.click();\n      setTimeout(()=>{ URL.revokeObjectURL(url); a.remove(); }, 0);\n    };\n\n    function enableActions(enabled) {\n      els.toCSV.disabled = !enabled;\n      els.toXLSX.disabled = !enabled;\n      els.copyCSV.disabled = !enabled;\n    }\n\n    function resetTable() {\n      els.table.tHead.innerHTML = \"<tr><th>Column A<\/th><th>Column B<\/th><th>Column C<\/th><th>Column D<\/th><\/tr>\";\n      els.table.tBodies[0].innerHTML = \"<tr><td colspan='4' class='mini'>Upload a PDF to begin.<\/td><\/tr>\";\n      setStatus('');\n      enableActions(false);\n      els.pagesInfo.textContent = \"\";\n    }\n\n    resetTable();\n\n    \/\/ --- File Handling ---\n    function onFiles(files) {\n      const f = files && files[0];\n      if(!f) return;\n      if(f.type !== \"application\/pdf\" && !\/\\.pdf$\/i.test(f.name)) {\n        setStatus(\"Please upload a PDF file.\", true); return;\n      }\n      pdfMeta.name = f.name.replace(\/\\.[^.]+$\/,'');\n      els.parseBtn.disabled = false;\n      els.resetBtn.disabled = false;\n      setStatus(`Ready to parse: <b>${f.name}<\/b>`);\n      rawItems = []; \/\/ clear previous\n      \/\/ store file in input for parse click:\n      els.file._selectedFile = f;\n    }\n\n    els.drop.addEventListener('click', ()=> els.file.click());\n    els.drop.addEventListener('dragover', (e)=> { e.preventDefault(); els.drop.classList.add('hover'); });\n    els.drop.addEventListener('dragleave', ()=> els.drop.classList.remove('hover'));\n    els.drop.addEventListener('drop', (e)=> { e.preventDefault(); els.drop.classList.remove('hover'); onFiles(e.dataTransfer.files); });\n    els.file.addEventListener('change', (e)=> onFiles(e.target.files));\n\n    els.resetBtn.addEventListener('click', ()=>{\n      els.file.value = \"\";\n      els.file._selectedFile = null;\n      els.parseBtn.disabled = true;\n      els.resetBtn.disabled = true;\n      rawItems = [];\n      pdfMeta = { pages: 0, name: \"\" };\n      resetTable();\n    });\n\n    \/\/ --- PDF Parsing ---\n    async function extractTextItems(file) {\n      const buf = await file.arrayBuffer();\n      const pdf = await pdfjsLib.getDocument({ data: buf }).promise;\n      pdfMeta.pages = pdf.numPages;\n      const items = [];\n      for(let p=1; p<=pdf.numPages; p++){\n        const page = await pdf.getPage(p);\n        const textContent = await page.getTextContent();\n        const scale = 1;\n        const viewport = page.getViewport({ scale });\n        \/\/ Normalize coordinates to page coordinate space\n        textContent.items.forEach(it=>{\n          \/\/ transform: [a,b,c,d,e,f]; we can take e,f (x,y)\n          const [a,b,c,d,e,f] = it.transform;\n          items.push({ page: p, x: e, y: f, str: it.str });\n        });\n      }\n      return items;\n    }\n\n    function groupIntoLines(items, yTolerancePx) {\n      \/\/ Sort by y desc (top-to-bottom visually), then x asc\n      const sorted = items.slice().sort((a,b)=> (b.y - a.y) || (a.x - b.x));\n      const lines = [];\n      let line = [];\n      let lastY = null;\n      for(const it of sorted){\n        if(lastY === null || Math.abs(it.y - lastY) <= yTolerancePx){\n          line.push(it); lastY = (lastY===null? it.y : (lastY*0.7 + it.y*0.3)); \/\/ gentle smoothing\n        } else {\n          if(line.length){\n            line.sort(by('x',1));\n            lines.push({ y:lastY, text: line.map(x=>x.str).join(' ') });\n          }\n          line = [it];\n          lastY = it.y;\n        }\n      }\n      if(line.length){\n        line.sort(by('x',1));\n        lines.push({ y:lastY, text: line.map(x=>x.str).join(' ') });\n      }\n      \/\/ Clean & collapse whitespace\n      return lines.map(l=> ({ y:l.y, text: sanitize(l.text) })).filter(l=> l.text);\n    }\n\n    function detectHeaderIndex(lines){\n      const headers = [\/description|item|details|product\/i, \/sku|code|id\/i, \/qty|quantity|hours\/i, \/unit\\s*price|price\\\/unit|rate\/i, \/amount|line\\s*total|price\/i];\n      for(let i=0;i<lines.length;i++){\n        const t = lines[i].text;\n        let hits = 0;\n        headers.forEach(rx=>{ if(rx.test(t)) hits++; });\n        if(hits >= 2) return i;\n      }\n      return -1;\n    }\n\n    function looksLikeTotals(line){\n      return \/(subtotal|tax|gst|vat|total|grand\\s*total|balance\\s*due)\/i.test(line.text);\n    }\n\n    function splitColumns(text, minSpaces){\n      \/\/ Replace 2+ spaces with a tab, then split\n      const rx = new RegExp(`\\\\s{${minSpaces},}`, 'g');\n      return text.replace(rx, '\\t').split('\\t').map(s=> s.trim()).filter(Boolean);\n    }\n\n    function buildTable(headers, rows){\n      const thead = els.table.tHead;\n      const tbody = els.table.tBodies[0];\n      thead.innerHTML = '';\n      const trh = document.createElement('tr');\n      headers.forEach(h=>{\n        const th = document.createElement('th');\n        th.textContent = h || '\u2014';\n        th.contentEditable = \"true\";\n        trh.appendChild(th);\n      });\n      thead.appendChild(trh);\n\n      tbody.innerHTML = '';\n      if(rows.length === 0){\n        const tr = document.createElement('tr');\n        const td = document.createElement('td');\n        td.colSpan = headers.length;\n        td.textContent = 'No line items detected. Try adjusting the options above.';\n        tr.appendChild(td); tbody.appendChild(tr);\n      } else {\n        rows.forEach(r=>{\n          const tr = document.createElement('tr');\n          for(let i=0;i<headers.length;i++){\n            const td = document.createElement('td');\n            td.contentEditable = \"true\";\n            td.textContent = r[i] ?? '';\n            tr.appendChild(td);\n          }\n          tbody.appendChild(tr);\n        });\n      }\n    }\n\n    function tableToArrays(){\n      const headers = Array.from(els.table.tHead.querySelectorAll('th')).map(th=> th.textContent.trim());\n      const rows = Array.from(els.table.tBodies[0].querySelectorAll('tr')).map(tr=>{\n        return Array.from(tr.querySelectorAll('td')).map(td=> td.textContent.trim());\n      });\n      return { headers, rows };\n    }\n\n    \/\/ --- Main Extract Flow ---\n    els.parseBtn.addEventListener('click', async ()=>{\n      const file = els.file._selectedFile;\n      if(!file){ setStatus('No file selected.', true); return; }\n      setLoading(true); setStatus('');\n      enableActions(false);\n\n      try{\n        rawItems = await extractTextItems(file);\n        const yTol = parseInt(els.yTol.value || '3', 10);\n        const minSpaces = parseInt(els.splitSpaces.value || '2', 10);\n        const lines = groupIntoLines(rawItems, yTol);\n\n        els.pagesInfo.textContent = `Pages: ${pdfMeta.pages}`;\n        if(lines.length === 0){ setStatus('No text found in PDF. Is it a scanned image? (This tool does not perform OCR.)', true); resetTable(); setLoading(false); return; }\n\n        let headerIdx = els.hasHeader.checked ? detectHeaderIndex(lines) : -1;\n        let dataStart = headerIdx >= 0 ? headerIdx + 1 : 0;\n\n        let headerCols = headerIdx >= 0 ? splitColumns(lines[headerIdx].text, minSpaces) : ['Description','Qty','Unit Price','Amount'];\n        \/\/ constrain header length (avoid 1, or absurd numbers)\n        if(headerCols.length < 2) headerCols = ['Description','Qty','Unit Price','Amount'];\n        if(headerCols.length > 10) headerCols = headerCols.slice(0,10);\n\n        const rows = [];\n        for(let i=dataStart;i<lines.length;i++){\n          const L = lines[i];\n          if(els.stopOnTotals.checked &#038;&#038; looksLikeTotals(L)) break;\n          \/\/ split into parts\n          let parts = splitColumns(L.text, minSpaces);\n          if(parts.length < 2) continue; \/\/ skip obvious non-rows\n          \/\/ Normalize to header length\n          if(parts.length > headerCols.length){\n            \/\/ Heuristic: merge extras into Description (first col)\n            const extras = parts.length - headerCols.length;\n            const merged = [parts.slice(0, 1+extras).join(' '), ...parts.slice(1+extras)];\n            parts = merged;\n          } else if(parts.length < headerCols.length){\n            parts = [...parts, ...Array(headerCols.length - parts.length).fill('')];\n          }\n          rows.push(parts);\n        }\n\n        if(rows.length === 0 &#038;&#038; headerIdx < 0){\n          \/\/ Fallback: try a generic 4-col split across all lines, ignoring totals\n          const fallback = [];\n          for(const L of lines){\n            if(els.stopOnTotals.checked &#038;&#038; looksLikeTotals(L)) break;\n            const parts = splitColumns(L.text, minSpaces);\n            if(parts.length >= 2) fallback.push(parts.slice(0,4));\n          }\n          if(fallback.length){\n            headerCols = ['Column 1','Column 2','Column 3','Column 4'];\n            buildTable(headerCols, fallback);\n            setStatus('No clear headers found. Displaying best-effort split. You can edit headers and cells, then export.', true);\n            enableActions(true);\n          } else {\n            resetTable();\n            setStatus('Couldn\u2019t detect a table. Try increasing \u201cSplit on \u2265 spaces\u201d, or uncheck \u201cStop at totals\u201d.', true);\n            enableActions(false);\n          }\n        } else {\n          buildTable(headerCols, rows);\n          setStatus(headerIdx >= 0 ? 'Headers detected automatically. Review & export.' : 'Table built without headers. Edit headers if needed, then export.');\n          enableActions(true);\n        }\n\n      } catch(err){\n        console.error(err);\n        setStatus('Parsing failed. Ensure the PDF is text-based (not scanned).', true);\n        resetTable();\n      } finally {\n        setLoading(false);\n      }\n    });\n\n    \/\/ --- Exports ---\n    els.toCSV.addEventListener('click', ()=>{\n      const { headers, rows } = tableToArrays();\n      const all = [headers, ...rows];\n      const lines = all.map(r=> r.map(csvEscape).join(',')).join('\\n');\n      const bom = new Uint8Array([0xEF,0xBB,0xBF]); \/\/ help Excel open UTF-8\n      const blob = new Blob([bom, lines], { type: 'text\/csv;charset=utf-8' });\n      const base = pdfMeta.name || 'invoice';\n      download(blob, `${base}-line-items.csv`);\n    });\n\n    els.copyCSV.addEventListener('click', async ()=>{\n      const { headers, rows } = tableToArrays();\n      const all = [headers, ...rows];\n      const text = all.map(r=> r.map(csvEscape).join(',')).join('\\n');\n      try {\n        await navigator.clipboard.writeText(text);\n        setStatus('CSV copied to clipboard.');\n      } catch(e){\n        setStatus('Clipboard copy failed \u2014 your browser may block it. Use \u201cDownload CSV\u201d instead.', true);\n      }\n    });\n\n    els.toXLSX.addEventListener('click', ()=>{\n      const table = els.table;\n      const wb = XLSX.utils.table_to_book(table, { sheet: \"Invoice\" });\n      const base = pdfMeta.name || 'invoice';\n      XLSX.writeFile(wb, `${base}-line-items.xlsx`);\n    });\n\n  })();\n  <\/script>\n\n  <p class=\"mini\" style=\"margin-top:14px\">\n    <b>Privacy note:<\/b> Processing happens entirely in your browser; nothing is uploaded to a server.\n    For scanned invoices (images), run OCR first (e.g., convert to searchable PDF) then try again.\n  <\/p>\n<\/div>\n<!-- ===== \/Invoice \u2192 CSV Extractor ===== -->\ninvoice to csv, pdf invoice to csv, invoice csv extractor, convert invoice to csv, extract invoice line items csv, pdf to csv tool, invoice data extractor, invoice to excel, convert invoice pdf to excel, extract line items from invoice pdf, invoice parser to csv online, free invoice csv converter, automated invoice data extraction, online invoice to csv converter, export invoice line items to excel, pdf invoice data to spreadsheet, convert multiple invoices to csv, invoice csv download tool, invoice to csv no upload, browser-based invoice extractor, invoice data entry automation, pdf table extractor, convert invoice pdf to spreadsheet, best invoice to csv software, pdf invoice line item parser, export invoices to xlsx, clean csv from pdf invoice, invoice digitization tool, invoice data capture software, csv invoice export online, extract structured data from invoices, convert scanned invoice to excel, invoice to csv free online tool, invoice to csv for accountants, invoice to excel for bookkeeping, invoice extraction tool for small business, invoice csv converter australia, invoice csv converter usa, invoice csv converter uk\n\n\n","protected":false},"excerpt":{"rendered":"<p>Invoice \u2192 CSV Upload a PDF invoice, get clean CSV or Excel Private by default \u2014 files never leave your [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"blank-slate-template.php","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_glsr_average":0,"_glsr_ranking":0,"_glsr_reviews":0,"footnotes":""},"class_list":["post-349","page","type-page","status-publish","hentry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages\/349","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=349"}],"version-history":[{"count":2,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages\/349\/revisions"}],"predecessor-version":[{"id":351,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages\/349\/revisions\/351"}],"wp:attachment":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=349"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}