Skip to content

Commit 59e44f6

Browse files
committed
Parse more information from the RNG grammar.
The parser now knows which elements or attributes are allowed on elements. It also know which elements may contain text. This information is not yet output.
1 parent f5949f3 commit 59e44f6

File tree

1 file changed

+213
-55
lines changed

1 file changed

+213
-55
lines changed

webodf/tools/odfRng2Config.js

Lines changed: 213 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,73 @@
2222
* @source: https://github.com/kogmbh/WebODF/
2323
*/
2424

25-
function aggregate(callback) {
26-
return function (collection, individual) {
27-
return collection.concat(callback(individual));
28-
};
29-
}
25+
"use strict";
3026

31-
function toArray(nodeList) {
32-
"use strict";
33-
return Array.prototype.slice.call(nodeList);
27+
var rngns = "http://relaxng.org/ns/structure/1.0";
28+
29+
/**
30+
* Check if a node is an element in the rng namespace and the given localName.
31+
* @param {!string} localName
32+
* @param {!Node} node
33+
* @return {!boolean}
34+
*/
35+
function isRng(localName, node) {
36+
if (node.nodeType !== 1) {
37+
return false;
38+
}
39+
if (node.namespaceURI !== rngns) {
40+
return false;
41+
}
42+
if (node.localName !== localName) {
43+
return false;
44+
}
45+
return true;
3446
}
3547

36-
function getName(node) {
37-
return node && node.getAttribute("name");
48+
/**
49+
* @param {!Node} node
50+
* @return {?Element}
51+
*/
52+
function getFirstElementNode(node) {
53+
node = node.firstChild;
54+
while (node && node.nodeType !== 1) {
55+
node = node.nextSibling;
56+
}
57+
return node;
3858
}
3959

60+
/**
61+
* Return all explicit names for an <element/> or <attribute/> element.
62+
* <anyName/> and <nsName/> return nothing.
63+
*
64+
* @param {!Node} node
65+
* @return {!Array.<!string>}
66+
*/
4067
function getNames(node) {
41-
var name = getName(node);
42-
return name ? [name] : toArray(node.getElementsByTagName("name")).map(function (node) {
43-
return node.textContent;
44-
});
68+
var names = [];
69+
if (node.hasAttribute("name")) {
70+
names.push(node.getAttribute("name"));
71+
} else {
72+
node = getFirstElementNode(node);
73+
if (isRng("choice", node)) {
74+
node = getFirstElementNode(node);
75+
}
76+
while (node) {
77+
if (isRng("name", node)) {
78+
names.push(node.textContent);
79+
}
80+
node = node.nextSibling;
81+
}
82+
}
83+
return names;
4584
}
4685

86+
/**
87+
* Increase length of string by adding spaces.
88+
* @param {!string} str
89+
* @param {!number} length
90+
* @return {!string}
91+
*/
4792
function pad(str, length) {
4893
while (str.length < length) {
4994
str += " ";
@@ -52,58 +97,171 @@ function pad(str, length) {
5297
}
5398

5499
/**
55-
* Extract container node information out of the supplied RNG schema document.
56-
* This only does extremely simplistic parsing.
57-
*
58-
* @constructor
59-
* @param {!Document} document
100+
* Get all the <define/> elements from an RNG grammar.
101+
* @param {!Element} grammar
102+
* @return {!Object.<!string,!Element>}
60103
*/
61-
function ExtractContainerInfo(document) {
62-
/**
63-
* @param {!Node} node
64-
* @return {!Array.<!Node>}
65-
*/
66-
function findParentElements(node) {
67-
var refs;
68-
69-
while (node && /(define|element)/.test(node.localName) === false) {
70-
node = node.parentNode;
104+
function getDefines(grammar) {
105+
var defines = {},
106+
c = grammar.firstChild;
107+
while (c) {
108+
if (c.nodeType === 1 && isRng("define", c)) {
109+
defines[c.getAttribute("name")] = c;
71110
}
111+
c = c.nextSibling;
112+
}
113+
return defines;
114+
}
72115

73-
if (node) {
74-
if (node.localName === "element") {
75-
return [node];
116+
/**
117+
* Information about an attribute or element.
118+
* @constructor
119+
*/
120+
function Info() {
121+
/**@type {!Object.<!string,!string>}*/
122+
this.refs = {};
123+
/**@type {!boolean}*/
124+
this.text = false;
125+
/**@type {!boolean}*/
126+
this.data = false;
127+
/**@type {!boolean}*/
128+
this.value = false;
129+
/**@type {!Array.<!Info}*/
130+
this.childElements = [];
131+
/**@type {!Array.<!Info}*/
132+
this.attributes = [];
133+
}
134+
135+
/**
136+
* Add information from a <define/> to that of <element/> or <attribute/>.
137+
* @param {!Info} info
138+
* @param {!string} ref
139+
* @param {!Object.<!string,!Info>} defines
140+
* @return {undefined}
141+
*/
142+
function addDefine(info, ref, defines) {
143+
var define = defines[ref],
144+
c;
145+
if (define) {
146+
info.text = info.text || define.text;
147+
info.data = info.data || define.data;
148+
info.value = info.value || define.value;
149+
define.childElements.forEach(function (ce) {
150+
if (info.childElements.indexOf(ce) === -1) {
151+
info.childElements.push(ce);
76152
}
77-
refs = toArray(document.querySelectorAll("ref[name='" + getName(node) + "']"));
78-
return refs.reduce(aggregate(findParentElements), []);
79-
}
80-
return [];
153+
});
154+
define.attributes.forEach(function (a) {
155+
if (info.attributes.indexOf(a) === -1) {
156+
info.attributes.push(a);
157+
}
158+
});
81159
}
160+
}
82161

83-
this.getTextElements = function() {
84-
return toArray(document.getElementsByTagName("text")).reduce(aggregate(findParentElements), []);
85-
};
162+
/**
163+
* Add information from <define/> elements to the set of <element/> or
164+
* <attribute/> elements.
165+
* @param {!Object.<!string,!Info>} infos
166+
* @param {!Object.<!string,!Info>} defines
167+
* @return {undefined}
168+
*/
169+
function resolveDefines(infos, defines) {
170+
Object.keys(infos).forEach(function (name) {
171+
var info = infos[name];
172+
Object.keys(info.refs).forEach(function (ref) {
173+
addDefine(info, ref, defines);
174+
});
175+
});
176+
}
177+
178+
/**
179+
* Recursively collect information from all elements in an RNG grammar.
180+
* If a <ref/> is encountered, the corresponding <define/> is traversed.
181+
* This is done only once for each <define/>.
182+
*
183+
* @param {!Element} e
184+
* @param {!Object.<!string,!Element} defs
185+
* @param {?Info} current
186+
* @param {!Object.<!string,!Info} elements
187+
* @param {!Object.<!string,!Info} attributes
188+
* @param {!Object.<!string,!Info} defines
189+
* @return {undefined}
190+
*/
191+
function handleChildElements(e, defs, current, elements, attributes, defines) {
192+
var c = e.firstChild,
193+
def,
194+
info,
195+
name;
196+
while (c) {
197+
if (isRng("ref", c)) {
198+
name = c.getAttribute("name");
199+
if (current) {
200+
current.refs[name] = name;
201+
}
202+
def = defs[name];
203+
if (def) {
204+
delete defs[name];
205+
info = new Info();
206+
defines[name] = info;
207+
handleChildElements(def, defs, info, elements, attributes, defines);
208+
}
209+
} else if (isRng("element", c)) {
210+
info = new Info();
211+
getNames(c).forEach(function (name) {
212+
elements[name] = info;
213+
});
214+
if (current) {
215+
current.childElements.push(info);
216+
}
217+
handleChildElements(c, defs, info, elements, attributes, defines);
218+
} else if (isRng("attribute", c)) {
219+
info = new Info();
220+
getNames(c).forEach(function (name) {
221+
attributes[name] = info;
222+
});
223+
if (current) {
224+
current.attributes.push(info);
225+
}
226+
handleChildElements(c, defs, info, elements, attributes, defines);
227+
} else if (isRng("text", c)) {
228+
current.text = true;
229+
} else if (isRng("data", c)) {
230+
current.data = true;
231+
} else if (isRng("value", c)) {
232+
current.value = true;
233+
} else {
234+
handleChildElements(c, defs, current, elements, attributes, defines);
235+
}
236+
c = c.nextSibling;
237+
}
86238
}
87239

88240
function onLoadRng(err, document) {
89241
if (err) {
90242
console.log("\nError: " + err + "\n");
91-
runtime.exit(1);
92-
} else {
93-
var containerFinder = new ExtractContainerInfo(document),
94-
textElements,
95-
elementNames,
96-
doc;
97-
98-
textElements = containerFinder.getTextElements();
99-
elementNames = textElements.reduce(aggregate(getNames), []).sort();
100-
doc = elementNames.map(function (elementName) {
101-
return "[" + pad('"' + elementName + '"', 40) + ", TODO]";
102-
}).join(",\n");
103-
104-
console.log(doc + "\n");
105-
runtime.exit(0);
243+
return runtime.exit(1);
106244
}
245+
var grammar = document.documentElement,
246+
start = document.getElementsByTagNameNS(rngns, "start").item(0),
247+
defs = getDefines(grammar),
248+
elements = {},
249+
attributes = {},
250+
defines = {},
251+
elementNames,
252+
doc;
253+
254+
handleChildElements(start, defs, null, elements, attributes, defines);
255+
resolveDefines(elements, defines);
256+
resolveDefines(attributes, defines);
257+
258+
elementNames = Object.keys(elements).sort();
259+
doc = elementNames.map(function (elementName) {
260+
return "[" + pad('"' + elementName + '"', 40) + ", TODO]";
261+
}).join(",\n");
262+
263+
console.log(doc + "\n");
264+
runtime.exit(0);
107265
}
108266

109267
function main(args) {
@@ -117,4 +275,4 @@ function main(args) {
117275
runtime.loadXML(rngPath, onLoadRng);
118276
}
119277
}
120-
main(String(typeof arguments) !== "undefined" && Array.prototype.slice.call(arguments));
278+
main(String(typeof arguments) !== "undefined" && Array.prototype.slice.call(arguments));

0 commit comments

Comments
 (0)