{"id":9423,"date":"2026-08-10T13:37:15","date_gmt":"2026-08-10T11:37:15","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/pihole-adguard-blocklist-converter-overlap\/"},"modified":"2026-08-10T13:45:07","modified_gmt":"2026-08-10T11:45:07","slug":"pihole-adguard-blocklist-converter-overlap","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/","title":{"rendered":"Pi-hole \/ AdGuard Blocklist Converter &#038; Overlap Analyzer"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0ab); margin-bottom: 20px;\">Merge Pi-hole and AdGuard blocklists, convert them into the format your resolver expects, and see how much the lists actually overlap. Everything runs in your browser tab, nothing is uploaded.<\/p>\n<div style=\"display: flex; gap: 15px; flex-wrap: wrap; margin-bottom: 20px;\">\n<div style=\"flex: 1; min-width: 280px;\">\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">List name<\/label><br \/>\n            <input type=\"text\" id=\"bl-name-1\" value=\"Blocklist 1\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; margin-bottom: 8px;\"><br \/>\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Entries &#8211; hosts, plain domains, AdGuard or dnsmasq syntax, one per line<\/label><br \/>\n            <textarea id=\"bl-list-1\" rows=\"8\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; font-family: monospace; font-size: 0.85rem;\"># hosts<br \/>\n0.0.0.0 ads.example.com<br \/>\n0.0.0.0 example.com<br \/>\n0.0.0.0 tracker.example.net<br \/>\n127.0.0.1 metrics.example.org<br \/>\n0.0.0.0 pixel.example.net<\/textarea>\n        <\/div>\n<div style=\"flex: 1; min-width: 280px;\">\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">List name<\/label><br \/>\n            <input type=\"text\" id=\"bl-name-2\" value=\"Blocklist 2\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; margin-bottom: 8px;\"><br \/>\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Entries &#8211; hosts, plain domains, AdGuard or dnsmasq syntax, one per line<\/label><br \/>\n            <textarea id=\"bl-list-2\" rows=\"8\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; font-family: monospace; font-size: 0.85rem;\">! adguard<br \/>\n||tracker.example.net^<br \/>\n||analytics.example.io^$important<br \/>\n||example.com^<br \/>\n||cdn.example.io^<br \/>\naddress=\/beacon.example.io\/0.0.0.0<\/textarea>\n        <\/div>\n<div style=\"flex: 1; min-width: 280px;\">\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">List name<\/label><br \/>\n            <input type=\"text\" id=\"bl-name-3\" value=\"Blocklist 3\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; margin-bottom: 8px;\"><br \/>\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Entries &#8211; hosts, plain domains, AdGuard or dnsmasq syntax, one per line<\/label><br \/>\n            <textarea id=\"bl-list-3\" rows=\"8\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; font-family: monospace; font-size: 0.85rem;\"><\/textarea>\n        <\/div>\n<div style=\"flex: 1; min-width: 280px;\">\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">List name<\/label><br \/>\n            <input type=\"text\" id=\"bl-name-4\" value=\"Blocklist 4\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; margin-bottom: 8px;\"><br \/>\n            <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Entries &#8211; hosts, plain domains, AdGuard or dnsmasq syntax, one per line<\/label><br \/>\n            <textarea id=\"bl-list-4\" rows=\"8\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; font-family: monospace; font-size: 0.85rem;\"><\/textarea>\n        <\/div>\n<\/p><\/div>\n<div style=\"margin-bottom: 20px;\">\n        <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Whitelist (optional)<\/label><\/p>\n<p style=\"color: var(--text-secondary, #a0a0ab); font-size: 0.8rem; margin: 0 0 5px 0;\">Domains that must stay reachable. An entry counts as neutralised when it matches a whitelist domain or is a subdomain of one.<\/p>\n<p>        <textarea id=\"bl-white\" rows=\"4\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; font-family: monospace; font-size: 0.85rem;\">metrics.example.org<br \/>\nexample.io<\/textarea>\n    <\/div>\n<div style=\"margin-bottom: 15px; max-width: 480px;\">\n        <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Target format<\/label><br \/>\n        <select id=\"bl-format\" class=\"form-control\" style=\"width: 100%; padding: 12px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px;\"><option value=\"hosts\">hosts file &#8211; 0.0.0.0 domain.tld<\/option><option value=\"domains\">Plain domain list &#8211; domain.tld<\/option><option value=\"adguard\">AdGuard or uBlock Origin &#8211; ||domain.tld^<\/option><option value=\"dnsmasq\">dnsmasq &#8211; address=\/domain.tld\/0.0.0.0<\/option><\/select>\n    <\/div>\n<div style=\"margin-bottom: 20px;\">\n        <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem; margin-bottom: 6px;\"><input type=\"checkbox\" id=\"bl-opt-white\" checked style=\"margin-right: 8px;\">Drop entries that the whitelist neutralises<\/label><br \/>\n        <label style=\"color: var(--text-secondary, #a0a0ab); display: block; font-size: 0.85rem;\"><input type=\"checkbox\" id=\"bl-opt-red\" style=\"margin-right: 8px;\">Drop subdomains already covered by a parent entry (wildcard coverage exists in the AdGuard and dnsmasq formats only)<\/label>\n    <\/div>\n<div style=\"margin-bottom: 20px;\">\n        <button id=\"bl-btn\" class=\"button\" style=\"background: var(--accent, #4ade80); color: #0b1114; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Analyse and convert<\/button>\n    <\/div>\n<div id=\"bl-ergebnis\"><\/div>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    \/\/ --- reine Rechenlogik, ohne jeden DOM-Zugriff ---<\/p>\n<p>    const LABEL_RE = \/^[a-z0-9_*]([a-z0-9_*-]*[a-z0-9_*])?$\/;<\/p>\n<p>    function normalisiereDomain(roh) {\n        if (typeof roh !== 'string') { return ''; }\n        let d = roh.trim().toLowerCase();\n        if (!d) { return ''; }\n        while (d.length > 1 && d.charAt(d.length - 1) === '.') { d = d.slice(0, d.length - 1); }\n        const fuehrenderPunkt = d.charAt(0) === '.';\n        const kern = fuehrenderPunkt ? d.slice(1) : d;\n        if (!kern || kern.length > 253) { return ''; }\n        const teile = kern.split('.');\n        if (teile.length < 2) { return ''; }\n        for (let i = 0; i < teile.length; i++) {\n            if (teile[i].length < 1 || teile[i].length > 63) { return ''; }\n            if (!LABEL_RE.test(teile[i])) { return ''; }\n        }\n        return fuehrenderPunkt ? '.' + kern : kern;\n    }<\/p>\n<p>    \/\/ null = bewusst uebersprungen (Kommentar, Leerzeile), [] = nicht erkannt\n    function parseZeileAlle(zeile) {\n        if (typeof zeile !== 'string') { return null; }\n        let z = zeile.replace(\/\\r\/g, '').trim();\n        if (!z) { return null; }\n        const erstes = z.charAt(0);\n        if (erstes === '#' || erstes === '!' || erstes === ';') { return null; }<\/p>\n<p>        let treffer = z.match(\/^\\|\\|([^\\^\\s\\\/\\$\\|]+)\\^\/);\n        if (treffer) {\n            const adguard = normalisiereDomain(treffer[1]);\n            return adguard ? [adguard] : [];\n        }<\/p>\n<p>        treffer = z.match(\/^(?:address|server|local)\\s*=\\s*\\\/([^\\\/\\s]+)\\\/\/);\n        if (treffer) {\n            const dnsmasq = normalisiereDomain(treffer[1]);\n            return dnsmasq ? [dnsmasq] : [];\n        }<\/p>\n<p>        const kommentar = z.search(\/\\s[#!]\/);\n        if (kommentar > -1) { z = z.slice(0, kommentar).trim(); }\n        if (!z) { return null; }<\/p>\n<p>        const teile = z.split(\/\\s+\/);\n        if (teile.length > 1) {\n            if (!\/^(?:0\\.0\\.0\\.0|127\\.0\\.0\\.1|255\\.255\\.255\\.255|::1|::)$\/.test(teile[0])) { return []; }\n            const domains = [];\n            for (let i = 1; i < teile.length; i++) {\n                const wirt = normalisiereDomain(teile[i]);\n                if (wirt) { domains.push(wirt); }\n            }\n            return domains;\n        }\n        const einzeln = normalisiereDomain(teile[0]);\n        return einzeln ? [einzeln] : [];\n    }\n\n    function parseZeile(zeile) {\n        const domains = parseZeileAlle(zeile);\n        if (domains === null) { return null; }\n        return domains.length ? domains[0] : '';\n    }\n\n    function parseListe(text) {\n        const ergebnis = { domains: [], eindeutig: 0, doppelt: 0, uebersprungen: 0, unlesbar: 0 };\n        if (typeof text !== 'string' || !text.trim()) { return ergebnis; }\n        const zeilen = text.split('\\n');\n        const gesehen = new Set();\n        for (let i = 0; i < zeilen.length; i++) {\n            const domains = parseZeileAlle(zeilen[i]);\n            if (domains === null) { ergebnis.uebersprungen++; continue; }\n            if (!domains.length) { ergebnis.unlesbar++; continue; }\n            for (let k = 0; k < domains.length; k++) {\n                if (gesehen.has(domains[k])) { ergebnis.doppelt++; continue; }\n                gesehen.add(domains[k]);\n                ergebnis.domains.push(domains[k]);\n            }\n        }\n        ergebnis.eindeutig = ergebnis.domains.length;\n        return ergebnis;\n    }\n\n    \/\/ alle Domains, auf die \"domain\" mit '.' + jene endet - laengste zuerst\n    function elternKandidaten(domain) {\n        const liste = [];\n        if (typeof domain !== 'string') { return liste; }\n        for (let i = 0; i < domain.length - 1; i++) {\n            if (domain.charAt(i) === '.') { liste.push(domain.slice(i + 1)); }\n        }\n        return liste;\n    }\n\n    function findeAbgedeckte(domains) {\n        const treffer = [];\n        const menge = new Set(domains);\n        for (let i = 0; i < domains.length; i++) {\n            const kandidaten = elternKandidaten(domains[i]);\n            for (let k = 0; k < kandidaten.length; k++) {\n                if (menge.has(kandidaten[k])) {\n                    treffer.push({ eintrag: domains[i], elter: kandidaten[k] });\n                    break;\n                }\n            }\n        }\n        return treffer;\n    }\n\n    function findeFreigegebene(domains, whitelist) {\n        const treffer = [];\n        const menge = new Set(whitelist);\n        if (menge.size === 0) { return treffer; }\n        for (let i = 0; i < domains.length; i++) {\n            const domain = domains[i];\n            if (menge.has(domain)) {\n                treffer.push({ eintrag: domain, elter: domain });\n                continue;\n            }\n            const kandidaten = elternKandidaten(domain);\n            for (let k = 0; k < kandidaten.length; k++) {\n                if (menge.has(kandidaten[k])) {\n                    treffer.push({ eintrag: domain, elter: kandidaten[k] });\n                    break;\n                }\n            }\n        }\n        return treffer;\n    }\n\n    function ueberschneidung(domainsA, domainsB) {\n        const menge = new Set(domainsB);\n        let anzahl = 0;\n        for (let i = 0; i < domainsA.length; i++) {\n            if (menge.has(domainsA[i])) { anzahl++; }\n        }\n        return anzahl;\n    }\n\n    function formatiereDomain(domain, ziel) {\n        if (ziel === 'hosts') { return '0.0.0.0 ' + domain; }\n        if (ziel === 'adguard') { return '||' + domain + '^'; }\n        if (ziel === 'dnsmasq') { return 'address=\/' + domain + '\/0.0.0.0'; }\n        return domain;\n    }\n\n    function formatiere(domains, ziel) {\n        if (!domains || !domains.length) { return ''; }\n        return domains.map(function (domain) { return formatiereDomain(domain, ziel); }).join('\\n');\n    }\n\n    function anteil(teil, ganz) {\n        if (!ganz) { return 0; }\n        return teil \/ ganz * 100;\n    }\n\n    function berechne(eingaben, whitelistText, ziel, optWhitelist, optRedundanz) {\n        const quellen = Array.isArray(eingaben) ? eingaben : [];\n        const listen = [];\n        for (let i = 0; i < quellen.length; i++) {\n            const text = quellen[i] &#038;&#038; typeof quellen[i].text === 'string' ? quellen[i].text : '';\n            if (!text.trim()) { continue; }\n            const geparst = parseListe(text);\n            const name = quellen[i].name &#038;&#038; String(quellen[i].name).trim() ? String(quellen[i].name).trim() : '#' + (i + 1);\n            listen.push({\n                name: name,\n                domains: geparst.domains,\n                eindeutig: geparst.eindeutig,\n                doppelt: geparst.doppelt,\n                uebersprungen: geparst.uebersprungen,\n                unlesbar: geparst.unlesbar\n            });\n        }\n\n        const leer = {\n            fehler: '', listen: [], whitelistAnzahl: 0, gelesen: 0, gesamtDoppelt: 0,\n            gesamtUebersprungen: 0, gesamtUnlesbar: 0, matrix: [], freigegeben: [],\n            abgedeckt: [], ausgabe: [], text: ''\n        };\n\n        if (listen.length === 0) { leer.fehler = 'Paste at least one list, then run the analysis again.'; return leer; }\n\n        const whitelist = parseListe(whitelistText);\n\n        let gelesen = 0;\n        let gesamtUebersprungen = 0;\n        let gesamtUnlesbar = 0;\n        const gesehen = new Set();\n        const vereinigung = [];\n        for (let i = 0; i < listen.length; i++) {\n            gelesen += listen[i].eindeutig + listen[i].doppelt;\n            gesamtUebersprungen += listen[i].uebersprungen;\n            gesamtUnlesbar += listen[i].unlesbar;\n            for (let j = 0; j < listen[i].domains.length; j++) {\n                const domain = listen[i].domains[j];\n                if (!gesehen.has(domain)) { gesehen.add(domain); vereinigung.push(domain); }\n            }\n        }\n\n        if (vereinigung.length === 0) {\n            leer.fehler = 'No valid domain was found in the pasted lines. Please check the syntax of your entries.';\n            leer.listen = listen;\n            leer.whitelistAnzahl = whitelist.eindeutig;\n            leer.gesamtUebersprungen = gesamtUebersprungen;\n            leer.gesamtUnlesbar = gesamtUnlesbar;\n            return leer;\n        }\n\n        const matrix = [];\n        for (let i = 0; i < listen.length; i++) {\n            const zeile = [];\n            for (let j = 0; j < listen.length; j++) {\n                const gemeinsam = i === j ? listen[i].eindeutig : ueberschneidung(listen[i].domains, listen[j].domains);\n                zeile.push({ gemeinsam: gemeinsam, anteil: anteil(gemeinsam, listen[i].eindeutig) });\n            }\n            matrix.push(zeile);\n        }\n\n        const freigegeben = findeFreigegebene(vereinigung, whitelist.domains);\n        const freiMenge = new Set(freigegeben.map(function (eintrag) { return eintrag.eintrag; }));\n\n        let basis = vereinigung;\n        if (optWhitelist &#038;&#038; freiMenge.size > 0) {\n            basis = vereinigung.filter(function (domain) { return !freiMenge.has(domain); });\n        }<\/p>\n<p>        const abgedeckt = findeAbgedeckte(basis);\n        const abgedecktMenge = new Set(abgedeckt.map(function (eintrag) { return eintrag.eintrag; }));<\/p>\n<p>        let ausgabe = basis;\n        if (optRedundanz && abgedecktMenge.size > 0) {\n            ausgabe = basis.filter(function (domain) { return !abgedecktMenge.has(domain); });\n        }\n        ausgabe = ausgabe.slice().sort();<\/p>\n<p>        return {\n            fehler: '',\n            listen: listen,\n            whitelistAnzahl: whitelist.eindeutig,\n            gelesen: gelesen,\n            gesamtDoppelt: gelesen - vereinigung.length,\n            gesamtUebersprungen: gesamtUebersprungen,\n            gesamtUnlesbar: gesamtUnlesbar,\n            matrix: matrix,\n            freigegeben: freigegeben,\n            abgedeckt: abgedeckt,\n            ausgabe: ausgabe,\n            text: formatiere(ausgabe, ziel)\n        };\n    }<\/p>\n<p>    \/\/ --- Darstellung, ebenfalls ohne DOM-Zugriff ---<\/p>\n<p>    const ERSATZ = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' };<\/p>\n<p>    function sicher(wert) {\n        return String(wert).replace(\/[&<>\"]\/g, function (zeichen) { return ERSATZ[zeichen]; });\n    }<\/p>\n<p>    function zahl(wert) {\n        if (typeof wert !== 'number' || !isFinite(wert)) { return '0'; }\n        return String(Math.round(wert));\n    }<\/p>\n<p>    function prozentText(wert) {\n        if (typeof wert !== 'number' || !isFinite(wert)) { return '0.0 %'; }\n        return wert.toFixed(1) + ' %';\n    }<\/p>\n<p>    function meldung(text) {\n        return '<\/p>\n<div style=\"background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 15px; color: var(--text-primary, #e8e8ea);\">' + text + '<\/div>\n<p>';\n    }<\/p>\n<p>    function ueberschrift(text) {\n        return '<\/p>\n<h4 style=\"color: var(--text-primary, #e8e8ea); margin: 25px 0 10px 0; font-size: 1rem;\">' + text + '<\/h4>\n<p>';\n    }<\/p>\n<p>    function zeileWert(bezeichnung, wert) {\n        return '<\/p>\n<div style=\"display: flex; justify-content: space-between; gap: 15px; padding: 6px 0; border-bottom: 1px solid var(--border, #2a2a35);\">' +\n            '<span style=\"color: var(--text-secondary, #a0a0ab);\">' + bezeichnung + '<\/span>' +\n            '<span style=\"color: var(--text-primary, #e8e8ea); font-family: monospace;\">' + wert + '<\/span><\/div>\n<p>';\n    }<\/p>\n<p>    function eintragsListe(eintraege, beziehung) {\n        if (!eintraege.length) {\n            return '<\/p>\n<p style=\"color: var(--text-secondary, #a0a0ab); margin: 0;\">No entries.<\/p>\n<p>';\n        }\n        const grenze = 200;\n        const teile = [];\n        for (let i = 0; i < eintraege.length &#038;&#038; i < grenze; i++) {\n            const zusatz = eintraege[i].elter === eintraege[i].eintrag ? '' :\n                ' <span style=\"color: var(--text-secondary, #a0a0ab);\">' + beziehung + ' ' + sicher(eintraege[i].elter) + '<\/span>';\n            teile.push('<\/p>\n<div>' + sicher(eintraege[i].eintrag) + zusatz + '<\/div>\n<p>');\n        }\n        if (eintraege.length > grenze) {\n            teile.push('<\/p>\n<div style=\"color: var(--text-secondary, #a0a0ab); margin-top: 6px;\">' +\n                'and {n} more entries'.replace('{n}', zahl(eintraege.length - grenze)) + '<\/div>\n<p>');\n        }\n        return '<\/p>\n<div style=\"max-height: 220px; overflow: auto; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 10px; font-family: monospace; font-size: 0.85rem; color: var(--text-primary, #e8e8ea);\">' +\n            teile.join('') + '<\/div>\n<p>';\n    }<\/p>\n<p>    function baueHtml(ergebnis, optWhitelist, optRedundanz) {\n        if (ergebnis.fehler) { return meldung(ergebnis.fehler); }<\/p>\n<p>        const teile = [];<\/p>\n<p>        teile.push(ueberschrift('Converted list'));\n        teile.push('<\/p>\n<p style=\"color: var(--text-secondary, #a0a0ab); margin: 0 0 8px 0; font-size: 0.85rem;\">Select the text below and copy it into your blocklist file.<\/p>\n<p>');\n        teile.push('<textarea id=\"bl-ausgabe\" rows=\"12\" readonly class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea); border-radius: 6px; font-family: monospace; font-size: 0.85rem;\"><\/textarea>');<\/p>\n<p>        teile.push(ueberschrift('Totals'));\n        teile.push(zeileWert('Unique domains in the output', zahl(ergebnis.ausgabe.length)));\n        teile.push(zeileWert('Duplicate entries removed', zahl(ergebnis.gesamtDoppelt)));\n        teile.push(zeileWert('Comment and blank lines skipped', zahl(ergebnis.gesamtUebersprungen)));\n        teile.push(zeileWert('Lines not recognised', zahl(ergebnis.gesamtUnlesbar)));\n        teile.push(zeileWert('Whitelist domains recognised', zahl(ergebnis.whitelistAnzahl)));\n        teile.push(zeileWert('Entries neutralised by the whitelist', zahl(ergebnis.freigegeben.length) + ' - ' + (optWhitelist ? 'removed from the output' : 'kept in the output')));\n        teile.push(zeileWert('Entries covered by a parent entry', zahl(ergebnis.abgedeckt.length) + ' - ' + (optRedundanz ? 'removed from the output' : 'kept in the output')));<\/p>\n<p>        teile.push(ueberschrift('Per list'));\n        const kopf = '<\/p>\n<th style=\"text-align: left; padding: 8px; border-bottom: 1px solid var(--border, #2a2a35); color: var(--text-secondary, #a0a0ab); font-weight: 600;\">';\n        const zelle = '<\/p>\n<td style=\"padding: 8px; border-bottom: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ea);\">';\n        let tabelle = '<\/p>\n<div style=\"overflow-x: auto;\">\n<table style=\"width: 100%; border-collapse: collapse; font-size: 0.9rem;\">\n<thead>\n<tr>' +\n            kopf + 'List<\/th>\n<p>' + kopf + 'Unique<\/th>\n<p>' + kopf + 'Duplicates<\/th>\n<p>' + kopf + 'Skipped<\/th>\n<p>' + kopf + 'Unreadable<\/th>\n<\/tr>\n<\/thead>\n<tbody>';\n        for (let i = 0; i < ergebnis.listen.length; i++) {\n            const liste = ergebnis.listen[i];\n            tabelle += '\n\n<tr>' + zelle + sicher(liste.name) + '<\/td>\n<p>' +\n                zelle + zahl(liste.eindeutig) + '<\/td>\n<p>' +\n                zelle + zahl(liste.doppelt) + '<\/td>\n<p>' +\n                zelle + zahl(liste.uebersprungen) + '<\/td>\n<p>' +\n                zelle + zahl(liste.unlesbar) + '<\/td>\n<\/tr>\n<p>';\n        }\n        tabelle += '<\/tbody>\n<\/table>\n<\/div>\n<p>';\n        teile.push(tabelle);<\/p>\n<p>        if (ergebnis.listen.length > 1) {\n            teile.push(ueberschrift('Overlap matrix'));\n            teile.push('<\/p>\n<p style=\"color: var(--text-secondary, #a0a0ab); margin: 0 0 10px 0; font-size: 0.85rem;\">Each cell counts the domains of the row list that also appear in the column list, followed by the share of the row list.<\/p>\n<p>');\n            let matrixHtml = '<\/p>\n<div style=\"overflow-x: auto;\">\n<table style=\"width: 100%; border-collapse: collapse; font-size: 0.9rem;\">\n<thead>\n<tr>' + kopf + '<\/th>\n<p>';\n            for (let i = 0; i < ergebnis.listen.length; i++) {\n                matrixHtml += kopf + sicher(ergebnis.listen[i].name) + '<\/th>\n<p>';\n            }\n            matrixHtml += '<\/tr>\n<\/thead>\n<tbody>';\n            for (let i = 0; i < ergebnis.matrix.length; i++) {\n                matrixHtml += '\n\n<tr>' + zelle + '<strong>' + sicher(ergebnis.listen[i].name) + '<\/strong><\/td>\n<p>';\n                for (let j = 0; j < ergebnis.matrix[i].length; j++) {\n                    const feld = ergebnis.matrix[i][j];\n                    matrixHtml += zelle + '<span style=\"font-family: monospace;\">' + zahl(feld.gemeinsam) + '<\/span>' +\n                        '<span style=\"color: var(--text-secondary, #a0a0ab); font-family: monospace;\"> (' + prozentText(feld.anteil) + ')<\/span><\/td>\n<p>';\n                }\n                matrixHtml += '<\/tr>\n<p>';\n            }\n            matrixHtml += '<\/tbody>\n<\/table>\n<\/div>\n<p>';\n            teile.push(matrixHtml);\n        }<\/p>\n<p>        teile.push(ueberschrift('Neutralised by the whitelist'));\n        teile.push('<\/p>\n<p style=\"color: var(--text-secondary, #a0a0ab); margin: 0 0 10px 0; font-size: 0.85rem;\">These entries never take effect, because the whitelist allows them again.<\/p>\n<p>');\n        teile.push(eintragsListe(ergebnis.freigegeben, 'allowed by'));<\/p>\n<p>        teile.push(ueberschrift('Redundant entries'));\n        teile.push('<\/p>\n<p style=\"color: var(--text-secondary, #a0a0ab); margin: 0 0 10px 0; font-size: 0.85rem;\">A broader entry already covers these domains. In the AdGuard and dnsmasq formats you can drop them without changing what gets blocked.<\/p>\n<p>');\n        teile.push(eintragsListe(ergebnis.abgedeckt, 'covered by'));<\/p>\n<p>        return teile.join('');\n    }<\/p>\n<p>    \/\/ fuer die Pruefung von aussen erreichbar\n    if (typeof window !== 'undefined') {\n        window.LW_TEST = window.LW_TEST || {};\n        window.LW_TEST.BL = {\n            berechne: berechne,\n            formatiere: formatiere,\n            normalisiereDomain: normalisiereDomain,\n            parseZeile: parseZeile,\n            parseZeileAlle: parseZeileAlle,\n            parseListe: parseListe,\n            elternKandidaten: elternKandidaten,\n            findeAbgedeckte: findeAbgedeckte,\n            findeFreigegebene: findeFreigegebene,\n            ueberschneidung: ueberschneidung,\n            formatiereDomain: formatiereDomain,\n            baueHtml: baueHtml\n        };\n    }<\/p>\n<p>    \/\/ --- DOM-Anbindung, sauber abgesichert ---\n    const btn = document.getElementById('bl-btn');\n    const out = document.getElementById('bl-ergebnis');\n    if (!btn || !out) { return; }<\/p>\n<p>    function wert(id) {\n        const feld = document.getElementById(id);\n        return feld && typeof feld.value === 'string' ? feld.value : '';\n    }<\/p>\n<p>    function angehakt(id) {\n        const feld = document.getElementById(id);\n        return feld ? feld.checked === true : false;\n    }<\/p>\n<p>    btn.addEventListener('click', function () {\n        try {\n            const eingaben = [];\n            for (let i = 1; i <= 4; i++) {\n                eingaben.push({ name: wert('bl-name-' + i), text: wert('bl-list-' + i) });\n            }\n            const ziel = wert('bl-format') || 'hosts';\n            const optWhitelist = angehakt('bl-opt-white');\n            const optRedundanz = angehakt('bl-opt-red');\n            const ergebnis = berechne(eingaben, wert('bl-white'), ziel, optWhitelist, optRedundanz);\n            out.innerHTML = baueHtml(ergebnis, optWhitelist, optRedundanz);\n            const feld = document.getElementById('bl-ausgabe');\n            if (feld) { feld.value = ergebnis.text; }\n        } catch (fehler) {\n            out.innerHTML = meldung('The lists could not be analysed. Please check your input and try again.');\n        }\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Merge Pi-hole and AdGuard blocklists, convert them into the format your resolver expects, and see how much the lists actually overlap. Everything runs in your browser tab, nothing is uploaded. List name Entries &#8211; hosts, plain domains, AdGuard or dnsmasq syntax, one per line # hosts 0.0.0.0 ads.example.com 0.0.0.0 example.com 0.0.0.0 tracker.example.net 127.0.0.1 metrics.example.org 0.0.0.0 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"class_list":["post-9423","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Pi-hole \/ AdGuard Blocklist Converter &amp; Overlap Analyzer - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Pi-hole \/ AdGuard Blocklist Converter &amp; Overlap Analyzer - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Merge Pi-hole and AdGuard blocklists, convert them into the format your resolver expects, and see how much the lists actually overlap. Everything runs in your browser tab, nothing is uploaded. List name Entries &#8211; hosts, plain domains, AdGuard or dnsmasq syntax, one per line # hosts 0.0.0.0 ads.example.com 0.0.0.0 example.com 0.0.0.0 tracker.example.net 127.0.0.1 metrics.example.org 0.0.0.0 [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-10T11:45:07+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/pihole-adguard-blocklist-converter-overlap\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/pihole-adguard-blocklist-converter-overlap\\\/\",\"name\":\"Pi-hole \\\/ AdGuard Blocklist Converter & Overlap Analyzer - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-08-10T11:37:15+00:00\",\"dateModified\":\"2026-08-10T11:45:07+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/pihole-adguard-blocklist-converter-overlap\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/pihole-adguard-blocklist-converter-overlap\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/pihole-adguard-blocklist-converter-overlap\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Pi-hole \\\/ AdGuard Blocklist Converter &#038; Overlap Analyzer\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Pi-hole \/ AdGuard Blocklist Converter & Overlap Analyzer - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/","og_locale":"en_US","og_type":"article","og_title":"Pi-hole \/ AdGuard Blocklist Converter & Overlap Analyzer - Lukas Wojcik - Blog","og_description":"Merge Pi-hole and AdGuard blocklists, convert them into the format your resolver expects, and see how much the lists actually overlap. Everything runs in your browser tab, nothing is uploaded. List name Entries &#8211; hosts, plain domains, AdGuard or dnsmasq syntax, one per line # hosts 0.0.0.0 ads.example.com 0.0.0.0 example.com 0.0.0.0 tracker.example.net 127.0.0.1 metrics.example.org 0.0.0.0 [&hellip;]","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-08-10T11:45:07+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/","name":"Pi-hole \/ AdGuard Blocklist Converter & Overlap Analyzer - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-08-10T11:37:15+00:00","dateModified":"2026-08-10T11:45:07+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/pihole-adguard-blocklist-converter-overlap\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"Pi-hole \/ AdGuard Blocklist Converter &#038; Overlap Analyzer"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9423","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=9423"}],"version-history":[{"count":1,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9423\/revisions"}],"predecessor-version":[{"id":9441,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9423\/revisions\/9441"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=9423"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}