Changelog
Source:NEWS.md
zuxml 0.1.0
First release.
Parsing
-
xml_parse()parses XML from a single string or a raw vector, andxml_read()from a file, a URL or a connection. Both build an immutable document tree. A character vector of another length is an error rather than being joined silently: join the lines ofreadLines()with"\n"first. -
xml_read()streams: a string starting withhttp://,https://,ftp://,ftps://orfile://is opened withurl(), anything else withfile(), and a connection (gzfile(),rawConnection(), a socket) is accepted as is. Bytes are fed to the parser as they are read through R’s connection API, so the body is never held whole; only an encoding that must go throughiconv()is read in full first. An unopened connection is opened in binary mode and closed afterwards; an open one must be binary and blocking, and is left open. A URL or file that cannot be opened is azuxml:error naming the cause (HTTP status was '404 Not Found'), with no stray warning. - Encodings Expat handles natively are passed through, under any of their common spellings (
"latin1","UTF8","ASCII"); anything else is transcoded withiconv(), after which the (now stale) encoding declaration is overridden so it cannot mislead the parser. A character string is already decoded, soencodingmust beNULLor UTF-8 for one.
Navigation and accessors
-
xml_root(),xml_parent(),xml_children(),xml_elements()andxml_find()walk the tree.xml_elements()andxml_find()take an optional name and namespace filter. -
xml_find_first()returns one node per input: the first matching descendant, or a missing node where there is none. Every accessor givesNAfor a missing node, so per-record lookups stay aligned when a field is absent, which a flatxml_find()cannot do. -
xml_name(),xml_local(),xml_ns(),xml_prefix(),xml_attr(),xml_attrs(),xml_text()andxml_type()read node properties. All are vectorized over a nodeset, so n nodes give a result of length n. - Text nodes are maximal: adjacent character data is always one node, whatever the input chunk boundaries were and whether or not
commentsorpisdropped a node that sat in the middle of it. - Names are matched on
(namespace, local name)only. Prefixes are retained for serialization and diagnostics but never decide identity, so two prefixes bound to one URI are the same name. - Nodesets behave like vectors:
[,[[,c(),rev(),length()andformat()methods are provided. -
xml_version(),xml_encoding()andxml_standalone()read the XML declaration.
Extracting tables and lists
-
xml_table()turnstableelements into data frames: rows from the table and itsthead,tbodyandtfoot,colspanandrowspanexpanded, header detected from a row ofthcells, every column character. The expanded size is bounded bymax_cells, so a hostile span cannot become a huge allocation. -
xml_list()turnsulandolelements into lists, with nested lists as nested items. - Both read XML, XHTML included, and apply no HTML parsing rules.
Writing
-
xml_serialize()returns XML text andxml_write()sends it to a file. Order, attributes, text and namespace semantics round-trip; original formatting (quote style, inter-attribute whitespace, CDATA boundaries) does not. - Output is always UTF-8, and a prepended declaration says so whatever the source document declared.
declaration = TRUEapplies to a single node, so it cannot produce a file carrying one declaration per node.
Security
- Document type declarations are rejected by default.
allow_doctype = TRUEaccepts one, but still rejects an internal subset, which is the only place a document can declare entities. - External entity resolution is not compiled in at all, so no file or network access is reachable from a parse regardless of input or options.
-
max_depth,max_nodes,max_attrs,max_textandmax_memorybound what a hostile document can cost. Each raises its own condition, and all of them inherit fromzuxml_limit_error. A limit must be a positive whole number, orInffor the largest the parser can represent; anything else, including a value above that, is an error, never replaced by the default. - Every error is a typed condition under a
zuxml_errorparent. Parse failures carry line, column, byte offset, the C status and Expat’s error code; limit failures also name the limit and its value. Unusable arguments raisezuxml_invalid_argument. See?"zuxml-conditions". -
zuxml_info()reports the policy compiled into the installed build.
For package authors
- A registered C function table lets other packages parse XML without linking against Expat themselves:
Imports: zuxmlplusLinkingTo: zuxml, animportFrom(zuxml, ...)directive inNAMESPACE(without it zuxml’s namespace is never loaded and the table is never registered), thenzuxml_api_get()from<zuxml.h>. The header documents the string-lifetime contract and the versioning rules, and compiles cleanly as C and C++ under-Wall -Wextra -Werror. - Tree construction, traversal, text concatenation, serialization and freeing are all iterative, so arbitrarily deep documents cannot overflow the C stack.
- An installed zuxml also ships
lib${R_ARCH}/libzuxml.a(plainlib/where R sets no architecture) together with Expat’sexpat.handexpat_external.h, so a package whose C code is written against Expat itself can link the parser statically throughLinkingTorather than being rewritten around the function table. The table remains the recommended interface; see “Using zuxml from C” in the README for the differences that come with the archive, the parser policy compiled into it among them.