# XML 格式化 — 排開、壓平，或者轉成 JSON

排開了好讀的 XML，壓平了好發的 XML，還有兩個方向的 JSON 轉換。這些都不會貼進別人的伺服器。

> 格式化、縮排和壓縮 XML，把 XML 轉成 JSON 或者把 JSON 轉回 XML。解析器在你的瀏覽器裡跑，什麼都不上傳，所以一個訂閱源、一張發票或者一份配置檔案永遠不會離開你的裝置。

本頁是一個完全在你的瀏覽器裡執行的互動式工具，地址是 https://abox.tools/zh-TW/xml-formatter/。你交給它的任何內容都不會被上傳。下面是這個頁面用文字對該工具的全部說明；要使用它，請開啟這個地址。

## 你的 XML 和 JSON **不會上傳**。這裡根本沒有伺服器。

格式化和轉換都是對一段字串做算術，就在這裡，在這個頁面上。解析器是手寫的，在 `src/shared/parse-xml.js` 裡，除此之外再沒有別的。這個工具壓根就沒有網路功能，沒有要取的，也沒有要發的。這一點在這裡比「XML」這個詞聽起來更重要：以這個格式送過來的，多半是一張發票、一份銀行流水、一份病歷，或者一個頭裡帶著某人憑據的 SOAP 請求。

- ✗ 不上傳
- ✗ 不註冊
- ✗ 無大小限制
- ✓ 斷網可用
- ✓ 開源

## 不上傳就把 XML 格式化的步驟

1. **挑一件事做。** 兩個分頁，一個輸入框：*格式化*把 XML 排開或者壓平，*轉換*把它變成 JSON，或者把 JSON 變回 XML。你剛排開的那份 XML 就是你要轉換的那份，不用貼兩遍。
2. **貼進來，或者把檔案放上去。** 能選中能複製的都行。把 `.xml`、`.svg`、`.rss` 或 `.xsd` 檔案放到選擇框上，你自己的瀏覽器會讀它並填進輸入框——這裡沒有上傳這一步可以省略。
3. **選縮排，或者壓平。** 兩個空格、四個空格，或者一個製表符。壓平之後還是同一份文件，只是去掉了每一個只為好看而存在的空格，結果裡會說這省了多少位元組。
4. **在出錯的地方讀錯誤。** 在這裡失敗的解析器會告訴你*哪個標籤*沒閉合，在第幾行第幾列，而不是「第 1 行出錯」——瀏覽器一口氣讀完一份文件之後說的就是那句。
5. **把結果拿走。** 複製，或者下載成檔案，檔名按它出來的格式取。

## 工具箱裡還有

- [文字比對](https://abox.tools/zh-TW/text-diff/): 兩段文字進來，每處差異都被標出來，逐行、逐詞。沒有一個字被貼進別人的伺服器。
- [Base64 編碼解碼](https://abox.tools/zh-TW/base64/): Base64、百分號編碼、HTML 實體、十六進位和反斜槓轉義，雙向都行。沒有一個字被貼進別人的伺服器。
- [文字與檔案分享](https://abox.tools/zh-TW/share-text/): 分享只活在這個開著的分頁裡。讀的人從你的瀏覽器直接取得加密內容，關掉分頁就結束——任何伺服器上都不留一點東西。
- [QR Code 與條碼產生](https://abox.tools/zh-TW/qr-barcode/): 打進去，就變成一個碼。產生過程不往外送任何東西。

## 常見問題

### 我的 XML 會被上傳到什麼地方嗎？

不會。這個頁面上的解析器和列印器都是在你自己的瀏覽器裡、你自己的硬體上跑的函式。這個工具壓根就沒有網路功能，從不取任何東西，也從不發任何東西，頁面的 `Content-Security-Policy` 裡寫著它允許聯絡的每一個地址，沒有一個屬於本站。這一點對 XML 來說比這個格式的名聲聽起來更要緊：以它送過來的多半是一張發票、一份銀行流水、一份病歷，或者一個頭裡帶著憑據的 SOAP 請求。

### 它會解析外部實體嗎？

不會，也沒有什麼可關的。解析外部實體，是把 XML 解析器誆去讀執行它的那臺機器上的檔案的辦法，也就是通常寫作 XXE 的那個攻擊，而 `src/shared/parse-xml.js` 是手寫的讀取器，裡面壓根沒有實體解析。你的文字也從不交給瀏覽器內建的 `DOMParser`。`DOCTYPE` 會原樣帶過去，但永遠不會被執行。

### 把 XML 轉成 JSON 會丟什麼？

混合內容的順序、註釋，還有屬性和子元素之間的區別——最後這條是被淡化而不是抹掉，因為屬性會變成一個名字以 `@` 開頭的成員。元素自己的文字在必須跟別的東西並排時會變成 `#text`，重複的子元素會變成陣列。每個值都還是字串：XML 沒有類型，判定 `8080` 是個數字，等於憑空造資訊。

### 把 JSON 轉成 XML 會丟什麼？

空物件、空陣列和空字串之間的區別，三者都會變成一個空元素；還有每個值的類型，因為 XML 沒有類型。陣列會變成重複的元素，那是唯一能讀回來的形狀；元素名承載不了的鍵，會把不方便的字元替換掉，而不是吐出一份沒有解析器肯讀的文件。

### 它能格式化 SVG、RSS 訂閱源或者 POM 檔案嗎？

能。三者都是 XML，而這裡讀的是 XML 本身，不是它的某種方言。這樣排開的 SVG 更好手改；RSS 或 Atom 訂閱源通常是壓平了發出來的，不開啟就沒法讀。怎麼排版，一點也不改變這份文件的意思。

### 重新縮排會改變 XML 的意思嗎？

對於元素裡裝著元素的文件，不會。可能有影響的是文字：裝著詞的元素內部的空白就是那段文字的一部分，所以只裝文字的元素會留在一行上，而不是被排開。`CDATA` 段會原封不動地抄過去。

### 為什麼不直接用瀏覽器內建的 XML 解析器？

因為文件壞掉時它說的話。`DOMParser` 交回來的是一份錯誤文件，措辭在每個瀏覽器裡都不一樣，而且常常就歸結成「第 1 行出錯」。手寫的讀取器能說出哪個標籤沒閉合、它是在哪裡開啟的，而這正是你真正需要知道的。不解析外部實體是另一個理由。

### 檔案最大能有多大？

這裡沒有設限，因為沒有伺服器在為此付錢。真正的上限是你自己的裝置：幾 MB 的 XML 沒問題，文件很長的時候，頁面會等你打字停下來再重新格式化，而不是跟你搶鍵盤。

### 免費嗎？需要註冊嗎？

免費，沒有帳號，不用登入，沒有試用期，也不限制你貼多少。本站有廣告，網站靠它維持；廣告拿不到關於你文字的任何東西。

### 斷網還能用嗎？

能。頁面載入一次之後拔掉網線，它照樣工作。這也是證明沒有上傳的最簡單辦法：一個把你的 XML 送出去格式化的工具，在你拔線的那一刻就停了。

## 這條隱私承諾怎麼核實

- **你貼進來的東西沒有出去的路。** Content-Security-Policy 裡寫著這個頁面允許聯絡的每一個地址，沒有一個屬於本站。這裡沒有任何可以收走一張貼進來的發票的接收端，就算有，程式碼裡也沒有把它送過去的東西。
- **這裡不會去取任何東西。** `src/` 裡哪裡都沒有 `fetch`，沒有 `XMLHttpRequest`，也沒有 `sendBeacon`。解析器和列印器都是這個頁面裡的函式：進去一段字串，出來一段字串。
- **外部實體永遠不會被解析。** 帶外部實體的 `DOCTYPE`，是把 XML 解析器誆去讀解析它的那臺機器上的檔案的辦法，也是這個格式最老的一個洞。`src/shared/parse-xml.js` 是手寫的讀取器，裡面壓根就沒有實體解析這回事——不是關掉了，是根本沒有——而且這個頁面也從不把你的文字交給瀏覽器內建的 `DOMParser`。
- **從 XML 出來的每個值都是字串。** `<port>8080</port>` 並沒有說那是不是一個數字，所以 JSON 寫的是 `"8080"`。替你決定，就是憑空造出一條資訊，然後它還會一路當作檔案裡本來就有的東西傳下去。
- **Google 載入了什麼，又沒拿到什麼。** 廣告和統計指令碼來自 Google，捐贈按鈕來自 Buy Me a Coffee。它們誰也沒拿到你文字裡的一個字元。讀它、解析它、寫它的每一行程式碼，都從這個源提供，也都列在儲存庫裡。
- **斷網可用。** 拔掉網線，這個工具一點變化都沒有，因為它裡面本來就沒有聯網這一步。這是最簡單的證明。

**你可以自己核對。**上面這些不用你信。這個頁面是儲存庫裡的模板和配置產生出來的，構建指令碼你能讀，也能自己跑一遍，跑出來的結果就提交在 `dist` 分支上。拿實際發給你的內容，跟你自己從原始碼構建出來的結果對一遍就知道了： https://github.com/A-Box-of-Tools/website

最值得先讀的幾個檔案：`config/site.toml` 裡是 Content-Security-Policy，告訴你哪個標籤沒閉合的解析器在 `src/shared/parse-xml.js`，XML 裡每個值為什麼都按字串出來、而不是靠猜的，在 `src/convert.js`。
