forked from paulhiggs/dvb-i-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathschema_checks.js
221 lines (202 loc) · 9.49 KB
/
schema_checks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// schema_checks.js
import { parseXmlString } from "libxmljs2";
import format from "xml-formatter";
import { elementize } from "./phlib/phlib.js";
import { dvbi } from "./DVB-I_definitions.js";
import { APPLICATION, INFORMATION, WARNING } from "./ErrorList.js";
import { OLD, DRAFT } from "./sl-check.js";
import { isIn, xPath } from "./utils.js";
import { datatypeIs } from "./phlib/phlib.js";
import { keys } from "./CommonErrors.js";
/**
* check that the specified child elements are in the parent element
*
* @param {Object} checkElement the element whose attributes should be checked
* @param {Array} requiredAttributes the element names permitted within the parent
* @param {Array} optionalAttributes the element names permitted within the parent
* @param {Array} definedAttributes attributes that defined in the schema, whether requited, optional or profiled out
* @param {Class} errs errors found in validaton
* @param {string} errCode error code to be used in reports,
*/
export function checkAttributes(checkElement, requiredAttributes, optionalAttributes, definedAttributes, errs, errCode) {
if (!checkElement || !requiredAttributes) {
errs.addError({ type: APPLICATION, code: "AT000", message: "checkAttributes() called with checkElement==null or requiredAttributes==null" });
return;
}
let p = "";
try {
p = `${checkElement.parent().name()}.${checkElement.name()}`;
} catch (e) {
p = checkElement.name();
}
requiredAttributes.forEach((attributeName) => {
if (!checkElement.attr(attributeName))
errs.addError({ code: `${errCode}-1`, message: `${attributeName.attribute(`${p}`)} is a required attribute`, key: "missing attribute", line: checkElement.line() });
});
checkElement.attrs().forEach((attr) => {
if (!isIn(requiredAttributes, attr.name()) && !isIn(optionalAttributes, attr.name()) && !isIn(definedAttributes, attr.name()))
errs.addError({ code: `${errCode}-2`, message: `${attr.name().attribute()} is not permitted in ${p}`, key: "unexpected attribute", line: checkElement.line() });
});
definedAttributes.forEach((attribute) => {
if (!isIn(requiredAttributes, attribute) && !isIn(optionalAttributes, attribute))
if (checkElement.attr(attribute))
errs.addError({
type: INFORMATION,
code: `${errCode}-3`,
message: `${attribute.attribute()} is profiled out of ${checkElement.name().elementize()}`,
key: "unused attribute",
line: checkElement.line(),
});
});
}
/**
* check that the specified child elements are in the parent element
*
* @param {XMLNode} parentElement the element whose children should be checked
* @param {Array} childElements the names of elements and their cardinality
* @param {Array} definedChildElements the names of all child elements of parentElement that are defined in the schema, including
* those which are profiled out of DVB-I
* @param {boolean} allowOtherElements flag indicating if other elements, i.e. those defined in the another are permitted
* @param {Class} errs errors found in validaton
* @param {string} errCode error code to be used for any error found
* @returns {boolean} true if no errors are found (all mandatory elements are present and no extra elements are specified)
*
* NOTE: elements are described as an object containing "name", "minOccurs", "maxOccurs".
* Default values for minOccurs and maxOccurs are 1
*/
export function checkTopElementsAndCardinality(parentElement, childElements, definedChildElements, allowOtherElements, errs, errCode) {
var findElementIn = (elementList, elementName) => (datatypeIs(elementList, "array") ? elementList.find((element) => element.name == elementName) : false);
function getNamedChildElements(node, childElementName) {
let res = [],
childElems = node ? node.childNodes() : null;
if (childElems)
childElems.forEachSubElement((elem) => {
if (elem.name() == childElementName) res.push(elem);
});
return res;
}
if (!parentElement) {
errs.addError({ type: APPLICATION, code: "TE000", message: "checkTopElementsAndCardinality() called with a 'null' element to check" });
return false;
}
let rv = true,
thisElem = elementize(`${parentElement.parent().name()}.${parentElement.name()}`);
// check that each of the specifid childElements exists
childElements.forEach((elem) => {
let min = Object.prototype.hasOwnProperty.call(elem, "minOccurs") ? elem.minOccurs : 1;
let max = Object.prototype.hasOwnProperty.call(elem, "maxOccurs") ? elem.maxOccurs : 1;
let namedChildren = getNamedChildElements(parentElement, elem.name),
count = namedChildren.length;
if (count == 0 && min != 0) {
errs.addError({ code: `${errCode}-1`, line: parentElement.line(), message: `Mandatory element ${elem.name.elementize()} not specified in ${thisElem}` });
rv = false;
} else {
if (count < min || count > max) {
namedChildren.forEach((child) =>
errs.addError({
code: `${errCode}-2`,
line: child.line(),
message: `Cardinality of ${elem.name.elementize()} in ${thisElem} is not in the range ${min}..${max == Infinity ? "unbounded" : max}`,
})
);
rv = false;
}
}
});
// check that no additional child elements existance if the "Other Child Elements are OK" flag is not set
if (parentElement.childNodes()) {
// create a set of child elements that are in the schema but not in DVB-I
let excludedChildren = [];
definedChildElements.forEach((child) => {
if (!findElementIn(childElements, child)) excludedChildren.push(child);
});
parentElement.childNodes().forEachSubElement((child) => {
let childName = child.name();
if (!findElementIn(childElements, childName)) {
if (isIn(excludedChildren, childName))
errs.addError({ type: INFORMATION, code: `${errCode}-10`, message: `Element ${childName.elementize()} in ${thisElem} is not included in DVB-I`, line: child.line() });
else if (!allowOtherElements) {
errs.addError({ code: `${errCode}-11`, line: child.line(), message: `Element ${childName.elementize()} is not permitted in ${thisElem}` });
rv = false;
}
}
});
}
return rv;
}
/**
* check if the element contains the named child element
*
* @param {Object} elem the element to check
* @param {string} childElementName the name of the child element to look for
* @returns {boolean} true if the element contains the named child element(s) otherwise false
*/
export var hasChild = (elem, childElementName) => (elem ? elem.childNodes().find((el) => el.type() == "element" && el.name().endsWith(childElementName)) != undefined : false);
/**
* validate a XML document gainst the specified schema (included schemas must be in the same directory)
*
* @param {Document} XML the XML document to check
* @param {Document} XSD the schema
* @param {object} errs array to record any errors
* @param {string} errCode the error code to report with each error
*/
export function SchemaCheck(XML, XSD, errs, errCode) {
if (!XML.validate(XSD)) {
let prettyXML = format(XML.toString(), { collapseContent: true, lineSeparator: "\n", strictMode: true });
let lines = prettyXML.split("\n");
XML.validationErrors.forEach((ve) => {
// ignore errors related to duplicated schema imports (schema passed to this function vs that declared in instance document)
if (!(ve.domain == 16 && ve.code == 3083)) {
let splt = ve.toString().split("\r");
splt.forEach((err) => errs.addError({ code: errCode, message: err, fragment: lines[ve.line - 1], line: ve.line, key: keys.k_XSDValidation }));
}
});
}
}
/**
* report if the schema version being used is not 'formal'
*
* @param {object} props Metadata of the XML document
* @param {XMLdocument} document the XML document
* @param {enum} publication_state the publication status of the schema
* @param {Class} errs array to record any errors
* @param {string} errCode the error code to report with each error
*/
export function SchemaVersionCheck(props, document, publication_state, errs, errCode) {
let ServiceList = document.get(xPath(props.prefix, dvbi.e_ServiceList), props.schema);
if (publication_state & OLD) {
let err1 = { code: `${errCode}a`, message: "schema version is out of date", key: "schema version" };
if (ServiceList) err1.line = ServiceList.line();
errs.addError(err1);
}
if (publication_state & DRAFT) {
let err2 = { type: WARNING, code: `${errCode}b`, message: "schema is in draft state", key: "schema version" };
if (ServiceList) err2.line = ServiceList.line();
errs.addError(err2);
}
}
/**
* load the XML data
* @param {XMLdocument} document XMLdocument
* @param {Class} errs error handler for any loading errors
* @param {string} errcode error code prefix to use for any loading issues
* @returns {Document} an XML document structure for use with libxmljs2
*/
export function SchemaLoad(document, errs, errcode) {
let tmp = null,
prettyXML = format(document.replace(/(\n\t)/gm, "\n"), { collapseContent: true, lineSeparator: "\n" });
try {
tmp = parseXmlString(prettyXML);
} catch (err) {
errs.addError({ code: `${errcode}-1`, message: `XML parsing failed: ${err.message}`, key: "malformed XML" });
errs.loadDocument(prettyXML);
return null;
}
if (!tmp || !tmp.root()) {
errs.addError({ code: `${errcode}-2`, message: "XML document is empty", key: "malformed XML" });
errs.loadDocument(prettyXML);
return null;
}
errs.loadDocument(prettyXML);
return tmp;
}