Skip to content

يتيح لك HTMLRewriter استخدام محددات CSS لتحويل مستندات HTML. يعمل مع Request و Response وكذلك string. تنفيذ Bun مبني على lol-html من Cloudflare.


الاستخدام

حالة استخدام شائعة هي إعادة كتابة عناوين URL في محتوى HTML. إليك مثال يعيد كتابة مصادر الصور وعناوين URL للروابط لاستخدام نطاق CDN:

ts
// استبدال جميع الصور بـ rickroll
const rewriter = new HTMLRewriter().on("img", {
  element(img) {
    // صورة مصغرة لفيديو rickroll الشهير
    img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");

    // تغليف الصورة برابط للفيديو
    img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
      html: true,
    });
    img.after("</a>", { html: true });

    // إضافة نص alt ممتع
    img.setAttribute("alt", "Definitely not a rickroll");
  },
});

// مثال على مستند HTML
const html = `
<html>
<body>
  <img src="/cat.jpg">
  <img src="dog.png">
  <img src="https://example.com/bird.webp">
</body>
</html>
`;

const result = rewriter.transform(html);
console.log(result);

هذا يستبدل جميع الصور بصورة مصغرة لـ Rick Astley ويغلف كل <img> برابط، منتجًا diff مثل هذا:

html
<html>
  <body>
    <img src="/cat.jpg" /> 
    <img src="dog.png" /> 
    <img src="https://example.com/bird.webp" /> 
    <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> 
      <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> 
    </a> 
    <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> 
      <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> 
    </a> 
    <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> 
      <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> 
    </a> 
  </body>
</html>

الآن سيتم استبدال كل صورة في الصفحة بصورة مصغرة لـ Rick Astley، والنقر على أي صورة سيؤدي إلى فيديو شهير جدًا.

أنواع الإدخال

يمكن لـ HTMLRewriter تحويل HTML من مصادر مختلفة. يتم التعامل مع الإدخال تلقائيًا بناءً على نوعه:

ts
// من Response
rewriter.transform(new Response("<div>content</div>"));

// من string
rewriter.transform("<div>content</div>");

// من ArrayBuffer
rewriter.transform(new TextEncoder().encode("<div>content</div>").buffer);

// من Blob
rewriter.transform(new Blob(["<div>content</div>"]));

// من File
rewriter.transform(Bun.file("index.html"));

لاحظ أن تنفيذ Cloudflare Workers لـ HTMLRewriter يدعم فقط كائنات Response.

معالجات العناصر

تتيح لك طريقة on(selector, handlers) تسجيل معالجات لعناصر HTML التي تطابق محدد CSS. يتم استدعاء المعالجات لكل عنصر مطابق أثناء التحليل:

ts
rewriter.on("div.content", {
  // معالجة العناصر
  element(element) {
    element.setAttribute("class", "new-content");
    element.append("<p>New content</p>", { html: true });
  },
  // معالجة عقد النص
  text(text) {
    text.replace("new text");
  },
  // معالجة التعليقات
  comments(comment) {
    comment.remove();
  },
});

يمكن أن تكون المعالجات غير متزامنة وتُرجع Promise. لاحظ أن العمليات غير المتزامنة ستحجب التحويل حتى تكتمل:

ts
rewriter.on("div", {
  async element(element) {
    await Bun.sleep(1000);
    element.setInnerContent("<span>replace</span>", { html: true });
  },
});

دعم محددات CSS

تدعم طريقة on() مجموعة واسعة من محددات CSS:

ts
// محددات الوسوم
rewriter.on("p", handler);

// محددات الفئات
rewriter.on("p.red", handler);

// محددات المعرفات
rewriter.on("h1#header", handler);

// محددات السمات
rewriter.on("p[data-test]", handler); // يحتوي على سمة
rewriter.on('p[data-test="one"]', handler); // تطابق تام
rewriter.on('p[data-test="one" i]', handler); // غير حساس لحالة الأحرف
rewriter.on('p[data-test="one" s]', handler); // حساس لحالة الأحرف
rewriter.on('p[data-test~="two"]', handler); // تطابق كلمة
rewriter.on('p[data-test^="a"]', handler); // يبدأ بـ
rewriter.on('p[data-test$="1"]', handler); // ينتهي بـ
rewriter.on('p[data-test*="b"]', handler); // يحتوي على
rewriter.on('p[data-test|="a"]', handler); // مفصول بشرطة

// المجمعات
rewriter.on("div span", handler); // سليل
rewriter.on("div > span", handler); // ابن مباشر

// الفئات الزائفة
rewriter.on("p:nth-child(2)", handler);
rewriter.on("p:first-child", handler);
rewriter.on("p:nth-of-type(2)", handler);
rewriter.on("p:first-of-type", handler);
rewriter.on("p:not(:first-child)", handler);

// المحدد العام
rewriter.on("*", handler);

عمليات العناصر

توفر العناصر طرقًا مختلفة للتلاعب. جميع طرق التعديل تُرجع مثيل العنصر للسلسلة:

ts
rewriter.on("div", {
  element(el) {
    // السمات
    el.setAttribute("class", "new-class").setAttribute("data-id", "123");

    const classAttr = el.getAttribute("class"); // "new-class"
    const hasId = el.hasAttribute("id"); // boolean
    el.removeAttribute("class");

    // التلاعب بالمحتوى
    el.setInnerContent("New content"); // يهرب HTML افتراضيًا
    el.setInnerContent("<p>HTML content</p>", { html: true }); // يحلل HTML
    el.setInnerContent(""); // مسح المحتوى

    // التلاعب بالموضع
    el.before("Content before").after("Content after").prepend("First child").append("Last child");

    // إدراج محتوى HTML
    el.before("<span>before</span>", { html: true })
      .after("<span>after</span>", { html: true })
      .prepend("<span>first</span>", { html: true })
      .append("<span>last</span>", { html: true });

    // الإزالة
    el.remove(); // إزالة العنصر والمحتوى
    el.removeAndKeepContent(); // إزالة وسوم العنصر فقط

    // الخصائص
    console.log(el.tagName); // اسم الوسم بأحرف صغيرة
    console.log(el.namespaceURI); // URI مساحة اسم العنصر
    console.log(el.selfClosing); // ما إذا كان العنصر مغلقًا ذاتيًا (مثل <div />)
    console.log(el.canHaveContent); // ما إذا كان العنصر يمكن أن يحتوي على محتوى (false للعناصر الفارغة مثل <br>)
    console.log(el.removed); // ما إذا تمت إزالة العنصر

    // تكرار السمات
    for (const [name, value] of el.attributes) {
      console.log(name, value);
    }

    // معالجة وسم النهاية
    el.onEndTag(endTag => {
      endTag.before("Before end tag");
      endTag.after("After end tag");
      endTag.remove(); // إزالة وسم النهاية
      console.log(endTag.name); // اسم الوسم بأحرف صغيرة
    });
  },
});

عمليات النص

توفر معالجات النص طرقًا للتلاعب بالنص. تمثل كتل النص أجزاء من محتوى النص وتوفر معلومات عن موضعها في عقدة النص:

ts
rewriter.on("p", {
  text(text) {
    // المحتوى
    console.log(text.text); // محتوى النص
    console.log(text.lastInTextNode); // ما إذا كانت هذه آخر كتلة
    console.log(text.removed); // ما إذا تمت إزالة النص

    // التلاعب
    text.before("Before text").after("After text").replace("New text").remove();

    // إدراج محتوى HTML
    text
      .before("<span>before</span>", { html: true })
      .after("<span>after</span>", { html: true })
      .replace("<span>replace</span>", { html: true });
  },
});

عمليات التعليقات

تسمح معالجات التعليقات بالتلاعب بالتعليقات بطرق مشابهة لعقد النص:

ts
rewriter.on("*", {
  comments(comment) {
    // المحتوى
    console.log(comment.text); // نص التعليق
    comment.text = "New comment text"; // تعيين نص التعليق
    console.log(comment.removed); // ما إذا تمت إزالة التعليق

    // التلاعب
    comment.before("Before comment").after("After comment").replace("New comment").remove();

    // إدراج محتوى HTML
    comment
      .before("<span>before</span>", { html: true })
      .after("<span>after</span>", { html: true })
      .replace("<span>replace</span>", { html: true });
  },
});

معالجات المستند

تتيح لك طريقة onDocument(handlers) معالجة أحداث مستوى المستند. يتم استدعاء هذه المعالجات للأحداث التي تحدث على مستوى المستند بدلاً من داخل عناصر محددة:

ts
rewriter.onDocument({
  // معالجة doctype
  doctype(doctype) {
    console.log(doctype.name); // "html"
    console.log(doctype.publicId); // المعرف العام إذا وجد
    console.log(doctype.systemId); // معرف النظام إذا وجد
  },
  // معالجة عقد النص
  text(text) {
    console.log(text.text);
  },
  // معالجة التعليقات
  comments(comment) {
    console.log(comment.text);
  },
  // معالجة نهاية المستند
  end(end) {
    end.append("<!-- Footer -->", { html: true });
  },
});

معالجة Response

عند تحويل Response:

  • يتم الحفاظ على رمز الحالة والرؤوس وخصائص response الأخرى
  • يتم تحويل الجسم مع الحفاظ على إمكانيات البث
  • يتم التعامل مع ترميز المحتوى (مثل gzip) تلقائيًا
  • يتم وضع علامة على جسم response الأصلي كمستخدم بعد التحويل
  • يتم استنساخ الرؤوس إلى response الجديد

معالجة الأخطاء

يمكن أن تطرح عمليات HTMLRewriter أخطاء في عدة حالات:

  • بناء جملة محدد غير صالح في طريقة on()
  • محتوى HTML غير صالح في طرق التحويل
  • أخطاء البث عند معالجة أجسام Response
  • فشل تخصيص الذاكرة
  • أنواع إدخال غير صالحة (مثل تمرير Symbol)
  • أخطاء الجسم المستخدم بالفعل

يجب التقاط الأخطاء والتعامل معها بشكل مناسب:

ts
try {
  const result = rewriter.transform(input);
  // معالجة النتيجة
} catch (error) {
  console.error("خطأ HTMLRewriter:", error);
}

انظر أيضًا

يمكنك أيضًا قراءة توثيق Cloudflare الذي تهدف واجهة برمجة التطبيقات هذه إلى التوافق معه.

Bun بواسطة www.bunjs.com.cn تحرير