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 تحرير