從XML讀懂智慧設備設定與地方導覽資料
從樹狀標記中找出水井三寶多語內容與設備參數,同時守住格式、來源及操作安全。
完成本篇後,你能辨認XML元素、屬性、文字與階層,使用
ElementTree 解析、搜尋、建立及輸出XML,處理命名空間與解析錯誤,並把設備設定轉成「待人工確認」的安全資料。一、XML與JSON有何不同?
兩者都能表示結構化資料。JSON常見於網頁API;XML在設備設定、標準文件、RSS及舊系統交換格式中仍很常見。
| 特性 | JSON | XML |
|---|---|---|
| 主要結構 | 物件、陣列、基本型別 | 元素樹、屬性、文字 |
| 資料型別 | 數字、布林、null等 | 讀出時多半先是字串 |
| 重複節點 | 通常使用陣列 | 重複同名元素 |
| 命名空間 | 沒有內建機制 | 可避免不同詞彙名稱衝突 |
二、看懂XML結構
<guide version="1.0">
<item id="white-horse" language="zh-Hant">
<title>白馬</title>
<status>待社區確認</status>
</item>
<item id="turtle" language="zh-Hant">
<title>烏龜</title>
<status>待社區確認</status>
</item>
</guide>guide 是根元素;item 是子元素;id 與 language 是屬性;title 元素內的「白馬」是文字內容。XML必須只有一個根元素,開始與結束標籤要正確巢狀。
三、從字串解析XML
import xml.etree.ElementTree as ET
xml_text = """
<guide version="1.0">
<item id="marriage-flower" language="zh-Hant">
<title>姻緣花</title>
<status>待社區確認</status>
</item>
</guide>
"""
root = ET.fromstring(xml_text)
print("根元素:", root.tag)
print("版本:", root.get("version"))fromstring() 回傳根元素。XML屬性可用 get() 取得,找不到時回傳 None。
四、搜尋元素與讀取文字
for item in root.findall("item"):
item_id = item.get("id")
language = item.get("language")
title = item.findtext("title", default="").strip()
status = item.findtext("status", default="").strip()
print(item_id, language, title, status)| 方法 | 用途 |
|---|---|
find() | 尋找第一個符合的子元素 |
findall() | 取得所有符合元素 |
findtext() | 直接取得第一個符合元素的文字 |
iter() | 走訪目前元素下所有指定節點 |
五、從檔案安全解析
import xml.etree.ElementTree as ET
from pathlib import Path
def load_xml(path, max_bytes=2_000_000):
path = Path(path)
if not path.is_file():
raise FileNotFoundError(f"找不到XML:{path}")
if path.stat().st_size > max_bytes:
raise ValueError("XML超過教學程式允許大小")
try:
tree = ET.parse(path)
except ET.ParseError as error:
raise ValueError(f"XML格式錯誤:{error}") from error
return tree檔案大小限制可降低意外資源消耗,但不是完整的XML安全防護。若要解析來源不可信的XML,應採用專為防禦XML攻擊設計的函式庫(如defusedxml)及環境資源限制。
六、把導覽元素轉成字典
def parse_guide_item(item):
item_id = (item.get("id") or "").strip()
language = (item.get("language") or "").strip()
title = item.findtext("title", default="").strip()
status = item.findtext("status", default="").strip()
missing = [
name
for name, value in {
"id": item_id,
"language": language,
"title": title,
"status": status,
}.items()
if not value
]
if missing:
raise ValueError("缺少內容:" + "、".join(missing))
return {
"id": item_id,
"language": language,
"title": title,
"status": status,
}XML沒有自動幫你驗證業務規則。讀取後仍須確認必要屬性、空白文字、重複ID及可接受的語言代碼。
七、批次處理並隔離錯誤
def parse_guide(root):
records = []
errors = []
used_ids = set()
for index, item in enumerate(root.findall("item"), start=1):
try:
record = parse_guide_item(item)
if record["id"] in used_ids:
raise ValueError(f"重複id:{record['id']}")
used_ids.add(record["id"])
records.append(record)
except ValueError as error:
errors.append({"序號": index, "原因": str(error)})
return records, errors八、解析智慧設備設定
device_xml = """
<device-config version="1.0" apply="preview-only">
<device id="TEMP-01" type="temperature">
<enabled>true</enabled>
<sample-seconds>60</sample-seconds>
</device>
</device-config>
"""
config_root = ET.fromstring(device_xml)
device = config_root.find("device")
device_id = device.get("id")
device_type = device.get("type")
enabled_text = device.findtext("enabled", default="").strip().lower()
sample_seconds = int(
device.findtext("sample-seconds", default="")
)
print(device_id, device_type, enabled_text, sample_seconds)XML文字須自行轉成 int 或布林值。不要使用 bool("false"),因為非空字串會得到 True。
九、嚴格轉換布林與整數
def parse_boolean(text, field_name):
normalized = str(text).strip().lower()
if normalized == "true":
return True
if normalized == "false":
return False
raise ValueError(f"{field_name}只能是true或false")
def parse_positive_int(text, field_name, maximum):
value = int(str(text).strip())
if not 1 <= value <= maximum:
raise ValueError(
f"{field_name}必須介於1到{maximum}"
)
return value
enabled = parse_boolean(enabled_text, "enabled")
interval = parse_positive_int(
sample_seconds, "sample-seconds", 86400
)
print(enabled, interval)十、命名空間不是多餘的括號
XML命名空間避免不同標準使用相同標籤時衝突。ElementTree搜尋時要提供前綴對照。
import xml.etree.ElementTree as ET
xml_text = """
<g:guide xmlns:g="https://example.edu/guide">
<g:item id="white-horse">
<g:title>白馬</g:title>
</g:item>
</g:guide>
"""
root = ET.fromstring(xml_text)
ns = {"g": "https://example.edu/guide"}
for item in root.findall("g:item", ns):
title = item.findtext("g:title", default="", namespaces=ns)
print(item.get("id"), title)範例網址只是命名空間識別字,不代表瀏覽器一定能開啟該網址。
十一、建立XML導覽檔
import xml.etree.ElementTree as ET
from pathlib import Path
def build_guide_xml(records):
root = ET.Element("guide", {"version": "1.0"})
for record in records:
item = ET.SubElement(
root,
"item",
{
"id": record["id"],
"language": record["language"],
},
)
ET.SubElement(item, "title").text = record["title"]
ET.SubElement(item, "status").text = record["status"]
tree = ET.ElementTree(root)
ET.indent(tree, space=" ")
return tree
records = [
{"id": "white-horse", "language": "zh-Hant",
"title": "白馬", "status": "待社區確認"},
]
tree = build_guide_xml(records)由ElementTree建立元素時,特殊字元會被正確跳脫;不要用字串拼接XML,否則文字中的 &、< 等符號容易破壞格式。
十二、不覆蓋地輸出XML
from pathlib import Path
def write_xml_exclusive(tree, path):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
xml_bytes = ET.tostring(
tree.getroot(),
encoding="utf-8",
xml_declaration=True,
)
with path.open("xb") as file:
file.write(xml_bytes)
return path
# write_xml_exclusive(tree, "導覽資料/水井三寶_v1.xml")十三、設定變更比較
def compare_device_config(old_config, new_config):
changes = {}
keys = sorted(old_config.keys() | new_config.keys())
for key in keys:
old_value = old_config.get(key)
new_value = new_config.get(key)
if old_value != new_value:
changes[key] = {
"舊值": old_value,
"新值": new_value,
}
return changes變更報告應先交由設備負責人確認,再經測試環境驗證;不可由XML解析程式直接套用到正式場域。
十四、常見錯誤檢查表
| 現象 | 原因 | 修正 |
|---|---|---|
| ParseError | 標籤未關閉或巢狀錯誤 | 檢查錯誤位置與原始來源 |
| find找不到元素 | 路徑錯誤或有命名空間 | 確認階層並提供ns對照 |
| false被轉成True | 使用bool("false") | 明確比對true/false |
| 中文輸出成亂碼 | 編碼不一致 | 輸出UTF-8並寫入XML宣告 |
| 設定檔直接控制設備 | 缺少驗證與核准層 | 只產生預覽與變更報告 |
十五、USR實作挑戰
為白馬、烏龜與姻緣花建立華語、台語文字節點,加入確認狀態與授權欄位,找出缺漏及重複ID。
解析三個匿名設備設定,比較新舊版本,輸出變更報告,但不連接或操作任何真實設備。
準備缺標籤、缺屬性、錯誤布林、過大整數與命名空間版本,確認程式能提供清楚錯誤。
十六、與生成式AI協作
請擔任Python XML與智慧設備設定安全助教。
請檢查我的ElementTree程式:
1. 驗證根元素、版本、必要屬性與文字;
2. 數字與布林必須嚴格轉型;
3. 支援XML命名空間;
4. 不以字串拼接XML;
5. 輸出UTF-8且不得覆蓋舊檔;
6. 不可信XML須限制大小並說明安全解析方案;
7. 設定只能產生預覽和變更報告,不可直接控制設備。
請提供正常與故障測試資料。XML是一棵帶有元素、屬性與文字的樹。解析只是第一步;真正可靠的流程還要驗證結構、型別、命名空間、來源與版本,設備設定更必須經人工核准。
沒有留言:
張貼留言