3
//! A namespace aware version of Parser.XML.Tree. This implementation
4
//! does as little validation as possible, so e.g. you can call your
5
//! namespace xmlfoo without complaints.
7
inherit Parser.XML.Tree;
9
//! Namespace aware node.
15
/* static */ string default_ns;
16
static mapping(string:string) nss;
17
static string element_ns;
18
static mapping(string:mapping(string:string)) ns_attrs = ([]);
20
//! Returns the namespace in which the current element is defined in.
21
string get_ns() { return element_ns; }
23
//! Returns the default namespace in the current scope.
24
string get_default_ns() { return default_ns; }
26
//! Returns a mapping with all the namespaces defined in the current
27
//! scope, except the default namespace.
29
//! The returned mapping is the same as the one in the node, so
30
//! destructive changes will affect the node.
31
mapping(string:string) get_defined_nss() {
35
//! @decl mapping(string:string) get_ns_attributes(string namespace)
36
//! Returns the attributes in this node that is declared in the provided
39
//! @decl mapping(string:mapping(string:string)) get_ns_attributes()
40
//! Returns all the attributes in all namespaces that is associated with
43
//! The returned mapping is the same as the one in the node, so
44
//! destructive changes will affect the node.
46
mapping(string:string)|mapping(string:mapping(string:string))
47
get_ns_attributes(void|string namespace) {
49
return ns_attrs[namespace] || ([]);
53
//! Adds a new namespace to this node. The preferred symbol to
54
//! use to identify the namespace can be provided in the @[symbol]
55
//! argument. If @[chain] is set, no attempts to overwrite an
56
//! already defined namespace with the same identifier will be made.
57
void add_namespace(string ns, void|string symbol, void|int(0..1) chain) {
59
symbol = make_prefix(ns);
60
if(chain && nss[symbol])
65
get_children()->add_namespace(ns, symbol, 1);
68
//! Returns the difference between this nodes and its parents namespaces.
69
mapping(string:string) diff_namespaces() {
72
foreach(nss; string sym; string ns)
77
static string make_prefix(string ns) {
79
// return String.string2hex(Crypto.MD5->hash(ns))[..10];
80
return (string) hash(ns);
83
//! Returns the element name as it occurs in xml files. E.g.
84
//! "zonk:name" for the element "name" defined in a namespace
85
//! denoted with "zonk". It will look up a symbol for the namespace
86
//! in the symbol tables for the node and its parents. If none is
87
//! found a new label will be generated by hashing the namespace.
88
string get_xml_name() {
89
if(element_ns==default_ns) return mTagName;
90
string prefix = search(nss, element_ns);
92
prefix = make_prefix(element_ns);
93
return prefix + ":" + mTagName;
98
void create(int type, string name, mapping attr, string text,
101
// Get the parent namespace context.
103
parent->add_child(this);
104
nss = parent->get_defined_nss();
105
default_ns = parent->get_default_ns();
110
if(type == XML_TEXT || type == XML_COMMENT) {
111
::create(type, name, attr, text);
115
// First handle all xmlns attributes.
116
foreach(attr; string name; string value) {
118
// Update namespace scope. Must be done before analyzing the namespace
119
// of the current element.
120
string lcname = lower_case(name);
121
if(lcname=="xmlns") {
123
m_delete(attr, name);
125
else if(has_prefix(lcname, "xmlns:")) {
126
nss = nss + ([ name[6..]:value ]); // No destructive changes.
131
// Assign the right namespace to this element
132
if(has_value(name, ":")) {
133
sscanf(name, "%s:%s", element_ns, name);
135
error("Unknown namespace %s.\n", element_ns);
136
element_ns = nss[element_ns];
139
element_ns = default_ns;
141
// Then take care of the rest of the attributes.
142
foreach(attr; string name; string value) {
144
if(has_prefix(lower_case(name), "xmlns"))
147
// Move namespaced attributes to a mapping of their own.
149
if( sscanf(name, "%s:%s", ns, m)==2 ) {
152
add_namespace("xml","xml");
154
error("Unknown namespace %s.\n", ns);
157
m_delete(attr, name);
159
// FIXME: This makes the RDF-tests work,
160
// but is it according to the spec?
163
if (ns_attrs[ns] && !zero_type(ns_attrs[ns][m])) {
164
// We have an explicit entry already,
165
// so skip the implicit entry.
171
ns_attrs[ns][m] = value;
174
::create(type, name, attr, text);
177
void set_parent(NSNode parent) {
178
nss = parent->get_defined_nss() + diff_namespaces();
179
::set_parent(parent);
182
NSNode add_child(NSNode c) {
184
return ::add_child(c);
187
//! @decl void remove_child(NSNode child)
188
//! The remove_child is a not updated to take care of name
189
//! space issues. To properly remove all the parents name spaces
190
//! from the chid, call @[remove_node] in the child.
193
nss = diff_namespaces();
197
NSNode clone(void|int(-1..1) direction) {
198
Node n = NSNode(get_node_type(), get_ns()+":"+get_tag_name(),
199
get_attributes(), get_text(), mParent);
202
Node p = get_parent();
204
n->set_parent( p->clone(-1) );
208
foreach(get_children(), Node child)
209
n->add_child( child->clone(1) );
216
String.Buffer data = String.Buffer();
220
switch(n->get_node_type()) {
223
data->add( text_quote(n->get_text()) );
227
if (!sizeof(n->get_tag_name()))
230
data->add("<", n->get_xml_name());
232
if (mapping attr = n->get_attributes()) { // FIXME
233
foreach(indices(attr), string a)
234
data->add(" ", a, "='",
235
attribute_quote(attr[a]), "'");
238
mapping attr = n->get_ns_attrubutes();
240
foreach(attr; string ns; mapping attr) {
244
if (n->count_children())
253
if (n->get_node_type() == XML_ELEMENT)
254
if (n->count_children())
255
if (sizeof(n->get_tag_name()))
256
data->add("</", n->get_xml_name(), ">");
262
string _sprintf(int t) {
264
mapping nt = ([ XML_ROOT:"ROOT",
265
XML_ELEMENT:"ELEMENT",
269
XML_COMMENT:"COMMENT",
270
XML_DOCTYPE:"DOCTYPE",
272
string n = get_any_name();
274
return sprintf("%O(%s)", this_program,
275
nt[get_node_type()] || "UNKNOWN");
276
return sprintf("%O(%s,%O,%O)", this_program,
277
nt[get_node_type()] || "UNKNOWN", n, get_ns());
282
static NSNode|int(0..0) parse_xml_callback(string type, string name,
283
mapping attr, string|array contents,
284
mixed location, mixed ...extra)
287
NSNode parent = sizeof(extra[0]) && extra[0]->top();
293
return NSNode(XML_TEXT, "", 0, contents, parent);
296
// Create comment node
297
return NSNode(XML_COMMENT, "", 0, contents, parent);
301
return NSNode(XML_HEADER, "", attr, "", parent);
304
// XML processing instruction
305
return NSNode(XML_PI, name, attr, contents, parent);
308
// Create new tag node.
309
return NSNode(XML_ELEMENT, name, attr, "", parent);
312
// Create tree node for this contaier.
313
extra[0]->push( NSNode(XML_ELEMENT, name, attr, "", parent) );
317
return extra[0]->pop();
319
// We need to merge consecutive text
320
// children since two text elements can't be neighbors according to
322
// FIXME: When does this happen?
325
if (location && mappingp(location))
326
error(contents + " [Offset: " + location->location + "]\n");
328
error(contents + "\n");
336
//! Takes a XML string @[data] and produces a namespace node tree.
337
//! If @[default_ns] is given, it will be used as the default namespace.
339
//! Throws an @[error] when an error is encountered during XML
341
NSNode parse_input(string data, void|string default_ns)
343
object xp = spider.XML();
346
xp->allow_rxml_entities(1);
348
// Construct tree from string
349
mRoot = NSNode(XML_ROOT, "", ([ ]), "");
350
mRoot->default_ns = default_ns;
351
ADT.Stack s = ADT.Stack();
353
xp->parse(data, parse_xml_callback, s);
357
//! Makes a visualization of a node graph suitable for
358
//! printing out on a terminal.
361
//! > object x = parse_input("<a><b><c/>d</b><b><e/><f>g</f></b></a>");
362
//! > write(visualize(x));
364
//! NSNode(ELEMENT,"a")
365
//! NSNode(ELEMENT,"b")
366
//! NSNode(ELEMENT,"c")
368
//! NSNode(ELEMENT,"b")
369
//! NSNode(ELEMENT,"e")
370
//! NSNode(ELEMENT,"f")
374
string visualize(Node n, void|string indent) {
376
return sprintf("%O\n", n) + visualize(n, " ");
379
foreach(n->get_children(), Node c) {
380
ret += sprintf("%s%O\n", indent, c);
381
ret += visualize(c, indent+" ");