@@ -3,30 +3,57 @@ local Document = {}
33local libxml2 = require (" xmlua.libxml2" )
44local ffi = require (" ffi" )
55local converter = require (" xmlua.converter" )
6- local to_string = converter .to_string
76
87local Serializable = require (" xmlua.serializable" )
98local Searchable = require (" xmlua.searchable" )
109
1110
11+ local Attribute
12+ local AttributeDeclaration
1213local CDATASection
1314local Comment
1415local DocumentFragment
1516local DocumentType
1617local Element
18+ local ElementDeclaration
19+ local EntityDeclaration
1720local EntityReference
1821local Namespace
22+ local NamespaceDeclaration
23+ local Notation
1924local ProcessingInstruction
25+ local Text
2026
2127function Document .lazy_load ()
28+ Attribute = require (" xmlua.attribute" )
29+ AttributeDeclaration = require (" xmlua.attribute-declaration" )
2230 CDATASection = require (" xmlua.cdata-section" )
2331 Comment = require (" xmlua.comment" )
2432 DocumentFragment = require (" xmlua.document-fragment" )
2533 DocumentType = require (" xmlua.document-type" )
2634 Element = require (" xmlua.element" )
35+ ElementDeclaration = require (" xmlua.element-declaration" )
36+ EntityDeclaration = require (" xmlua.entity-declaration" )
2737 EntityReference = require (" xmlua.entity-reference" )
2838 Namespace = require (" xmlua.namespace" )
39+ NamespaceDeclaration = require (" xmlua.namespace-declaration" )
40+ Notation = require (" xmlua.notation" )
2941 ProcessingInstruction = require (" xmlua.processing-instruction" )
42+ Text = require (" xmlua.text" )
43+ end
44+
45+ local DEFAULT_C14N_MODE = " EXCLUSIVE_1_0"
46+
47+ local C14N_MODES = {
48+ [" 1_0" ] = ffi .C .XML_C14N_1_0 , -- Original C14N 1.0 spec
49+ [" EXCLUSIVE_1_0" ] = ffi .C .XML_C14N_EXCLUSIVE_1_0 , -- Exclusive C14N 1.0 spec
50+ [" 1_1" ] = ffi .C .XML_C14N_1_1 , -- C14N 1.1 spec
51+ }
52+
53+ local C14N_MODES_LOOKUP = {} -- lookup by name or number, returns the number
54+ for name , number in pairs (C14N_MODES ) do
55+ C14N_MODES_LOOKUP [name ] = number
56+ C14N_MODES_LOOKUP [number ] = number
3057end
3158
3259local methods = {}
@@ -152,6 +179,187 @@ function methods:get_dtd_entity(name)
152179 return converter .convert_xml_entity (raw_dtd_entity )
153180end
154181
182+
183+ do -- C14N methods
184+ local function create_xml_string_array (list )
185+ if (not list ) or # list == 0 then
186+ return nil
187+ end
188+
189+ local result = ffi .new (' xmlChar*[?]' , # list + 1 )
190+ local xml_nses = {}
191+ for i , prefix in ipairs (list ) do
192+ local xml_ns = ffi .new (" unsigned char[?]" , # prefix + 1 , prefix )
193+ ffi .copy (xml_ns , prefix )
194+ result [i - 1 ] = xml_ns
195+ xml_nses [i ] = xml_ns -- hold on to xml_nses to prevent GC while in use
196+ end
197+ result [# list ] = nil
198+
199+ return ffi .gc (result , function (ptr )
200+ xml_nses = nil -- release references, so they can be GC'ed
201+ end )
202+ end
203+
204+ local function create_xml_node_set (nodes )
205+ if (not nodes ) or # nodes == 0 then
206+ return nil
207+ end
208+
209+ local xml_nodes = ffi .new (" xmlNodePtr[?]" , # nodes )
210+ for i = 1 , # nodes do
211+ xml_nodes [i - 1 ] = nodes [i ].node -- FFI side is 0 indexed
212+ end
213+
214+ local set = ffi .new (" xmlNodeSet" )
215+ set .nodeNr = # nodes
216+ set .nodeMax = # nodes
217+ set .nodeTab = xml_nodes
218+
219+ return ffi .gc (set , function (ptr )
220+ xml_nodes = nil -- release references, so they can be GC'ed
221+ end )
222+ end
223+
224+ local wrap_raw_node do
225+ -- order is according to the constant value of xmlElementType enum in libxml2
226+ local type_generators = setmetatable ({
227+ [ffi .C .XML_ELEMENT_NODE ] = function (document , xml_node )
228+ return Element .new (document , xml_node )
229+ end ,
230+ [ffi .C .XML_ATTRIBUTE_NODE ] = function (document , xml_node )
231+ return Attribute .new (document , xml_node )
232+ end ,
233+ [ffi .C .XML_TEXT_NODE ] = function (document , xml_node )
234+ return Text .new (document , xml_node )
235+ end ,
236+ [ffi .C .XML_CDATA_SECTION_NODE ] = function (document , xml_node )
237+ return CDATASection .new (document , xml_node )
238+ end ,
239+ [ffi .C .XML_ENTITY_REF_NODE ] = function (document , xml_node )
240+ error (" XML_ENTITY_REF_NODE not implemented" ) -- TODO: implement
241+ end ,
242+ [ffi .C .XML_ENTITY_NODE ] = function (document , xml_node )
243+ error (" XML_ENTITY_NODE not implemented" ) -- TODO: implement
244+ end ,
245+ [ffi .C .XML_PI_NODE ] = function (document , xml_node )
246+ return ProcessingInstruction .new (document , xml_node )
247+ end ,
248+ [ffi .C .XML_COMMENT_NODE ] = function (document , xml_node )
249+ return Comment .new (document , xml_node )
250+ end ,
251+ [ffi .C .XML_DOCUMENT_NODE ] = function (document , xml_node )
252+ return Document .new (xml_node )
253+ end ,
254+ [ffi .C .XML_DOCUMENT_TYPE_NODE ] = function (document , xml_node )
255+ return DocumentType .new (document , xml_node )
256+ end ,
257+ [ffi .C .XML_DOCUMENT_FRAG_NODE ] = function (document , xml_node )
258+ return DocumentFragment .new (document , xml_node )
259+ end ,
260+ [ffi .C .XML_NOTATION_NODE ] = function (document , xml_node )
261+ return Notation .new (document , xml_node )
262+ end ,
263+ [ffi .C .XML_HTML_DOCUMENT_NODE ] = function (document , xml_node )
264+ error (" XML_HTML_DOCUMENT_NODE not implemented" ) -- TODO: implement
265+ end ,
266+ [ffi .C .XML_DTD_NODE ] = function (document , xml_node )
267+ error (" XML_DTD_NODE not implemented" ) -- TODO: implement
268+ end ,
269+ [ffi .C .XML_ELEMENT_DECL ] = function (document , xml_node )
270+ return ElementDeclaration .new (document , xml_node )
271+ end ,
272+ [ffi .C .XML_ATTRIBUTE_DECL ] = function (document , xml_node )
273+ return AttributeDeclaration .new (document , xml_node )
274+ end ,
275+ [ffi .C .XML_ENTITY_DECL ] = function (document , xml_node )
276+ return EntityDeclaration .new (document , xml_node )
277+ end ,
278+ [ffi .C .XML_NAMESPACE_DECL ] = function (document , xml_node )
279+ return NamespaceDeclaration .new (document , xml_node )
280+ end ,
281+ [ffi .C .XML_XINCLUDE_START ] = function (document , xml_node )
282+ error (" XML_XINCLUDE_START not implemented" ) -- TODO: implement
283+ end ,
284+ [ffi .C .XML_XINCLUDE_END ] = function (document , xml_node )
285+ error (" XML_XINCLUDE_END not implemented" ) -- TODO: implement
286+ end ,
287+ [ffi .C .XML_DOCB_DOCUMENT_NODE ] = function (document , xml_node )
288+ error (" XML_DOCB_DOCUMENT_NODE not implemented" ) -- TODO: implement
289+ end ,
290+ }, {
291+ __index = function (self , key )
292+ error (" Unknown node type: " .. tostring (key ))
293+ end
294+ })
295+
296+ function wrap_xml_node (document , xml_node )
297+ if xml_node == ffi .NULL then
298+ return nil
299+ end
300+ return type_generators [tonumber (xml_node .type )](document , xml_node )
301+ end
302+ end
303+
304+ --- Canonicalize an XML document or set of elements.
305+ -- @param self xmlua.Document from which to canonicalize elements
306+ -- @tparam [opt={}] array|function select array of nodes to include, or function to determine if a node should be
307+ -- included in the canonicalized output. Signature: `boolean = function(node, parent)`. Defaults to an empty
308+ -- array, which canonicalizes the entire document.
309+ -- @tparam [opt] table opts options table with the following fields:
310+ -- @tparam [opt="EXCLUSIVE_1_0"] string|number opts.mode any of '1_0", "EXCLUSIVE_1_0", "1_1"
311+ -- @tparam [opt] array opts.inclusive_ns_prefixes array of namespace prefixes to include
312+ -- @tparam [opt=false] boolean with_comments if truthy, comments will be included
313+ -- @return string containing canonicalized XML, or throws an error if it fails
314+ function methods :canonicalize (select , opts )
315+ select = select or {} -- default to include all nodes in the output
316+ opts = opts or {}
317+
318+ local with_comments = 0 -- default = not including comments
319+ if opts .with_comments then
320+ with_comments = 1
321+ end
322+
323+ local mode = opts .mode or DEFAULT_C14N_MODE
324+ if not C14N_MODES_LOOKUP [mode ] then
325+ error (" mode must be a valid C14N mode constant, got: " .. tostring (mode ))
326+ end
327+ mode = C14N_MODES_LOOKUP [mode ]
328+
329+ local prefixes = create_xml_string_array (opts .inclusive_ns_prefixes )
330+ local buffer = libxml2 .xmlBufferCreate ()
331+ local output_buffer = libxml2 .xmlOutputBufferCreate (buffer )
332+
333+ local success
334+ if type (select ) == " function" then -- callback function
335+ -- wrap the callback to pass wrapped objects, and return 1 or 0
336+ local callback = function (_ , xml_node , xml_parent )
337+ local node = wrap_xml_node (self , xml_node )
338+ local parent = wrap_xml_node (self , xml_parent )
339+ if select (node , parent ) then
340+ return 1
341+ else
342+ return 0
343+ end
344+ end
345+ success = libxml2 .xmlC14NExecute (self .document , callback , nil , mode ,
346+ prefixes , with_comments , output_buffer )
347+
348+ elseif type (select ) == " table" then -- array of nodes
349+ local node_set = create_xml_node_set (select )
350+ success = libxml2 .xmlC14NDocSaveTo (self .document , node_set , mode ,
351+ prefixes , with_comments , output_buffer )
352+ else
353+ error (" select must be a function or an array of nodes" )
354+ end
355+
356+ if success < 0 then
357+ error (" failed to generate C14N string" )
358+ end
359+ return libxml2 .xmlBufferGetContent (buffer )
360+ end
361+ end -- end of C14N methods
362+
155363local function build_element (element , tree )
156364 local sub_element = element :append_element (tree [1 ], tree [2 ])
157365 for i = 3 , # tree do
0 commit comments