مُصغِّر XML
ضغط XML بإزالة المسافات والتعليقات
إدخال XML
XML المضغوط
ما هو ضغط XML؟
ضغط XML هو عملية حذف جميع الأحرف غير الضرورية من مستند XML دون تغيير معناه. يُزيل مُصغِّر XML المسافات البيضاء بين الوسوم، ويحذف التعليقات، ويلغي فواصل الأسطر، ويطوي المسافات البادئة لينتج مخرجًا مضغوطًا في سطر واحد. والنتيجة سلسلة XML يُحلّلها أي محلل بصورة مطابقة للنسخة الأصلية المنسَّقة، منتجًا نموذج بيانات واحدًا.
تحدد مواصفة XML 1.0 (توصية W3C، الطبعة الخامسة) قواعد التعامل مع المسافات البيضاء في القسم 2.10. المسافات البيضاء بين الوسوم التي لا قيمة دلالية لها تُسمى "المسافة البيضاء غير ذات الدلالة"، ويحق لمعالجات XML تجاهلها. أما المسافة البيضاء داخل محتوى النص فهي ذات دلالة بالافتراضي ما لم يُعلن العنصر الأب xml:space="default". يُميّز المضغوط الصحيح بين الحالتين ولا يحذف إلا ما يأمن حذفه.
يختلف الضغط عن الترميز المضغوط (compression). يقلل كلٌّ من Gzip وBrotli الحجم على طبقة النقل ويستلزمان فك الضغط قبل التحليل. أما ضغط XML فيقلل حجم المستند الخام نفسه، فيبقى XML صالحًا ومقروءًا لأي محلل دون خطوة فك ضغط. عمليًا، ضغط XML أولًا ثم تطبيق Gzip يُعطي أفضل النتائج: تُزال الأحرف الزائدة أولًا، ثم تعمل خوارزمية الضغط على مدخل أكثر إحكامًا.
<?xml version="1.0" encoding="UTF-8"?><catalog><product id="p101"><name>Widget A</name><price currency="USD">29.99</price><stock>142</stock></product><product id="p102"><name>Widget B</name><price currency="EUR">19.50</price><stock>87</stock></product></catalog>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Product catalog for Q1 2026 -->
<catalog>
<product id="p101">
<name>Widget A</name>
<price currency="USD">29.99</price>
<!-- Temporarily discounted -->
<stock>142</stock>
</product>
<product id="p102">
<name>Widget B</name>
<price currency="EUR">19.50</price>
<stock>87</stock>
</product>
</catalog>لماذا تستخدم مُصغِّر XML؟
XML المنسَّق بمسافات بادئة وتعليقات مثالي للتطوير ومراجعة الكود. أما للتخزين والإرسال والاستهلاك الآلي، فذلك التنسيق الإضافي يُضيف بايتات دون أي فائدة. يسد مضغوط XML هذه الفجوة.
حالات استخدام مضغوط XML
ما الذي يحذفه ضغط XML
ليس كل شيء في مستند XML يمكن حذفه بأمان. يوضح هذا الجدول المرجعي كل نوع من المحتوى القابل للحذف، وما إذا كان حذفه آمنًا دائمًا أم مشروطًا بحالة الاستخدام.
| العنصر | مثال | مستوى الأمان |
|---|---|---|
| Indentation | Spaces/tabs before tags | Always safe to remove |
| Line breaks | \n and \r\n between tags | Always safe to remove |
| Comments | <!-- ... --> | Safe unless parsed by app |
| XML declaration | <?xml version="1.0"?> | Keep if encoding is non-UTF-8 |
| Processing instructions | <?xml-stylesheet ...?> | Keep if consumed downstream |
| Trailing whitespace | Spaces after closing tags | Always safe to remove |
| Text node whitespace | Spaces inside text content | Remove only between tags, not within |
ضغط XML مقابل Gzip مقابل الصيغ الثنائية
كلٌّ من الضغط والترميز المضغوط والترميز الثنائي يستهدف طبقة مختلفة من مشكلة الحجم. يحافظ الضغط على المخرجات بوصفها XML صالحًا مقروءًا للإنسان. يُقلص الضغط عبر HTTP (Gzip وBrotli) أكثر لكنه يستلزم خطوة فك ضغط قبل التحليل. تذهب الصيغ الثنائية إلى أبعد مدى، لكن طرفَي الاتصال يحتاجان إلى مشفّر/مفكّك متوافق — وهو أمر عملي أساسًا للأنظمة المدمجة أو الخدمات المؤسسية الثقيلة على WSDL.
أمثلة على الكود
ضغط XML برمجيًا يتبع النمط ذاته في كل لغة: حلّل المستند إلى شجرة، أزل اختياريًا عقد التعليقات، ثم أعد التسلسل دون مسافات بادئة.
// Minify XML by parsing and re-serializing (strips formatting)
const raw = `<root>
<item id="1">
<!-- note -->
<name>Test</name>
</item>
</root>`
const parser = new DOMParser()
const doc = parser.parseFromString(raw, 'application/xml')
// Remove comment nodes
const walker = doc.createTreeWalker(doc, NodeFilter.SHOW_COMMENT)
const comments = []
while (walker.nextNode()) comments.push(walker.currentNode)
comments.forEach(c => c.parentNode.removeChild(c))
const minified = new XMLSerializer().serializeToString(doc)
// → "<root><item id=\"1\"><name>Test</name></item></root>"from lxml import etree
xml = """<root>
<item id="1">
<!-- note -->
<name>Test</name>
</item>
</root>"""
tree = etree.fromstring(xml.encode())
# Remove comments
for comment in tree.iter(etree.Comment):
comment.getparent().remove(comment)
# Serialize without pretty-print (minified)
result = etree.tostring(tree, xml_declaration=False).decode()
# → '<root><item id="1"><name>Test</name></item></root>'
# With xml.etree (stdlib, no lxml needed)
import xml.etree.ElementTree as ET
root = ET.fromstring(xml)
ET.indent(root, space='') # Python 3.9+
print(ET.tostring(root, encoding='unicode'))package main
import (
"encoding/xml"
"fmt"
"strings"
)
func minifyXML(input string) (string, error) {
decoder := xml.NewDecoder(strings.NewReader(input))
var out strings.Builder
encoder := xml.NewEncoder(&out)
// No indentation = minified output
for {
tok, err := decoder.Token()
if err != nil {
break
}
// Skip comments
if _, ok := tok.(xml.Comment); ok {
continue
}
// Skip whitespace-only char data
if cd, ok := tok.(xml.CharData); ok {
if strings.TrimSpace(string(cd)) == "" {
continue
}
}
encoder.EncodeToken(tok)
}
encoder.Flush()
return out.String(), nil
}
// minifyXML("<a>\n <b>1</b>\n</a>") → "<a><b>1</b></a>"# Minify XML with xmllint (part of libxml2) xmllint --noblanks input.xml > minified.xml # Minify from stdin echo '<root> <item>hello</item> </root>' | xmllint --noblanks - # → <?xml version="1.0"?><root><item>hello</item></root> # Strip comments too (combine with sed or xmlstarlet) xmlstarlet ed -d '//comment()' input.xml | xmllint --noblanks - # Check size reduction echo "Before: $(wc -c < input.xml) bytes" echo "After: $(xmllint --noblanks input.xml | wc -c) bytes"