b311480e |
1 | // Created on: 2001-07-20 |
2 | // Created by: Alexander GRIGORIEV |
973c2be1 |
3 | // Copyright (c) 2001-2014 OPEN CASCADE SAS |
b311480e |
4 | // |
973c2be1 |
5 | // This file is part of Open CASCADE Technology software library. |
b311480e |
6 | // |
d5f74e42 |
7 | // This library is free software; you can redistribute it and/or modify it under |
8 | // the terms of the GNU Lesser General Public License version 2.1 as published |
973c2be1 |
9 | // by the Free Software Foundation, with special exception defined in the file |
10 | // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT |
11 | // distribution for complete text of the license and disclaimer of any warranty. |
b311480e |
12 | // |
973c2be1 |
13 | // Alternatively, this file may be used under the terms of Open CASCADE |
14 | // commercial license or contractual agreement. |
b311480e |
15 | |
16 | //AGV 060302: Input from istream |
7fd59977 |
17 | // AGV 130302: bug corr: was error if strlen(root_elem_name) < 7 |
18 | |
19 | #include <LDOM_XmlReader.hxx> |
20 | #include <Standard_Stream.hxx> |
21 | #include <LDOM_MemManager.hxx> |
22 | #include <LDOM_BasicAttribute.hxx> |
23 | #include <LDOM_CharReference.hxx> |
24 | #include <LDOM_OSStream.hxx> |
25 | |
26 | #include <string.h> |
27 | #include <errno.h> |
57c28b61 |
28 | #ifdef _MSC_VER |
7fd59977 |
29 | #include <io.h> |
30 | #else |
31 | #include <unistd.h> |
32 | #endif |
33 | |
34 | //#include <ctype.h> |
35 | |
36 | const int XML_MIN_BUFFER = 10; |
7fd59977 |
37 | const int FILE_NONVALUE = -1; |
38 | |
39 | typedef enum { |
40 | STATE_WAITING = 0, |
41 | STATE_HEADER, |
42 | STATE_DOCTYPE, |
43 | STATE_DOCTYPE_MARKUP, |
44 | STATE_ELEMENT, |
45 | STATE_ELEMENT_END, |
46 | STATE_ATTRIBUTE_NAME, |
47 | STATE_ATTRIBUTE_EQUAL, |
48 | STATE_ATTRIBUTE_VALUE, |
49 | STATE_COMMENT, |
50 | STATE_CDATA, |
51 | STATE_TEXT |
52 | } ParserState; |
53 | |
54 | #define TEXT_COMPARE(aPtr,aPattern) \ |
55 | (memcmp ((aPtr), (aPattern), sizeof(aPattern) - 1) == 0) |
56 | |
57 | static Standard_Boolean isName (const char * aString, |
58 | const char * aStringEnd, |
59 | const char *& aNameEnd); |
60 | |
61 | //======================================================================= |
62 | //function : LDOM_XmlReader() |
63 | //purpose : Constructor (file descriptor) |
64 | //======================================================================= |
65 | |
4ff92abe |
66 | LDOM_XmlReader::LDOM_XmlReader ( |
cbff1e55 |
67 | const Handle(LDOM_MemManager)& theDocument, |
68 | TCollection_AsciiString& theErrorString) |
69 | : myEOF (Standard_False), |
cbff1e55 |
70 | myError (theErrorString), |
71 | myDocument (theDocument), |
72 | myElement (NULL), |
73 | myLastChild(NULL), |
74 | myPtr (&myBuffer[0]), |
75 | myEndPtr (&myBuffer[0]) |
76 | { |
77 | } |
7fd59977 |
78 | |
79 | //======================================================================= |
80 | //function : ReadRecord |
81 | //purpose : Read a record from XML file |
82 | //======================================================================= |
83 | |
4ff92abe |
84 | LDOM_XmlReader::RecordType LDOM_XmlReader::ReadRecord (Standard_IStream& theIStream, |
85 | LDOM_OSStream& theData) |
7fd59977 |
86 | { |
87 | theData.Clear(); |
88 | myError.Clear(); |
89 | ParserState aState = STATE_WAITING; |
1d47d8d0 |
90 | const char * aStartData = NULL, * aNameEnd = NULL, * aPtr; |
7fd59977 |
91 | LDOMBasicString anAttrName, anAttrValue; |
92 | char anAttDelimiter = '\0'; |
93 | |
302f96fb |
94 | for(;;) { |
7fd59977 |
95 | // Check if the current file buffer is exhausted |
96 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
97 | // There should always be some bytes available in the buffer for analysis |
7dc9e047 |
98 | Standard_Integer aBytesRest = (Standard_Integer)(myEndPtr - myPtr); |
7fd59977 |
99 | if (aBytesRest < XML_MIN_BUFFER) { |
100 | if (myEOF == Standard_True) { |
101 | if (aBytesRest <= 0) |
102 | break; // END of processing |
103 | } else { |
104 | // If we are reading some data, save the beginning and preserve the state |
105 | if (aStartData /* && aState != STATE_WAITING */) { |
106 | if (myPtr > aStartData) |
107 | theData.rdbuf()->sputn(aStartData, myPtr - aStartData); |
108 | aStartData = &myBuffer[0]; |
109 | } |
110 | // Copy the rest of file data to the beginning of buffer |
111 | if (aBytesRest > 0) |
112 | memcpy (&myBuffer[0], myPtr, aBytesRest); |
113 | |
114 | // Read the full buffer and reset start and end buffer pointers |
115 | myPtr = &myBuffer[0]; |
60be1f9b |
116 | Standard_Size aNBytes; |
4ff92abe |
117 | theIStream.read (&myBuffer[aBytesRest], |
7fd59977 |
118 | XML_BUFFER_SIZE - aBytesRest); |
4ff92abe |
119 | aNBytes = (Standard_Size)theIStream.gcount(); |
7fd59977 |
120 | if (aNBytes == 0) |
121 | myEOF = Standard_True; // END-OF-FILE |
122 | myEndPtr = &myBuffer[aBytesRest + aNBytes]; |
123 | myBuffer[aBytesRest + aNBytes] = '\0'; |
124 | } |
125 | } |
126 | |
127 | // Check the character data |
128 | switch (aState) { |
129 | |
130 | // Checking the characters in STATE_WAITING (blank, TEXT or markup) |
131 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
132 | case STATE_WAITING: |
133 | switch (myPtr[0]) { |
134 | case ' ': |
135 | case '\t': |
136 | case '\n': |
137 | case '\r': |
138 | ++ myPtr; |
139 | continue; |
140 | case '<': |
141 | // XML markup found, then make detect the record type |
142 | switch (myPtr[1]) { |
143 | case '?': |
144 | aState = STATE_HEADER; |
145 | myPtr += 2; |
146 | aStartData = myPtr; |
147 | continue; |
148 | case '/': |
149 | aState = STATE_ELEMENT_END; |
150 | myPtr += 2; |
151 | aStartData = myPtr; |
152 | continue; |
153 | case '!': |
154 | if (myPtr[2] == '-' && myPtr[3] == '-') { |
155 | aState = STATE_COMMENT; |
156 | myPtr += 4; |
157 | } else if (TEXT_COMPARE (&myPtr[2], "DOCTYPE")) { |
158 | char ch = myPtr[9]; |
159 | if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r') |
160 | break; |
161 | aState = STATE_DOCTYPE; |
162 | myPtr += 10; |
163 | } else if (TEXT_COMPARE (&myPtr[2], "[CDATA[")) { |
164 | aState = STATE_CDATA; |
165 | myPtr += 9; |
166 | } else break; // ERROR |
167 | aStartData = myPtr; |
168 | continue; |
169 | default: |
170 | if (::isName (&myPtr[1], myEndPtr, aNameEnd)) { |
171 | aStartData = myPtr + 1; |
172 | myPtr = aNameEnd; |
173 | if (myPtr < myEndPtr) { |
174 | myElement = & LDOM_BasicElement::Create (aStartData, |
7dc9e047 |
175 | (Standard_Integer)(myPtr - aStartData), |
7fd59977 |
176 | myDocument); |
177 | myLastChild = NULL; |
178 | aState = STATE_ATTRIBUTE_NAME; |
179 | aStartData = NULL; |
180 | }else |
181 | aState = STATE_ELEMENT; |
182 | continue; |
183 | } // otherwise ERROR |
184 | } // end of switch |
185 | myError = "Unknown XML object: "; |
186 | myError += TCollection_AsciiString ((const Standard_CString)myPtr, |
187 | XML_MIN_BUFFER); |
188 | return XML_UNKNOWN; |
189 | case '\0': |
190 | if (myEOF == Standard_True) continue; |
191 | default: |
192 | // Limitation: we do not treat '&' as special character |
193 | aPtr = (const char *) memchr (myPtr, '<', myEndPtr - myPtr); |
194 | if (aPtr) { |
195 | // The end of text field reached |
196 | theData.rdbuf()->sputn(myPtr, aPtr - myPtr); |
197 | myPtr = aPtr; |
198 | return XML_TEXT; |
199 | } |
200 | aState = STATE_TEXT; |
201 | aStartData = myPtr; |
202 | myPtr = myEndPtr; |
203 | } // end of checking in STATE_WAITING |
204 | continue; |
205 | |
206 | // Checking the characters in STATE_HEADER, seek for "?>" sequence |
207 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
208 | case STATE_HEADER: |
209 | aPtr = (const char *) memchr (aStartData, '?', (myEndPtr-1) - aStartData); |
210 | if (aPtr) { |
211 | // The end of XML declaration found |
212 | if (aPtr[1] != '>') { // ERROR |
213 | myError = "Character \'>\' is expected in the end of XML declaration"; |
214 | return XML_UNKNOWN; |
215 | } |
216 | // The XML declaration is retrieved |
217 | theData.rdbuf()->sputn(aStartData, aPtr - aStartData); |
218 | myPtr = aPtr + 2; |
219 | return XML_HEADER; |
220 | } |
221 | myPtr = myEndPtr - 1; |
222 | continue; |
223 | |
224 | // Checking the characters in STATE_DOCTYPE, seek for "]>" sequence |
225 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
226 | case STATE_DOCTYPE: |
227 | for (aPtr = aStartData; aPtr < myEndPtr-1; aPtr++) { |
228 | const int aChar = aPtr[0]; |
229 | if (aChar == '[') { |
230 | aState = STATE_DOCTYPE_MARKUP; |
231 | aStartData = &aPtr[1]; |
232 | goto state_doctype_markup; |
233 | } |
234 | if (aChar == '>') { |
235 | // The DOCTYPE declaration is retrieved |
236 | theData.rdbuf()->sputn(aStartData, aPtr - aStartData - 1); |
237 | myPtr = aPtr + 1; |
238 | return XML_DOCTYPE; |
239 | } |
240 | } |
241 | myPtr = myEndPtr - 1; |
242 | continue; |
243 | |
244 | state_doctype_markup: |
245 | case STATE_DOCTYPE_MARKUP: |
246 | aPtr = (const char *) memchr (aStartData, ']', (myEndPtr-1) - aStartData); |
247 | if (aPtr) { |
248 | // The end of DOCTYPE declaration found |
249 | if (aPtr[1] != '>') { // ERROR |
250 | myError = |
251 | "Character \'>\' is expected in the end of DOCTYPE declaration"; |
252 | return XML_UNKNOWN; |
253 | } |
254 | // The DOCTYPE declaration is retrieved |
255 | theData.rdbuf()->sputn(aStartData, aPtr - aStartData); |
256 | myPtr = aPtr + 2; |
257 | return XML_DOCTYPE; |
258 | } |
259 | myPtr = myEndPtr - 1; |
260 | continue; |
261 | |
262 | // Checking the characters in STATE_COMMENT, seek for "-->" sequence |
263 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
264 | case STATE_COMMENT: |
265 | aPtr = aStartData; |
302f96fb |
266 | for(;;) { |
7fd59977 |
267 | aPtr = (const char *) memchr (aPtr, '-', (myEndPtr - 2) - aPtr); |
268 | if (aPtr == NULL) break; |
269 | if (aPtr[1] != '-') ++ aPtr; |
270 | else { |
271 | if (aPtr[2] != '>') { // ERROR |
272 | myError = "Character \'>\' is expected in the end of comment"; |
273 | return XML_UNKNOWN; |
274 | } |
275 | theData.rdbuf()->sputn(aStartData, aPtr - aStartData); |
276 | myPtr = aPtr + 3; |
277 | return XML_COMMENT; |
278 | } |
279 | } |
280 | myPtr = myEndPtr - 2; |
281 | continue; |
282 | |
283 | // Checking the characters in STATE_TEXT, seek for "<" |
284 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
285 | case STATE_TEXT: |
286 | aPtr = (const char *) memchr (aStartData, '<', myEndPtr - aStartData); |
287 | if (aPtr) { |
288 | // The end of text field reached |
289 | theData.rdbuf()->sputn(aStartData, aPtr - aStartData); |
290 | myPtr = aPtr; |
291 | return XML_TEXT; |
292 | } |
293 | myPtr = myEndPtr; |
294 | continue; |
295 | |
296 | // Checking the characters in STATE_CDATA, seek for "]]" |
297 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
298 | case STATE_CDATA: |
299 | aPtr = aStartData; |
302f96fb |
300 | for(;;) { |
7fd59977 |
301 | aPtr = (const char *) memchr (aPtr, ']', (myEndPtr - 1) - aStartData); |
302 | if (aPtr == NULL) break; |
303 | if (aPtr[1] != ']') { // ERROR |
304 | myError = "Characters \']]\' are expected in the end of CDATA"; |
305 | return XML_UNKNOWN; |
306 | } |
307 | theData.rdbuf()->sputn(aStartData, aPtr - aStartData); |
308 | myPtr = aPtr + 2; |
309 | return XML_CDATA; |
310 | } |
311 | myPtr = myEndPtr - 1; |
312 | continue; |
313 | |
314 | // Checking the characters in STATE_ELEMENT, seek the end of TagName |
315 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
316 | case STATE_ELEMENT: |
317 | if (::isName (myPtr, myEndPtr, aNameEnd) == Standard_False) |
318 | if (theData.Length() == 0 || aNameEnd != myPtr) { |
319 | myError = "Invalid tag name"; |
320 | return XML_UNKNOWN; |
321 | } |
322 | { |
323 | theData.rdbuf()->sputn(aStartData, aNameEnd - aStartData); |
324 | char* aDataString = (char *)theData.str(); |
325 | myElement = & LDOM_BasicElement::Create (aDataString, theData.Length(), |
326 | myDocument); |
327 | theData.Clear(); |
328 | myLastChild = NULL; |
329 | delete [] aDataString; |
330 | aState = STATE_ATTRIBUTE_NAME; |
331 | aStartData = NULL; |
332 | myPtr = aNameEnd; |
333 | continue; |
334 | } |
335 | // Parsing a single attribute (STATE_ATTRIBUTE) |
336 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
337 | case STATE_ATTRIBUTE_NAME: // attribute name |
338 | switch (myPtr[0]) { |
339 | case ' ' : |
340 | case '\t': |
341 | case '\n': |
342 | case '\r': |
343 | if (aStartData) goto attr_name; |
344 | ++ myPtr; |
345 | continue; |
346 | case '/' : |
347 | if (aStartData) |
348 | myError = "Inexpected end of attribute"; |
349 | else if (myPtr[1] != '>') |
350 | myError = "Improper element tag termination"; |
351 | else { |
352 | myPtr += 2; |
0797d9d3 |
353 | #ifdef OCCT_DEBUG |
7fd59977 |
354 | theData.Clear(); |
355 | theData << myElement->GetTagName(); |
356 | #endif |
357 | return XML_FULL_ELEMENT; |
358 | } |
359 | return XML_UNKNOWN; |
360 | case '>' : |
361 | if (aStartData) { |
362 | myError = "Inexpected end of attribute"; |
363 | return XML_UNKNOWN; |
364 | } |
365 | ++ myPtr; |
0797d9d3 |
366 | #ifdef OCCT_DEBUG |
7fd59977 |
367 | theData.Clear(); |
368 | theData << myElement->GetTagName(); |
369 | #endif |
370 | return XML_START_ELEMENT; |
371 | default : |
372 | if (::isName (myPtr, myEndPtr, aNameEnd) == Standard_False) |
373 | if (theData.Length() == 0 || aNameEnd != myPtr) { |
374 | myError = "Invalid attribute name"; |
375 | return XML_UNKNOWN; |
376 | } |
377 | if (aNameEnd >= myEndPtr) |
378 | aStartData = myPtr; |
379 | else { |
380 | if (theData.Length() == 0) |
7dc9e047 |
381 | anAttrName = LDOMBasicString(myPtr, (Standard_Integer)(aNameEnd - myPtr), myDocument); |
7fd59977 |
382 | else { |
383 | theData.rdbuf()->sputn(myPtr, aNameEnd - myPtr); |
384 | attr_name: |
385 | char* aDataString = (char *)theData.str(); |
386 | theData.Clear(); |
387 | anAttrName = LDOMBasicString (aDataString, myDocument); |
388 | delete [] aDataString; |
389 | } |
390 | aStartData = NULL; |
391 | aState = STATE_ATTRIBUTE_EQUAL; |
392 | } |
393 | myPtr = aNameEnd; |
394 | continue; |
395 | } |
396 | case STATE_ATTRIBUTE_EQUAL: // attribute 'equal' sign |
397 | switch (myPtr[0]) { |
398 | case '=' : |
399 | aState = STATE_ATTRIBUTE_VALUE; |
400 | case ' ' : |
401 | case '\t': |
402 | case '\n': |
403 | case '\r': |
404 | ++ myPtr; |
405 | continue; |
406 | default: |
407 | myError = "Equal sign expected in attribute definition"; |
408 | return XML_UNKNOWN; |
409 | } |
410 | |
411 | case STATE_ATTRIBUTE_VALUE: // attribute value |
412 | switch (myPtr[0]) { |
413 | case ' ' : |
414 | case '\t': |
415 | case '\n': |
416 | case '\r': |
417 | if (aStartData == NULL) { |
418 | ++ myPtr; |
419 | continue; |
420 | default: |
421 | if (anAttDelimiter == '\0') { |
422 | myError = "Expected an attribute value"; |
423 | return XML_UNKNOWN; |
424 | case '\"': |
425 | case '\'': |
426 | if (aStartData == NULL) { |
427 | aStartData = &myPtr[1]; |
428 | anAttDelimiter = myPtr[0]; |
429 | } |
430 | } |
431 | } |
432 | // Limitation: we do not take into account that '<' and '&' |
433 | // are not allowed in attribute values |
434 | aPtr = (const char *) memchr (aStartData, anAttDelimiter, |
435 | myEndPtr - aStartData); |
436 | if (aPtr) { |
437 | (char&) aPtr[0] = '\0'; |
438 | anAttDelimiter = '\0'; |
439 | char * aDataString = (char *) aStartData; |
440 | const char * ePtr = aPtr; |
441 | |
442 | // Append the end of the string to previously taken data |
443 | if (theData.Length() > 0) { |
444 | theData.rdbuf()->sputn(aStartData, aPtr-aStartData); |
445 | aDataString = (char *)theData.str(); |
446 | ePtr = strchr (aDataString, '\0'); |
447 | } |
448 | |
449 | Standard_Integer aDataLen; |
450 | aDataString = LDOM_CharReference::Decode (aDataString, aDataLen); |
451 | if (IsDigit(aDataString[0])) { |
452 | if (getInteger (anAttrValue, aDataString, ePtr)) |
453 | anAttrValue = LDOMBasicString (aDataString,aDataLen,myDocument); |
454 | } else |
455 | anAttrValue = LDOMBasicString (aDataString, aDataLen, myDocument); |
456 | |
457 | if (theData.Length() > 0) { |
458 | theData.Clear(); |
459 | delete [] aDataString; |
460 | } |
461 | // Create an attribute |
462 | myLastChild = myElement -> AddAttribute (anAttrName, anAttrValue, |
463 | myDocument, myLastChild); |
464 | myPtr = aPtr + 1; |
465 | aStartData = NULL; |
466 | aState = STATE_ATTRIBUTE_NAME; |
467 | } else |
468 | myPtr = myEndPtr; |
469 | continue; |
470 | } |
471 | // Checking the characters in STATE_ELEMENT_END, seek for ">" |
472 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
473 | case STATE_ELEMENT_END: |
474 | aPtr = (const char *) memchr (aStartData, '>', myEndPtr - aStartData); |
475 | if (aPtr) { |
476 | // The end of the end-element markup |
477 | theData.rdbuf()->sputn(aStartData, aPtr - aStartData); |
478 | myPtr = aPtr + 1; |
479 | return XML_END_ELEMENT; |
480 | } |
481 | myPtr = myEndPtr; |
482 | continue; |
483 | } |
484 | } |
485 | if (aState != STATE_WAITING) { |
486 | myError = "Unexpected end of file"; |
487 | return XML_UNKNOWN; |
488 | } |
489 | return XML_EOF; |
490 | } |
491 | |
492 | //======================================================================= |
493 | //function : isName |
494 | //type : static |
495 | //purpose : Check if aString is a valid XML Name |
496 | //======================================================================= |
497 | |
498 | static Standard_Boolean isName (const char * aString, |
499 | const char * aStringEnd, |
500 | const char *& aNameEnd) |
501 | { |
502 | Standard_Boolean aResult; |
302f96fb |
503 | char aCh = aString[0]; |
7fd59977 |
504 | if (IsAlphabetic(aCh) || aCh == '_' || aCh == ':') { |
505 | const char * aPtr = &aString[1]; |
506 | while (aPtr < aStringEnd) { |
507 | aCh = * aPtr; |
508 | switch (aCh) { |
509 | case ' ' : |
510 | case '\n': |
511 | case '\r': |
512 | case '\t': |
513 | case '=' : |
514 | case '\0': |
515 | case '/' : |
516 | case '>' : |
517 | aNameEnd = aPtr; |
518 | return Standard_True; |
519 | default: |
520 | if (IsAlphanumeric(aCh) == 0) { |
521 | aNameEnd = aPtr; |
522 | return Standard_False; |
523 | } |
524 | case '.' : |
525 | case '-' : |
526 | case '_' : |
527 | case ':' : |
528 | ++ aPtr; |
529 | } |
530 | } |
531 | aNameEnd = aPtr; |
532 | aResult = Standard_True; |
533 | } else { |
534 | aNameEnd = aString; |
535 | aResult = Standard_False; |
536 | } |
537 | return aResult; |
538 | } |
539 | |
540 | //======================================================================= |
541 | //function : getInteger |
542 | //purpose : Try to initialize theValue as Integer; return False on success |
543 | //======================================================================= |
544 | |
545 | Standard_Boolean LDOM_XmlReader::getInteger (LDOMBasicString& theValue, |
546 | const char * theStart, |
547 | const char * theEnd) |
548 | { |
549 | char * ptr; |
550 | errno = 0; |
551 | if (theEnd - theStart == 1 || theStart[0] != '0') |
552 | { |
553 | long aResult = strtol (theStart, &ptr, 10); |
554 | if (ptr == theEnd && errno == 0) |
555 | { |
556 | theValue = Standard_Integer(aResult); |
557 | return Standard_False; |
558 | } |
559 | } |
560 | return Standard_True; |
561 | } |