1
+ const Ajv = require ( "ajv" ) ;
2
+ const ajv = new Ajv ( { $data : true , allErrors : true } )
3
+ require ( "ajv-keywords" ) ( ajv ) ;
4
+ const { XMLParser, XMLValidator } = require ( 'fast-xml-parser' ) ;
5
+ const xsdValidator = require ( 'libxmljs2-xsd' ) ;
6
+ const fileSync = require ( "fs" ) ;
7
+ const path = require ( 'path' ) ;
8
+
9
+ exports . factorialize = factorialize ;
10
+ exports . makeDeepCopy = makeDeepCopy ;
11
+ exports . isStructureValid = isStructureValid
12
+ exports . roundToDigit = roundToDigit ;
13
+ exports . getLsd = getLsd ;
14
+ exports . roundToInterval = roundToInterval ;
15
+ exports . floorToInterval = floorToInterval ;
16
+ exports . ceilToInterval = ceilToInterval ;
17
+ exports . mod = mod ;
18
+
19
+ exports . validateXMLAgainstXSD = validateXMLAgainstXSD ;
20
+ exports . validateXML = validateXML ;
21
+ exports . parseXMLToJson = parseXMLToJson ;
22
+
23
+
24
+ const PRECISION = 15 ;
25
+
26
+ /**
27
+ * Calculates the faculty of an integer
28
+ *
29
+ * If the number is smaller than 0 or not an integer, -1 will be returned
30
+ *
31
+ * @param {number } num
32
+ * @returns
33
+ */
34
+ function factorialize ( num ) {
35
+ if ( ! Number . isInteger ( num ) || num < 0 )
36
+ return - 1 ;
37
+ else if ( num == 0 )
38
+ return 1 ;
39
+ else
40
+ return ( num * factorialize ( num - 1 ) ) ;
41
+ }
42
+
43
+ /**
44
+ * Makes a deep copy of the input object
45
+ *
46
+ * @param {any[], Object } obj
47
+ * @returns
48
+ */
49
+ function makeDeepCopy ( obj ) {
50
+ return JSON . parse ( JSON . stringify ( obj ) ) ;
51
+ }
52
+
53
+ /**
54
+ * Validates if the passed object fulfills the given JSON schema
55
+ *
56
+ * @author localhorst87
57
+ * @function
58
+ * @param {Object } obj The object to be validated
59
+ * @param {Object } schema The schema the object is validated against
60
+ * @return {Boolean } returns true if the object fulfills the schema
61
+ */
62
+ function isStructureValid ( obj , schema ) {
63
+ const validate = ajv . compile ( schema ) ;
64
+ return validate ( obj ) ;
65
+ }
66
+
67
+ /**
68
+ * Applies the given significant digits to the number
69
+ *
70
+ * @author localhorst87
71
+ * @function
72
+ * @param {number } value value to apply precision to
73
+ * @param {number } precision significant digits
74
+ * @returns {number } value with given significant digits
75
+ */
76
+ function precise ( value , precision ) {
77
+ return parseFloat ( value . toPrecision ( precision ) ) ;
78
+ }
79
+
80
+ /**
81
+ * Rounds a value to a given digit.
82
+ * The function is valid for fast floating point arithmetic,
83
+ * up to a precision of 15 digits.
84
+ *
85
+ * digit > 0: digit after decimal point
86
+ *
87
+ * digit == 0: round to integer
88
+ *
89
+ * digit < 0: digit before decimal point
90
+ *
91
+ * @author localhorst87
92
+ * @function
93
+ * @param {number } value value to round
94
+ * @param {number } digit the digit to round to
95
+ * @param {Function } [roundFn=Math.round] round function, Math.round by default
96
+ * @returns {number } rounded value
97
+ */
98
+ function roundToDigit ( value , digit , roundFn = Math . round ) {
99
+ if ( typeof ( value ) !== "number" )
100
+ throw ( "value must be numerical" ) ;
101
+ if ( typeof ( digit ) !== "number" )
102
+ throw ( "digit must be numerical" ) ;
103
+ if ( ! Number . isInteger ( digit ) )
104
+ throw ( "digit must be an integer" ) ;
105
+ if ( roundFn !== Math . round && roundFn !== Math . ceil && roundFn !== Math . floor )
106
+ throw ( "only Math.round, Math.ceil and Math.floor allowed as roundFn" ) ;
107
+
108
+ const tenPower = Math . pow ( 10 , digit ) ;
109
+ const roundedValue = roundFn ( value * tenPower ) / tenPower ;
110
+
111
+ return precise ( roundedValue , PRECISION ) ;
112
+ }
113
+
114
+ /**
115
+ * Returns the least significant digit of the number.
116
+ *
117
+ * This function is valid for numbers up to a precision
118
+ * of 15 (numbers with 15 digits without leading or trailing
119
+ * zeroes)
120
+ *
121
+ * lsd > 0: digit after decimal point
122
+ *
123
+ * lsd <= 0: digit before decimal point
124
+ *
125
+ * @author localhorst87
126
+ * @function
127
+ * @param {number } value
128
+ * @returns {number } least significant digit
129
+ */
130
+ function getLsd ( value ) {
131
+ if ( typeof ( value ) !== "number" )
132
+ throw ( "value must be numeric" ) ;
133
+
134
+ if ( value === 0 )
135
+ return 0 ;
136
+
137
+ value = precise ( value , PRECISION ) ;
138
+
139
+ let lsd = 0 ;
140
+
141
+ if ( Number . isInteger ( value ) ) {
142
+ while ( value - roundToDigit ( value , -- lsd , Math . round ) == 0 ) continue ;
143
+ lsd += 1 ;
144
+ }
145
+ else
146
+ while ( value - roundToDigit ( value , ++ lsd , Math . round ) !== 0 ) continue ;
147
+
148
+ return lsd ;
149
+ }
150
+
151
+ /**
152
+ * Rounds a number to the nearest number that
153
+ * can be divided by the given interval
154
+ *
155
+ * @author localhorst87
156
+ * @function
157
+ * @param {number } value raw numeric value
158
+ * @param {number } interval interval to round to
159
+ * @returns {number } number with applied interval
160
+ */
161
+ function roundToInterval ( value , interval ) {
162
+ if ( typeof ( value ) !== "number" )
163
+ throw ( "value must be a number" ) ;
164
+ if ( typeof ( interval ) !== "number" )
165
+ throw ( "interval must be a number" ) ;
166
+ if ( interval <= 0 )
167
+ throw ( "interval must be > 0" ) ;
168
+
169
+ const lsd = Math . max ( getLsd ( interval ) , getLsd ( value ) ) ;
170
+ const remainder = roundToDigit ( mod ( value , interval ) , lsd ) ; // use round to digit to avoid floating point errors
171
+
172
+ if ( value >= 0 )
173
+ return remainder < interval / 2 ? roundToDigit ( value - remainder , lsd ) : roundToDigit ( value - remainder + interval , lsd ) ;
174
+ else
175
+ return Math . abs ( remainder ) <= interval / 2 ? roundToDigit ( value - remainder , lsd ) : roundToDigit ( value - remainder - interval , lsd ) ;
176
+ }
177
+
178
+ /**
179
+ * Floors a number to the nearest number that
180
+ * can be divided by the given interval
181
+ *
182
+ * @author localhorst87
183
+ * @function
184
+ * @param {number } value raw numeric value
185
+ * @param {number } interval interval to round to
186
+ * @returns {number } number with applied interval
187
+ */
188
+ function floorToInterval ( value , interval ) {
189
+ if ( typeof ( value ) !== "number" )
190
+ throw ( "value must be a number" ) ;
191
+ if ( typeof ( interval ) !== "number" )
192
+ throw ( "interval must be a number" ) ;
193
+ if ( interval <= 0 )
194
+ throw ( "interval must be > 0" ) ;
195
+
196
+ const lsd = Math . max ( getLsd ( interval ) , getLsd ( value ) ) ;
197
+ const remainder = roundToDigit ( mod ( value , interval ) , lsd ) ; // use round to digit to avoid floating point errors
198
+
199
+ if ( value >= 0 )
200
+ return roundToDigit ( value - remainder , lsd ) ;
201
+ else
202
+ return Math . abs ( remainder ) > 0 ? roundToDigit ( value - remainder - interval , lsd ) : value ;
203
+ }
204
+
205
+ /**
206
+ * Ceils a number to the nearest number that
207
+ * can be divided by the given interval
208
+ *
209
+ * @author localhorst87
210
+ * @function
211
+ * @param {number } value raw numeric value
212
+ * @param {number } interval interval to round to
213
+ * @returns {number } number with applied interval
214
+ */
215
+ function ceilToInterval ( value , interval ) {
216
+ if ( typeof ( value ) !== "number" )
217
+ throw ( "value must be a number" ) ;
218
+ if ( typeof ( interval ) !== "number" )
219
+ throw ( "interval must be a number" ) ;
220
+ if ( interval <= 0 )
221
+ throw ( "interval must be > 0" ) ;
222
+
223
+ const lsd = Math . max ( getLsd ( interval ) , getLsd ( value ) ) ;
224
+ const remainder = roundToDigit ( mod ( value , interval ) , lsd ) ; // use round to digit to avoid floating point errors
225
+
226
+ if ( value >= 0 )
227
+ return remainder > 0 ? roundToDigit ( value - remainder + interval , lsd ) : value ;
228
+ else
229
+ return roundToDigit ( value - remainder , lsd ) ;
230
+ }
231
+
232
+ /**
233
+ * floating-point safe modulo operator
234
+ *
235
+ * @author localhorst87
236
+ * @function
237
+ * @param {number } value
238
+ * @param {number } divisor
239
+ * @returns {number } remainder of value / divisor
240
+ */
241
+ function mod ( value , divisor ) {
242
+ if ( typeof ( value ) !== "number" )
243
+ throw ( "value must be numeric" ) ;
244
+ if ( typeof ( divisor ) !== "number" )
245
+ throw ( "divisor must be numeric" ) ;
246
+
247
+ const lsd = Math . max ( getLsd ( value ) , getLsd ( divisor ) ) ;
248
+ const remainderPower = ( value * Math . pow ( 10 , lsd ) ) % ( divisor * Math . pow ( 10 , lsd ) ) ;
249
+
250
+ return remainderPower / Math . pow ( 10 , lsd ) ;
251
+ }
252
+
253
+ /**
254
+ * Checks if the XML model description file fulfills the given XSD scheme
255
+ * @author lvtan
256
+ * @function
257
+ * @param {String } xmlString XML-validated model description
258
+ * @param {String } xsdFilePath path of xsd file that is used to validate
259
+ * @return {Boolean } returns true/false upon valid/invalid scheme
260
+ */
261
+ function validateXMLAgainstXSD ( xmlString , xsdFilePath ) {
262
+ var xsdString = fileSync . readFileSync ( xsdFilePath , 'utf8' ) ;
263
+ var modelDescXsdBasePath = path . dirname ( path . resolve ( xsdFilePath ) ) + "/"
264
+ let parsedXsd = xsdValidator . parse ( xsdString , { baseUrl : modelDescXsdBasePath } ) ;
265
+ let validationErrors = parsedXsd . validate ( xmlString ) ;
266
+ return validationErrors == null ;
267
+ }
268
+
269
+ /**
270
+ * Checks if the XML structure of the given XML string is valid
271
+ * @author lvtan
272
+ * @function
273
+ * @param {String } xmlString XML as plain text
274
+ * @return {Boolean } returns true/false upon valid/invalid XML format
275
+ */
276
+
277
+ function validateXML ( xmlString ) {
278
+ const validationOptions = {
279
+ allowBooleanAttributes : true
280
+ } ;
281
+ const validationResult = XMLValidator . validate ( xmlString , validationOptions ) ;
282
+
283
+ return validationResult == true ;
284
+ }
285
+
286
+ /**
287
+ * Convert XML string to JSON
288
+ *
289
+ * @author lvtan3005
290
+ * @function
291
+ * @param {string } xmlString the xml at the string format
292
+ * @returns {object } json object after parsing
293
+ */
294
+
295
+ function parseXMLToJson ( xmlString ) {
296
+ const parserOptions = {
297
+ ignoreAttributes : false
298
+ } ;
299
+ const xmlParser = new XMLParser ( parserOptions ) ;
300
+ return xmlParser . parse ( xmlString ) ;
301
+ }
0 commit comments