|
1 /* |
|
2 * Copyright (c) 2006-2007 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of the License "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: ?Description |
|
15 * |
|
16 */ |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 // INCLUDE FILES |
|
22 #include <e32svr.h> |
|
23 |
|
24 #include <LiwServiceHandler.h> |
|
25 #include <Liwbufferextension.h> |
|
26 #include <gmxmldocument.h> |
|
27 #include <gmxmlelement.h> |
|
28 #include <gmxmlcharacterdata.h> |
|
29 #include <charconv.H> |
|
30 #include <gmxmltext.h> |
|
31 |
|
32 #include "txmlconvertor.h" |
|
33 |
|
34 #include <f32file.h> |
|
35 #include <e32des16.h> |
|
36 #include <e32base.h> |
|
37 |
|
38 #include "tsnsrprovidertestobserver.h" |
|
39 |
|
40 _LIT8(KErrorCode,"ErrorCode"); |
|
41 |
|
42 |
|
43 // Define constants |
|
44 const TInt KTYPE_ID_LEN = 7; |
|
45 const TInt KENTRY_LEN = 5; |
|
46 const TInt KINT_LEN = 3; |
|
47 const TInt KBOOL_LEN = 4; |
|
48 const TInt KDATE_LEN = 4; |
|
49 const TInt KFLOAT_LEN = 5; |
|
50 const TInt KMAX_LEN = 255; |
|
51 const TInt KSTRING_LEN = 8; |
|
52 const TInt KLIST_LEN = 4; |
|
53 const TInt KITER_LEN = 8; |
|
54 const TInt KMAP_LEN = 3; |
|
55 const TInt KKEY_LEN = 3; |
|
56 const TInt KNAME_LEN = 4; |
|
57 const TInt KPARAMS_LEN = 6; |
|
58 const TInt KPARAM_LEN = 5; |
|
59 const TInt KDateLen = 25; |
|
60 |
|
61 const TInt KMaxOutputLen = 1000; |
|
62 |
|
63 |
|
64 |
|
65 _LIT(KAttribute, "type_id"); |
|
66 _LIT(KKeyAttribute, "name"); |
|
67 _LIT(KInt, "int"); |
|
68 _LIT(KFloat, "float"); |
|
69 _LIT(KString, "string"); |
|
70 _LIT(KString8, "string8"); |
|
71 _LIT(KList, "list"); |
|
72 _LIT(KMap, "map"); |
|
73 _LIT(KKey, "key"); |
|
74 _LIT(KBool, "bool"); |
|
75 _LIT(KDate, "date"); |
|
76 |
|
77 _LIT(KEntry, "entry"); |
|
78 _LIT(KIterator, "iterator"); |
|
79 _LIT(KOpenTag, "<"); |
|
80 _LIT(KFinalCloseTag, "/>"); |
|
81 _LIT(KCloseTag, ">"); |
|
82 _LIT(KOpenTagSlash, "</"); |
|
83 _LIT(KParams, "params"); |
|
84 _LIT(KParam, "param"); |
|
85 _LIT(KNameAttribute, "name"); |
|
86 _LIT(KSTDDATE, "19700001:000000.000000"); |
|
87 |
|
88 |
|
89 |
|
90 // Converts the output params from LIW specific data type to xml based flash |
|
91 // specific data type. |
|
92 |
|
93 void CXmlConvertor::BuildOutputTreeL(CLiwGenericParamList& aOutParamList,TPtrC& aPtr) |
|
94 { |
|
95 CMDXMLDocument* pDocNode = CMDXMLDocument::NewLC(); |
|
96 if (pDocNode) |
|
97 { |
|
98 TBuf<KPARAMS_LEN> paramsNode(KParams); |
|
99 CMDXMLElement* pParamsNode = CMDXMLElement::NewLC(ETrue,pDocNode,paramsNode); |
|
100 |
|
101 if (pParamsNode) |
|
102 { |
|
103 for(TInt i=0; i < aOutParamList.Count(); ++i) |
|
104 { |
|
105 //create <param name=""/> tag |
|
106 TBuf<KPARAM_LEN> paramNode(KParam); |
|
107 CMDXMLElement* pParamNode = CMDXMLElement::NewLC(ETrue,pDocNode,paramNode); |
|
108 if (pParamNode) |
|
109 { |
|
110 TBuf<KMAX_LEN> paramName; |
|
111 TLiwGenericParam outParam = aOutParamList[i]; |
|
112 |
|
113 if(outParam.Name().Length()!=0) |
|
114 { |
|
115 paramName.Copy(outParam.Name()); |
|
116 } |
|
117 else |
|
118 { |
|
119 TInt semID = outParam.SemanticId(); |
|
120 const TDesC8& strParam = this->GetParamAsString(semID); |
|
121 paramName.Copy(strParam); |
|
122 } |
|
123 |
|
124 TBuf<KNAME_LEN> nameAttr(KKeyAttribute); |
|
125 //Create an attribute "name" |
|
126 pParamNode->SetAttributeL(nameAttr,paramName); |
|
127 |
|
128 pParamsNode->AppendChild(pParamNode); |
|
129 |
|
130 AppendOutParamL(pDocNode,pParamNode,outParam.Value()); |
|
131 CleanupStack::Pop(pParamNode); |
|
132 } |
|
133 } |
|
134 // Convert the output to string. |
|
135 ToStringL(pParamsNode, aPtr); |
|
136 |
|
137 CleanupStack::PopAndDestroy(pParamsNode); |
|
138 } |
|
139 CleanupStack::PopAndDestroy(pDocNode); |
|
140 } |
|
141 } |
|
142 |
|
143 |
|
144 // Converts the TGenericParamId passed by the service provider |
|
145 // to string, as we can pass only string values to flash content. |
|
146 const TDesC8& CXmlConvertor::GetParamAsString(TGenericParamId aParamID) |
|
147 { |
|
148 switch(aParamID) |
|
149 { |
|
150 case EGenericParamError: |
|
151 { |
|
152 return KErrorCode(); |
|
153 } |
|
154 |
|
155 //Convert other generic param IDs if required |
|
156 default: |
|
157 break; |
|
158 } |
|
159 return KNullDesC8(); |
|
160 } |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 //Output parameter processing |
|
169 // Appends the value in TLiwVariant to the XML tree. |
|
170 void CXmlConvertor::AppendOutParamL(CMDXMLDocument* aDocNode, CMDXMLElement* aParentNode, |
|
171 const TLiwVariant& aParamVar) |
|
172 { |
|
173 if (aDocNode && aParentNode) |
|
174 { |
|
175 TBuf<KTYPE_ID_LEN> typeIdAttr(KAttribute); |
|
176 TBuf<KENTRY_LEN> entryNode(KEntry); |
|
177 CMDXMLElement* pEntryNode = CMDXMLElement::NewL(ETrue,aDocNode,entryNode); |
|
178 if (pEntryNode) |
|
179 { |
|
180 CleanupStack::PushL(pEntryNode); |
|
181 |
|
182 if(aParamVar.TypeId()==EVariantTypeTInt32) |
|
183 { |
|
184 //Form attribute "name" with value "int" |
|
185 TBuf<KINT_LEN> typeIdValue(KInt); |
|
186 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
187 |
|
188 //Create text node with value equivalent to aParamVar.AsTInt32() |
|
189 CMDXMLText* pValueNode = CMDXMLText::NewL(aDocNode); |
|
190 CleanupStack::PushL(pValueNode); |
|
191 |
|
192 TInt intValue = aParamVar.AsTInt32(); |
|
193 TInt len = GetLengthToStoreInt(intValue); |
|
194 HBufC* hInt = HBufC::NewLC(len+1); |
|
195 TPtr intPtr(hInt->Des()); |
|
196 intPtr.Num(intValue); |
|
197 |
|
198 pValueNode->SetDataL(intPtr); |
|
199 pEntryNode->AppendChild(pValueNode); |
|
200 |
|
201 CleanupStack::PopAndDestroy(hInt); |
|
202 CleanupStack::Pop(pValueNode); |
|
203 } |
|
204 else if(aParamVar.TypeId()==EVariantTypeTBool) |
|
205 { |
|
206 //Form attribute "name" with value "bool" |
|
207 TBuf<KBOOL_LEN> typeIdValue(_L("bool")); |
|
208 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
209 |
|
210 //Create text node with value equivalent to aParamVar.AsTBool() |
|
211 CMDXMLText* pValueNode=CMDXMLText::NewL(aDocNode); |
|
212 CleanupStack::PushL(pValueNode); |
|
213 |
|
214 TBuf<KMAX_LEN> value; |
|
215 value.Num(aParamVar.AsTBool()); |
|
216 |
|
217 pValueNode->SetDataL(value); |
|
218 pEntryNode->AppendChild(pValueNode); |
|
219 CleanupStack::Pop(pValueNode); |
|
220 } |
|
221 else if(aParamVar.TypeId()==EVariantTypeDesC8) |
|
222 { |
|
223 //Form attribute "name" with value "string" |
|
224 TBuf<KSTRING_LEN> typeIdValue(KString8); |
|
225 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
226 |
|
227 //Create text node with value equivalent to aParamVar.AsDes() |
|
228 CMDXMLText* pValueNode=CMDXMLText::NewL(aDocNode); |
|
229 CleanupStack::PushL(pValueNode); |
|
230 |
|
231 TPtrC8 pValue; |
|
232 aParamVar.Get(pValue); |
|
233 HBufC* heapBuffer = HBufC::NewL( pValue.Length() ); |
|
234 CleanupStack::PushL( heapBuffer ); |
|
235 TPtr value( heapBuffer->Des() ); |
|
236 value.Copy( pValue ); |
|
237 pValueNode->SetDataL(value); |
|
238 CleanupStack::PopAndDestroy(); |
|
239 |
|
240 pEntryNode->AppendChild(pValueNode); |
|
241 CleanupStack::Pop(pValueNode); |
|
242 } |
|
243 |
|
244 else if(aParamVar.TypeId()==EVariantTypeTTime) |
|
245 { |
|
246 //Form attribute "name" with value "date" |
|
247 TBuf<KDATE_LEN> typeIdValue(_L("date")); |
|
248 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
249 |
|
250 //Create text node with value equivalent to aParamVar.AsTTime() |
|
251 CMDXMLText* pValueNode=CMDXMLText::NewL(aDocNode); |
|
252 CleanupStack::PushL(pValueNode); |
|
253 |
|
254 // Convert the date as ActionScript requires that the date be passed as |
|
255 // milliseconds since midnight January 1, 1970. |
|
256 TBuf<KDateLen> dateBuf(KSTDDATE); |
|
257 TTime std(dateBuf); |
|
258 |
|
259 TTime timeVal = aParamVar.AsTTime(); |
|
260 |
|
261 //Convert microseconds to milliseconds. |
|
262 TTimeIntervalMicroSeconds timeMS = timeVal.MicroSecondsFrom(std); |
|
263 TInt64 timeInt = timeMS.Int64(); |
|
264 timeInt = timeInt/1000; |
|
265 |
|
266 TInt64 len = GetLengthToStoreInt64(timeInt); |
|
267 HBufC* hInt = HBufC::NewLC(len); |
|
268 TPtr intPtr(hInt->Des()); |
|
269 intPtr.Num(timeInt); |
|
270 |
|
271 pValueNode->SetDataL(intPtr); |
|
272 |
|
273 pEntryNode->AppendChild(pValueNode); |
|
274 |
|
275 CleanupStack::PopAndDestroy(hInt); |
|
276 CleanupStack::Pop(pValueNode); |
|
277 } |
|
278 else if(aParamVar.TypeId()==EVariantTypeTReal) |
|
279 { |
|
280 //Form attribute "name" with value "float" |
|
281 TBuf<KFLOAT_LEN> typeIdValue(KFloat); |
|
282 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
283 |
|
284 //Create text node with value equivalent to aParamVar.AsReal() |
|
285 CMDXMLText* pValueNode=CMDXMLText::NewL(aDocNode); |
|
286 CleanupStack::PushL(pValueNode); |
|
287 |
|
288 TRealFormat format; |
|
289 format.iType = KRealFormatGeneral; |
|
290 |
|
291 TBuf<KMAX_LEN> value; |
|
292 value.Num(aParamVar.AsTReal(),format); |
|
293 |
|
294 |
|
295 pValueNode->SetDataL(value); |
|
296 pEntryNode->AppendChild(pValueNode); |
|
297 CleanupStack::Pop(pValueNode); |
|
298 } |
|
299 else if(aParamVar.TypeId()==EVariantTypeDesC) |
|
300 { |
|
301 //Form attribute "name" with value "string" |
|
302 TBuf<KSTRING_LEN> typeIdValue(KString); |
|
303 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
304 |
|
305 //Create text node with value equivalent to aParamVar.AsDes() |
|
306 CMDXMLText* pValueNode=CMDXMLText::NewL(aDocNode); |
|
307 CleanupStack::PushL(pValueNode); |
|
308 |
|
309 TPtrC pValue=aParamVar.AsDes(); |
|
310 pValueNode->SetDataL(pValue); |
|
311 |
|
312 pEntryNode->AppendChild(pValueNode); |
|
313 CleanupStack::Pop(pValueNode); |
|
314 } |
|
315 else if( aParamVar.TypeId()==EVariantTypeList) |
|
316 { |
|
317 //Form attribute "name" with value "list" |
|
318 TBuf<KLIST_LEN> typeIdValue(KList); |
|
319 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
320 const CLiwList* pList=aParamVar.AsList(); |
|
321 if ( pList ) |
|
322 { |
|
323 CleanupStack::PushL(const_cast<CLiwList*>(pList)); |
|
324 for(TInt l=0; l != pList->Count(); ++l) |
|
325 { |
|
326 TLiwVariant listEntry; |
|
327 pList->AtL(l,listEntry); |
|
328 this->AppendOutParamL(aDocNode,pEntryNode,listEntry); |
|
329 listEntry.Reset(); |
|
330 } |
|
331 CleanupStack::Pop(const_cast<CLiwList*>(pList)); |
|
332 } |
|
333 } |
|
334 else if(aParamVar.TypeId()==EVariantTypeIterable) |
|
335 { |
|
336 //Handling is same as that of list, accessor |
|
337 //method is different (AsIterable) |
|
338 //Form attribute "name" with value "iterator" |
|
339 |
|
340 TBuf<KITER_LEN> typeIdValue(KIterator); |
|
341 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
342 |
|
343 CLiwIterable* pIter=aParamVar.AsIterable(); |
|
344 if (pIter) |
|
345 { |
|
346 CleanupStack::PushL(pIter); |
|
347 |
|
348 TLiwVariant entry; |
|
349 while(EFalse != pIter->NextL(entry)) |
|
350 { |
|
351 this->AppendOutParamL(aDocNode,pEntryNode,entry); |
|
352 entry.Reset(); |
|
353 } |
|
354 entry.Reset(); |
|
355 CleanupStack::Pop(pIter); |
|
356 } |
|
357 } |
|
358 else if(aParamVar.TypeId()==EVariantTypeMap) |
|
359 { |
|
360 //Form attribute "name" with value "list" |
|
361 TBuf<KMAP_LEN> typeIdValue(KMap); |
|
362 pEntryNode->SetAttributeL(typeIdAttr,typeIdValue); |
|
363 const CLiwMap* pMap=aParamVar.AsMap(); |
|
364 if ( pMap ) |
|
365 { |
|
366 CleanupStack::PushL(const_cast<CLiwMap*>(pMap)); |
|
367 for(TInt m=0; m!=pMap->Count();++m) |
|
368 { |
|
369 TBuf8<KMAX_LEN> mapKey; |
|
370 pMap->AtL(m,mapKey); |
|
371 TBuf<KMAX_LEN> mapKey16; |
|
372 mapKey16.Copy(mapKey); |
|
373 |
|
374 //Add key node as child of entry node |
|
375 TBuf<KKEY_LEN> keyNode(KKey); |
|
376 TBuf<KNAME_LEN> nameAttr(KKeyAttribute); |
|
377 CMDXMLElement* pKeyNode = CMDXMLElement::NewL(ETrue,aDocNode,keyNode); |
|
378 if (pKeyNode) |
|
379 { |
|
380 CleanupStack::PushL(pKeyNode); |
|
381 pKeyNode->SetAttributeL(nameAttr,mapKey16); |
|
382 pEntryNode->AppendChild(pKeyNode); |
|
383 |
|
384 TLiwVariant fndEntry; |
|
385 TBool found = pMap->FindL(mapKey,fndEntry); |
|
386 if ( found ) |
|
387 { |
|
388 this->AppendOutParamL(aDocNode,pKeyNode,fndEntry); |
|
389 fndEntry.Reset(); |
|
390 } |
|
391 CleanupStack::Pop(pKeyNode); |
|
392 } |
|
393 } |
|
394 CleanupStack::Pop(const_cast<CLiwMap*>(pMap)); |
|
395 } |
|
396 } |
|
397 aParentNode->AppendChild(pEntryNode); |
|
398 CleanupStack::Pop(pEntryNode); |
|
399 } |
|
400 } |
|
401 } |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 // The ouput XML tree contains the output params. This function converts the output to the |
|
407 // string representation of the xml based flash specific data type. |
|
408 void CXmlConvertor::ToStringL(CMDXMLElement* aRootNode,TPtrC& aPtr) |
|
409 { |
|
410 delete iOutputBuffer; |
|
411 iOutputBuffer = NULL; |
|
412 iOutputBuffer = HBufC::NewL(KMaxOutputLen); |
|
413 WriteElementL(aRootNode); |
|
414 aPtr.Set(*iOutputBuffer); |
|
415 } |
|
416 |
|
417 // This function writes the contents of the XML node to the output. |
|
418 void CXmlConvertor::WriteElementL(CMDXMLElement* aElemNode) |
|
419 { |
|
420 if (aElemNode) |
|
421 { |
|
422 //write start element tag i.e <element_name> |
|
423 WriteStartElementTagL(aElemNode); |
|
424 if(aElemNode->HasChildNodes()==0) |
|
425 { |
|
426 AppendStringL(KFinalCloseTag); |
|
427 return; |
|
428 } |
|
429 else //write end element tag i.e </element_name> |
|
430 { |
|
431 AppendStringL(KCloseTag); |
|
432 |
|
433 for(CMDXMLNode* pCurrNode = aElemNode->FirstChild(); |
|
434 NULL != pCurrNode; |
|
435 pCurrNode = pCurrNode->NextSibling() |
|
436 ) |
|
437 { |
|
438 if(CMDXMLNode::EElementNode==pCurrNode->NodeType()) |
|
439 { |
|
440 WriteElementL((CMDXMLElement*)pCurrNode); |
|
441 } |
|
442 else if(CMDXMLNode::ETextNode==pCurrNode->NodeType()) |
|
443 { |
|
444 AppendStringL(((CMDXMLCharacterData*)pCurrNode)->Data()); |
|
445 } |
|
446 } |
|
447 |
|
448 WriteEndElementTagL(aElemNode); |
|
449 } |
|
450 } |
|
451 } |
|
452 |
|
453 // This function writes the start element tag and the attributes of the node |
|
454 // to the output. |
|
455 void CXmlConvertor::WriteStartElementTagL(CMDXMLElement* aElemNode) |
|
456 { |
|
457 if (aElemNode) |
|
458 { |
|
459 AppendStringL(KOpenTag); //< |
|
460 AppendStringL(aElemNode->NodeName()); //<element_name |
|
461 if(aElemNode->NumAttributes()>0) |
|
462 { |
|
463 WriteAttributeTagL(aElemNode); |
|
464 } |
|
465 } |
|
466 } |
|
467 |
|
468 // This function writes the end element tag to the output. |
|
469 void CXmlConvertor::WriteEndElementTagL(CMDXMLElement* aElemNode) |
|
470 { |
|
471 if(aElemNode && aElemNode->HasChildNodes()) |
|
472 { |
|
473 AppendStringL(KOpenTagSlash); |
|
474 AppendStringL(aElemNode->NodeName()); |
|
475 AppendStringL(KCloseTag); |
|
476 } |
|
477 else |
|
478 { |
|
479 AppendStringL(KFinalCloseTag); |
|
480 } |
|
481 } |
|
482 |
|
483 |
|
484 // This function writes the attributes of the node to the output. |
|
485 void CXmlConvertor::WriteAttributeTagL(CMDXMLElement* aElemNode) |
|
486 { |
|
487 //Add a space before adding attr |
|
488 if(aElemNode && aElemNode->NumAttributes()>0) |
|
489 { |
|
490 TPtrC attributeValue; |
|
491 TPtrC attributeName; |
|
492 for(TInt i=0; i!=aElemNode->NumAttributes(); ++i) |
|
493 { |
|
494 AppendStringL(KSingleSpace); |
|
495 aElemNode->AttributeDetails(i, attributeName, attributeValue); |
|
496 AppendStringL(attributeName); |
|
497 AppendStringL(KEqualSign); |
|
498 |
|
499 AppendStringL(KQuotation); |
|
500 AppendStringL(attributeValue); |
|
501 AppendStringL(KQuotation); |
|
502 } |
|
503 } |
|
504 } |
|
505 |
|
506 |
|
507 |
|
508 // Appends the output to the output buffer. |
|
509 void CXmlConvertor::AppendStringL(const TDesC& aStr) |
|
510 { |
|
511 TPtr bufPtr(iOutputBuffer->Des()); |
|
512 TInt maxLen = bufPtr.MaxLength(); |
|
513 |
|
514 TInt outLen = iOutputBuffer->Length(); |
|
515 TInt strLen = aStr.Length(); |
|
516 |
|
517 TInt remLen = maxLen - outLen; |
|
518 // ReAlloc if more space is required. |
|
519 if ( strLen > remLen) |
|
520 { |
|
521 iOutputBuffer = iOutputBuffer->ReAllocL(maxLen+strLen); |
|
522 } |
|
523 TPtr outPtr(iOutputBuffer->Des()); |
|
524 outPtr.Append(aStr); |
|
525 } |
|
526 |
|
527 |
|
528 // Calculates the length of the string needed to store the integer. |
|
529 TInt CXmlConvertor::GetLengthToStoreInt(TInt aNum) |
|
530 { |
|
531 TInt num = Abs(aNum); |
|
532 TInt len = 0; |
|
533 while(num) |
|
534 { |
|
535 len++; |
|
536 num=num/10; |
|
537 } |
|
538 return len; |
|
539 } |
|
540 |
|
541 |
|
542 // Calculates the length of the string needed to store the 64 bit integer. |
|
543 TInt64 CXmlConvertor::GetLengthToStoreInt64(TInt64 aNum) |
|
544 { |
|
545 TInt64 len = 0; |
|
546 while(aNum) |
|
547 { |
|
548 len++; |
|
549 aNum=aNum/10; |
|
550 } |
|
551 return len; |
|
552 } |
|
553 |
|
554 |
|
555 |
|
556 CXmlConvertor:: ~CXmlConvertor() |
|
557 { |
|
558 if (iOutputBuffer) |
|
559 { |
|
560 delete iOutputBuffer; |
|
561 iOutputBuffer = NULL; |
|
562 } |
|
563 } |
|
564 |
|
565 |
|
566 CXmlConvertor* CXmlConvertor:: NewL() |
|
567 { |
|
568 CXmlConvertor* temp = new(ELeave)CXmlConvertor(); |
|
569 CleanupStack::PushL(temp); |
|
570 temp->ConstructL(); |
|
571 CleanupStack::Pop(temp); |
|
572 return temp; |
|
573 } |
|
574 |
|
575 |
|
576 void CXmlConvertor:: ConstructL() |
|
577 { |
|
578 |
|
579 } |
|
580 // ========================== OTHER EXPORTED FUNCTIONS ========================= |
|
581 // None |
|
582 |
|
583 // End of File |