برنامه نویسی

DOM Parsing در جاوااسکریپت

DOM Parsing در جاوااسکریپت

9 دقیقه زمان برای خواندن این مطلب نیاز است.



در دنیای برنامه‌نویسی وب، تقریباً هیچ روزی نیست که با DOM (مدل شیءگرای سند) سر و کار نداشته باشید. چه در حال ساخت یک برنامه تک صفحه‌ای (SPA) باشید، چه در حال دریافت داده از یک API، یا حتی در حال تحلیل صفحات وب دیگران؛ بالاخره به نقطه‌ای می‌رسید که باید یک رشته HTML یا XML را بگیرید و آن را به یک درخت DOM قابل پیمایش و دستکاری تبدیل کنید. این فرآیند دقیقاً همان چیزی است که به آن DOM Parsing می‌گویند. شاید ساده به نظر برسد، اما روش‌ها، تله‌ها و نکات بهینه‌سازی زیادی در این زمینه وجود دارد که اگر ندانید، ممکن است برنامه‌تان کند شود، یا بدتر، در برابر حملات XSS آسیب‌پذیر گردد. در این راهنمای جامع، تمام جنبه‌های DOM Parsing در جاوااسکریپت را بررسی می‌کنیم: از روش‌های قدیمی و ناامن مثل innerHTML تا ابزارهای مدرن و استاندارد مثل DOMParser. همچنین یاد می‌گیرید که چگونه کدهای XML پیچیده را تجزیه کنید، چگونه عملکرد را بهینه کنید و مهم‌تر از همه چطور از امنیت برنامه خود محافظت کنید.

Docker Container Security Best Practices

DOM Parsing در جاوااسکریپت

DOM Parsing چیست و چه زمانی به آن نیاز داریم؟

مرورگرها وقتی یک صفحه HTML را بارگذاری می‌کنند، به طور خودکار رشته خام HTML را به یک درخت DOM تبدیل می‌کنند. اما در جاوااسکریپت، گاهی اوقات شما یک رشته حاوی HTML یا XML را از منبعی غیر از مرورگر دریافت می‌کنید. مثلاً:

  • پاسخ یک API که یک قطعه HTML را به صورت متن برمی‌گرداند.
  • محتوای یک ویرایشگر متن غنی که توسط کاربر ساخته شده است.
  • یک فایل XML که از سرور بارگذاری کرده‌اید.
  • خروجی یک سرویس شخص ثالث.

در همه این موارد، شما به یک روش قابل اعتماد نیاز دارید تا آن رشته را به عناصر DOM واقعی تبدیل کنید تا بتوانید آنها را به صفحه اضافه کنید، ویژگی‌هایشان را بخوانید، یا درختشان را جستجو کنید. به این فرآیند، DOM Parsing می‌گویند.

کتابخانه Playwright برای تست


روش‌های سنتی و خطرات امنیتی innerHTML

ساده‌ترین روشی که به ذهن هر برنامه‌نویسی می‌رسد، استفاده از خاصیت innerHTML است. شما یک عنصر موجود (مثلاً یک <div>) برمی‌دارید و رشته HTML را به آن نسبت می‌دهید:

javascript

const container = document.getElementById('app');
container.innerHTML = '<h1>سلام دنیا</h1><p>این یک پاراگراف است</p>';

این کار بسیار سریع و مستقیم است، اما مشکلات بزرگی دارد.

مشکل امنیتی XSS

بزرگ‌ترین خطر innerHTML، حملات اسکریپت بین سایتی (XSS) است. اگر رشته HTML شما حاوی <script> باشد یا رویدادهای inline مثل onclick، مرورگر آنها را اجرا می‌کند. تصور کنید رشتهای از کاربر دریافت می‌کنید:

javascript

const userInput = '<img src="x" onerror="alert(\'هک شدید\')">';
container.innerHTML = userInput; // کد اجرا می‌شود!

با یک innerHTML ساده، مهاجم می‌تواند کد مخرب خود را اجرا کند، کوکی‌ها را بدزدد، یا کاربر را به سایت جعلی هدایت کند. هرگز از innerHTML روی داده‌های کاربر بدون پاکسازی کامل استفاده نکنید.

مشکل بازسازی کامل درخت DOM

هنگامی که به innerHTML یک مقدار جدید نسبت می‌دهید، مرورگر تمام محتوای قبلی آن عنصر را حذف کرده و دوباره از صفر می‌سازد. این کار باعث می‌شود:

  • رویدادهای متصل شده به عناصر فرزند قبلی از بین برود.
  • تمرکز (focus) روی عناصر ورودی از دست برود.
  • اجرای مجدد اسکریپت‌های (غیرمستقیم) درون قطعه جدید.

اگر می‌خواهید فقط یک قطعه کوچک HTML را اضافه کنید و رویدادهای قبلی حفظ شوند، innerHTML گزینه مناسبی نیست.

آموزش React Native با Expo


راهکار مدرن و امن: DOMParser

برای تجزیه رشته‌های HTML و XML به صورت امن و کنترل شده، جاوااسکریپت شیء DOMParser را در اختیار شما قرار می‌دهد. این شیء هیچ کدی را اجرا نمی‌کند (حتی تگ‌های <script> را هم اجرا نمی‌کند) و فقط ساختار DOM را می‌سازد.

استفاده پایه از DOMParser برای HTML

javascript

const parser = new DOMParser();
const htmlString = '<div class="card"><h2>عنوان</h2><p>محتوا</p></div>';
const doc = parser.parseFromString(htmlString, 'text/html');
const newDiv = doc.body.firstChild; // عنصر div را می‌گیریم
document.body.appendChild(newDiv);

نکته مهم: خروجی parseFromString در حالت text/html یک سند کامل HTMLDocument است، نه فقط یک碎片. بنابراین برای استخراج عناصر، باید از body یا documentElement استفاده کنید.

تجزیه قطعات HTML بدون سند کامل

اگر فقط یک قطعه HTML (مثلاً چند عنصر بدون تگ body) دارید، باز هم DOMParser یک سند کامل برمی‌گرداند. برای راحت‌تر کار کردن، می‌توانید از خصوصیت children یا childNodes روی body استفاده کنید. روش دیگر، استفاده از تکنیک template است:

javascript

const template = document.createElement('template');
template.innerHTML = '<div>قطعه</div><div>دوم</div>';
const fragment = template.content; // DocumentFragment
document.body.appendChild(fragment);

این روش هم امن است (زیرا innerHTML روی template کدهای <script> را اجرا نمی‌کند) و هم بسیار سریع.

آموزش کامل برنامه نویسی پایتون از صفر تا صد

تجزیه XML با DOMParser

DOMParser برای XML هم به خوبی کار می‌کند. کافی است نوع MIME را به 'text/xml' یا 'application/xml' تغییر دهید:

javascript

const xmlString = `<bookstore><book><title>یادگیری DOM</title><author>علی رضایی</author></book></bookstore>`;
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');

const titles = xmlDoc.getElementsByTagName('title');
console.log(titles[0].textContent); // "یادگیری DOM"

اگر XML نامعتبر باشد، parseFromString یک سند با تگ <parsererror> برمی‌گرداند. پس همیشه بررسی کنید:

javascript

if (xmlDoc.querySelector('parsererror')) {
    console.error('خطا در تجزیه XML');
}

روش‌های جایگزین و بهینه‌تر برای سناریوهای خاص

هر کاری را نباید با DOMParser انجام داد. گاهی روش‌های ساده‌تر و کارآمدتر وجود دارند.

createElement و appendChild

اگر می‌خواهید یک ساختار HTML نسبتاً ساده بسازید و داده‌ها را از متغیرهای جاوااسکریپت تأمین می‌کنید، به جای تجزیه رشته، مستقیماً عناصر را با createElement بسازید:

javascript

const div = document.createElement('div');
div.className = 'card';
div.textContent = 'متن ساده';
// یا تنظیم innerHTML با کنترل کامل
div.innerHTML = '<span class="highlight">متن امن</span>';

این روش کاملاً امن است و سریع‌تر از تجزیه یک رشته بلند عمل می‌کند.

بهترین زبان برنامه نویسی برای شروع در ۲۰۲۵

insertAdjacentHTML

گاهی نیاز دارید یک قطعه HTML را به یک عنصر موجود اضافه کنید، بدون اینکه محتوای فعلی آن از بین برود. insertAdjacentHTML این کار را می‌کند و بر خلاف innerHTML، رویدادهای عناصر موجود را حذف نمی‌کند:

javascript

document.getElementById('list').insertAdjacentHTML('beforeend', '<li>مورد جدید</li>');

insertAdjacentHTML هم از نظر امنیتی مانند innerHTML رفتار می‌کند (اسکریپت‌ها اجرا می‌شوند) اما بازنویسی کل درخت را انجام نمی‌دهد. پس باز هم مراقب محتوای کاربر باشید.

Range و createContextualFragment

اگر نیاز به کنترل دقیق‌تری روی فرآیند درج دارید، می‌توانید از Range و createContextualFragment استفاده کنید:

javascript

const range = document.createRange();
const fragment = range.createContextualFragment('<p>متن جدید</p>');
document.body.appendChild(fragment);

این روش هم امنیت مشابه DOMParser را دارد (اسکریپت اجرا نمی‌شود) و هم مفید است.

Functional Programming در جاوااسکریپت

DOM Parsing در جاوااسکریپت

پیمایش و دستکاری DOM پس از تجزیه

بعد از اینکه یک رشته HTML را به DOM تبدیل کردید، نوبت به کار با آن می‌رسد. تکنیک‌های متداول عبارتند از:

  • جستجوی عناصرquerySelector و querySelectorAll روی سند یا قطعه تجزیه شده.
  • خواندن ویژگی‌هاgetAttribute، textContent، innerHTML.
  • تغییر عناصر: تغییر کلاس‌ها، ویژگی‌ها، یا بازسازی ساختار.
  • اضافه کردن به صفحهappendChild، insertBefore، replaceChild.

مثالی از یک سناریوی واقعی: فرض کنید از API یک مقاله دریافت کرده‌اید که محتوای آن به صورت HTML است. می‌خواهید فقط تصاویر داخل مقاله را استخراج کنید:

javascript

const parser = new DOMParser();
const doc = parser.parseFromString(articleHTML, 'text/html');
const images = doc.querySelectorAll('img');
images.forEach(img => {
    console.log(img.src);
    // شاید می‌خواهید عرض و ارتفاع را تنظیم کنید
    img.setAttribute('loading', 'lazy');
});
// سپس کل محتوا را به صفحه اضافه کنید
document.querySelector('.article-content').appendChild(doc.body);

عملکرد (Performance) و بهینه‌سازی

تجزیه رشته‌های HTML طولانی می‌تواند عملیات سنگینی باشد. نکات زیر به شما کمک می‌کند تا برنامه روان‌تری داشته باشید:

  • از DOMParser فقط یک بار استفاده کنید: اگر چندین رشته مجزا دارید، برای هر کدام یک نمونه DOMParser جدید نسازید. یک نمونه را نگه دارید و دوباره استفاده کنید.
  • برای قطعات خیلی بزرگ، از Web Worker استفاده کنید: عملیات DOMParser در ترد اصلی انجام می‌شود و می‌تواند UI را قفل کند. اگر رشته HTML شما چند مگابایت است، آن را به یک Worker بفرستید، آنجا تجزیه کنید و نتیجه را برگردانید.
  • از innerHTML برای قطعات خیلی کوچک استفاده کنید: اگر رشته HTML را خودتان ساخته‌اید و کاملاً امن است و حجم کمی دارد، innerHTML سریع‌ترین گزینه است.
  • فریم‌ورک‌های مجازی (مثل React) را نادیده نگیرید: در برنامه‌های بزرگ، استفاده از یک کتابخانه یا فریم‌ورک که DOM مجازی دارد، اغلب بهینه‌تر از دستکاری مستقیم و تجزیه مکرر است.

آموزش Node js برای مبتدیان


خطاهای رایج و نحوه عیب‌یابی آنها

تگ‌های ناقص یا نامعتبر

اگر رشته HTML شما حاوی تگ‌های باز نشده یا بسته نشده باشد، DOMParser سعی می‌کند با حدس خود آن را تصحیح کند. اما نتیجه همیشه قابل پیش‌بینی نیست. برای XML، در صورت خطا یک parsererror برگردانده می‌شود. بهترین راه اعتبارسنجی رشته قبل از تجزیه با یک اعتبارسنج HTML است.

فراموشی استفاده از body برای استخراج عناصر

این اشتباه رایج است:

javascript

const doc = parser.parseFromString('<div>test</div>', 'text/html');
console.log(doc.querySelector('div')); // null! چون div داخل body است

راه‌حل: doc.body.querySelector('div') یا doc.querySelector('body > div').

اجرای ناخواسته اسکریپت‌ها

اگر از innerHTML یا insertAdjacentHTML استفاده می‌کنید و نگران XSS هستید، راه حل ساده این است که محتوا را از طریق یک تابع مثل DOMPurify پاکسازی کنید. کتابخانه DOMPurify یک استاندارد صنعتی برای پالایش HTML است.

javascript

const clean = DOMPurify.sanitize(userHTML);
element.innerHTML = clean;

مشکلات حافظه

اگر بارها و بارها DOMParser را روی رشته‌های بزرگ اجرا کنید و نتیجه را به صفحه اضافه کنید، مرورگر ممکن است حافظه زیادی مصرف کند. حتماً ارجاع به عناصری که دیگر نیاز ندارید را با null مقداردهی کنید تا جمع‌آوری زباله (Garbage Collection) انجام شود.

تفاوت بین TypeScript و JavaScript


پارسینگ HTML در محیط‌های غیرمرورگر (Node.js)

در مرورگر که DOMParser وجود دارد، اما در Node.js به طور پیش‌فرض در دسترس نیست. برای تجزیه HTML در سرور، می‌توانید از کتابخانه‌هایی مثل jsdom استفاده کنید:

javascript

const { JSDOM } = require('jsdom');
const dom = new JSDOM('<div>متن</div>');
const element = dom.window.document.querySelector('div');

همچنین می‌توانید از parse5 (یک کتابخانه بسیار سازگار با استانداردهای HTML) یا cheerio (که شبیه jQuery عمل می‌کند) استفاده کنید. این ابزارها مخصوصاً برای خزیدن وب یا پردازش ایمیل‌های HTML کاربرد دارند.

فریمورک React چیست و چرا باید آن را یاد بگیریم


جمع‌بندی: کدام روش را کی استفاده کنیم؟

  • برای قطعات امن و ساده از کاربرcreateElement و textContent بهترین گزینه هستند.
  • برای قطعات HTML که از منبع قابل اعتماد می‌آید و نیازی به اجرای اسکریپت نیستDOMParser یا template و سپس درج.
  • برای اضافه کردن سریع قطعه به صفحه بدون حذف محتوای قبلیinsertAdjacentHTML (اما مراقب امنیت باشید).
  • برای تجزیه XML: فقط DOMParser با MIME مناسب.
  • برای داده‌های کاربر که حاوی HTML هستند: همیشه از یک کتابخانه پالایش مثل DOMPurify استفاده کنید، فرقی نمی‌کند از innerHTML استفاده کنید یا DOMParser.

در نهایت، به یاد داشته باشید که هر بار که شما یک رشته را به DOM تبدیل می‌کنید، در حال اجرای کد در محیط مرورگر کاربر هستید. با مسئولیت رفتار کنید و هرگز به ورودی‌های کاربر اعتماد نکنید.

جاوااسکریپت چیست و چه کاربردی دارد


سوالات متداول (FAQ)

۱. آیا استفاده از innerHTML همیشه بد است؟
خیر، اگر شما رشته HTML را خودتان ساخته‌اید و کاملاً کنترل می‌کنید (مثلاً '<div>' + text + '</div>' که text با textContent محافظت شده)، innerHTML سریع و بی‌خطر است. مشکل وقتی است که رشته از منبع خارجی (مثل کاربر یا API) می‌آید بدون پالایش.

۲. تفاوت بین DOMParser و createContextualFragment چیست؟
هر دو امن هستند (اسکریپت اجرا نمی‌کنند). DOMParser یک سند کامل برمی‌گرداند، در حالی که createContextualFragment یک DocumentFragment برمی‌گرداند. DocumentFragment سبک‌تر است و برای افزودن چندین عنصر به صفحه بهینه‌تر عمل می‌کند.

۳. آیا DOMParser می‌تواند HTML5 را با تگ‌های سفارشی (مثل <my-component>) تجزیه کند؟
بله. DOMParser از HTML5 پشتیبانی می‌کند و تگ‌های سفارشی را به عنوان HTMLElement معمولی در نظر می‌گیرد. فقط مطمئن شوید MIME صحیح (text/html) را استفاده کرده‌اید.

۴. چگونه می‌توانم یک رشته HTML را به یک شیء DOM بدون اضافه کردن به صفحه تبدیل کنم؟
با DOMParser و سپس استفاده از doc.body بدون appendChild. یا با document.createElement('template') و اختصاص به innerHTML و سپس template.content.

۵. آیا روشی برای معکوس کردن فرآیند (DOM به رشته HTML) وجود دارد؟
بله. می‌توانید از خاصیت outerHTML روی یک عنصر استفاده کنید: element.outerHTML رشته کامل شامل خود عنصر را برمی‌گرداند. برای کل سند از document.documentElement.outerHTML استفاده کنید.

۶. تجزیه XML با فضای نام (Namespace) با DOMParser چگونه است؟
DOMParser هنگام تجزیه XML، فضای نام را حفظ می‌کند. شما می‌توانید با getElementsByTagNameNS یا querySelector('[xmlns|tag]') به عناصر دسترسی پیدا کنید. همچنین می‌توانید از document.createNSResolver برای ارزیابی XPath استفاده کنید.

۷. بهترین روش برای استخراج متن خالص از یک رشته HTML (حذف تگ‌ها) چیست؟
می‌توانید از DOMParser استفاده کنید و سپس doc.body.textContent را بگیرید. یا در مرورگرهای جدید از element.innerText (که normalize هم می‌کند). برای Node.js، کتابخانه html-to-text توصیه می‌شود.

دیدگاهتان را بنویسید