การทำให้ XML เป็นมาตรฐานมักใช้เมื่อมีความจำเป็นต้องสร้างลายเซ็นดิจิทัลเพื่อส่งให้คู่ค้าตรวจสอบ เนื่องจากลายเซ็นดิจิทัลสร้างขึ้นจากข้อมูล XML ข้อมูล XML จึงต้องถูกทำให้เป็นมาตรฐานก่อนจึงจะสามารถคำนวณค่าลายเซ็นได้ แม้แต่ช่องว่างพิเศษก็อาจส่งผลต่อค่าลายเซ็นที่คำนวณได้ ดังนั้นจึงต้องปฏิบัติตามกฎบางอย่างเพื่อทำให้ข้อมูล XML เป็นมาตรฐานเพื่อให้มีรูปแบบมาตรฐาน นี่คือเหตุผลที่ W3C สร้างข้อกำหนด Canonical XML Version 1.1
ข้อกำหนดนี้มีกฎในการจัดรูปแบบโหนดองค์ประกอบ โหนดแอตทริบิวต์ และโหนดเนมสเปซ ฯลฯ ภาษาโปรแกรมต่างๆ ได้นำข้อกำหนดนี้ไปใช้เพื่อให้เราสามารถใช้เพื่อทำให้ข้อมูล XML เป็นมาตรฐานได้อย่างง่ายดายโดยไม่ต้องทราบรายละเอียดเกี่ยวกับข้อกำหนด
ในบทช่วยสอนนี้ เราจะไม่ลงรายละเอียดในข้อกำหนด เราจะเน้นเฉพาะวิธีการเรียก Java API เพื่อทำให้ข้อมูล XML เป็นมาตรฐานเท่านั้น ตาม XPath data model เอกสาร XML แสดงด้วยชุดของโหนด -- ชุดโหนด ในขณะที่ XML API ใน Java รับออบเจ็กต์ Data ที่จะทำให้เป็นมาตรฐาน เพื่อสาธิตการทำให้เป็นมาตรฐาน เราจะสร้าง NodeSetDataImpl ที่กำหนดเองซึ่งใช้ NodeSetData และอินเทอร์เฟซ Iterator
import java.util.Iterator; import javax.xml.crypto.NodeSetData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.traversal.DocumentTraversal; import org.w3c.dom.traversal.NodeFilter; import org.w3c.dom.traversal.NodeIterator; public class NodeSetDataImpl implements NodeSetData, Iterator { private Node ivNode; private NodeFilter ivNodeFilter; private Document ivDocument; private DocumentTraversal ivDocumentTraversal; private NodeIterator ivNodeIterator; private Node ivNextNode; public NodeSetDataImpl(Node pNode, NodeFilter pNodeFilter) throws Exception { ivNode = pNode; ivNodeFilter = pNodeFilter; if (ivNode instanceof Document) { ivDocument = (Document) ivNode; } else { ivDocument = ivNode.getOwnerDocument(); } ivDocumentTraversal = (DocumentTraversal) ivDocument; } private NodeSetDataImpl(NodeIterator pNodeIterator) { ivNodeIterator = pNodeIterator; } public Iterator iterator() { NodeIterator nodeIterator = ivDocumentTraversal.createNodeIterator(ivNode, NodeFilter.SHOW_ALL, ivNodeFilter, false); return new NodeSetDataImpl(nodeIterator); } private Node checkNextNode() { if (ivNextNode == null && ivNodeIterator != null) { ivNextNode = ivNodeIterator.nextNode(); if (ivNextNode == null) { ivNodeIterator.detach(); ivNodeIterator = null; } } return ivNextNode; } private Node consumeNextNode() { Node nextNode = checkNextNode(); ivNextNode = null; return nextNode; } public boolean hasNext() { return checkNextNode() != null; } public Node next() { return consumeNextNode(); } public void remove() { throw new UnsupportedOperationException("Removing nodes is not supported."); } public static NodeFilter getRootNodeFilter() { return new NodeFilter() { public short acceptNode(Node pNode) { if (pNode instanceof Element && pNode.getParentNode() instanceof Document) { return NodeFilter.FILTER_SKIP; } return NodeFilter.FILTER_ACCEPT; } }; } }
จากนั้นเราต้องสร้าง CanonicalizationMethod เพื่อดำเนินการทำให้เป็นมาตรฐานจริง มีตัวเลือกสองสามตัวที่นี่:
CanonicalizationMethod.INCLUSIVE CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS CanonicalizationMethod.EXCLUSIVE CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS
ในบทช่วยสอนนี้ เราจะสาธิตเฉพาะการทำให้เป็นมาตรฐานแบบรวมเท่านั้น ด้านล่างนี้คือโค้ดเพื่อทำสิ่งนี้
import java.io.ByteArrayInputStream; import javax.xml.crypto.Data; import javax.xml.crypto.OctetStreamData; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; public class CanonicalizationTest { private static String INPUT = "" + "hello" + "world" + ""; public static void main(String[] args){ transform(INPUT); } /** * Create new document with data * * @param xml * @return */ private static Document createNewDocument(String xml){ try{ byte[] bytes = xml.getBytes("UTF-8"); ByteArrayInputStream bin = new ByteArrayInputStream(bytes); DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); fac.setNamespaceAware(true); DocumentBuilder docBuilder = fac.newDocumentBuilder(); Document doc = docBuilder.parse(bin); return doc; } catch (Exception ex){ ex.printStackTrace(); } return null; } /** * Transform a XML string * * @param xml */ private static void transform(String xml){ Document doc = createNewDocument(xml); try{ Data data = new NodeSetDataImpl(doc, NodeSetDataImpl.getRootNodeFilter()); XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); CanonicalizationMethod canonicalizationMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null); // Doing the actual canonicalization OctetStreamData transformedData =(OctetStreamData) canonicalizationMethod.transform(data, null); byte[] bytes = tool.Util.readStream(transformedData.getOctetStream()); String str = new String(bytes); System.out.println(str); } catch (Exception ex){ ex.printStackTrace(); } } }
มาดูผลลัพธ์กันก่อน:
<my:Node xmlns:my="http://example.com" Id="1">hello</my:Node><my:Node xmlns:my="http://example.com" Id="2">world</my:Node>
การทดสอบจะสร้าง Document ก่อน จากนั้น Document นี้จะถูกส่งไปยัง NodeSetDataImpl เพื่อสร้างชุดโหนดที่จะทำให้เป็นมาตรฐาน จากเอาต์พุต เราจะเห็นว่าโหนด Root ถูกลบออกในข้อมูลที่ทำให้เป็นมาตรฐาน นี่เป็นเพราะ NodeFilter ใน NodeSetDataImpl ได้กรองโหนดนี้ออกไป ถัดไป my:Node ที่สองมีโหนด xmlns:my ก่อนโหนด Id ในรูปแบบที่ทำให้เป็นมาตรฐาน นี่เป็นไปตามข้อกำหนด Canonical XML ที่โหนดควรอยู่ในลำดับตามตัวอักษร
สำหรับวิธีการทำให้เป็นมาตรฐานอื่นๆ ตรรกะก็เหมือนกันที่นี่ ความแตกต่างคือเอาต์พุตไม่เหมือนกันเนื่องจากวิธีการทำให้เป็นมาตรฐานที่แตกต่างกันมีกฎที่แตกต่างกัน