Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
EDalerUriViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
3using EDaler;
9using System.ComponentModel;
10using System.Globalization;
11using System.Text;
12using Waher.Content;
15
17{
21 public partial class EDalerUriViewModel : QrXmppViewModel
22 {
23 private readonly EDalerUriNavigationArgs? navigationArguments;
24 private readonly IShareQrCode? shareQrCode;
25 private readonly TaskCompletionSource<string?>? uriToSend = null;
26
33 : base()
34 {
35 this.navigationArguments = Args;
36 this.shareQrCode = ShareQrCode;
37
38 this.uriToSend = Args?.UriToSend;
39 this.FriendlyName = Args?.FriendlyName;
40
41 if (Args?.Uri is not null)
42 {
43 this.Uri = Args.Uri.UriString;
44 this.Id = Args.Uri.Id;
45 this.Amount = Args.Uri.Amount;
46 this.AmountExtra = Args.Uri.AmountExtra;
47 this.Currency = Args.Uri.Currency;
48 this.Created = Args.Uri.Created;
49 this.Expires = Args.Uri.Expires;
50 this.ExpiresStr = this.Expires.ToShortDateString();
51 this.From = Args.Uri.From;
52 this.FromType = Args.Uri.FromType;
53 this.To = Args.Uri.To;
54 this.ToType = Args.Uri.ToType;
55 this.ToPreset = !string.IsNullOrEmpty(Args.Uri.To);
56 this.Complete = Args.Uri.Complete;
57 }
58
59 this.NotPaid = true;
60
61 this.AmountText = !this.Amount.HasValue || this.Amount.Value <= 0 ? string.Empty : MoneyToString.ToString(this.Amount.Value);
62 this.AmountOk = CommonTypes.TryParse(this.AmountText, out decimal d) && d > 0;
63 this.AmountPreset = !string.IsNullOrEmpty(this.AmountText) && this.AmountOk;
64 this.AmountAndCurrency = this.AmountText + " " + this.Currency;
65
66 this.AmountExtraText = this.AmountExtra.HasValue ? MoneyToString.ToString(this.AmountExtra.Value) : string.Empty;
67 this.AmountExtraOk = !this.AmountExtra.HasValue || this.AmountExtra.Value >= 0;
68 this.AmountExtraPreset = this.AmountExtra.HasValue;
69 this.AmountExtraAndCurrency = this.AmountExtraText + " " + this.Currency;
70
71 StringBuilder Url = new();
72
73 Url.Append("https://");
74 Url.Append(this.From);
75 Url.Append("/Images/eDalerFront200.png");
76
77 this.EDalerFrontGlyph = Url.ToString();
78
79 Url.Clear();
80 Url.Append("https://");
81 Url.Append(ServiceRef.TagProfile.Domain);
82 Url.Append("/Images/eDalerBack200.png");
83 this.EDalerBackGlyph = Url.ToString();
84 }
85
87 protected override async Task OnInitialize()
88 {
89 await base.OnInitialize();
90
91 if (this.navigationArguments is not null)
92 {
93 if (this.navigationArguments.Uri?.EncryptedMessage is not null)
94 {
95 if (this.navigationArguments.Uri.EncryptionPublicKey is null)
96 this.Message = Encoding.UTF8.GetString(this.navigationArguments.Uri.EncryptedMessage);
97 else
98 {
99 this.Message = await ServiceRef.XmppService.TryDecryptMessage(this.navigationArguments.Uri.EncryptedMessage,
100 this.navigationArguments.Uri.EncryptionPublicKey, this.navigationArguments.Uri.Id, this.navigationArguments.Uri.From);
101 }
102 this.HasMessage = !string.IsNullOrEmpty(this.Message);
103 }
104
105 this.MessagePreset = !string.IsNullOrEmpty(this.Message);
106 this.CanEncryptMessage = this.navigationArguments.Uri?.ToType == EntityType.LegalId;
107 this.EncryptMessage = this.CanEncryptMessage;
108 }
109 }
110
112 protected override async Task OnDispose()
113 {
114 this.uriToSend?.TrySetResult(null);
115
116 await base.OnDispose();
117 }
118
119 #region Properties
120
124 [ObservableProperty]
125 private string? uri;
126
130 [ObservableProperty]
131 private decimal? amount;
132
136 [ObservableProperty]
137 [NotifyCanExecuteChangedFor(nameof(PayOnlineCommand))]
138 [NotifyCanExecuteChangedFor(nameof(GenerateQrCodeCommand))]
139 [NotifyCanExecuteChangedFor(nameof(SendPaymentCommand))]
140 private bool amountOk;
141
142 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
143 {
144 base.OnPropertyChanged(e);
145
146 switch (e.PropertyName)
147 {
148 case nameof(this.IsConnected):
149 this.AcceptCommand.NotifyCanExecuteChanged();
150 this.PayOnlineCommand.NotifyCanExecuteChanged();
151 this.SubmitCommand.NotifyCanExecuteChanged();
152 break;
153
154 case nameof(this.HasQrCode):
155 this.PayOnlineCommand.NotifyCanExecuteChanged();
156 this.GenerateQrCodeCommand.NotifyCanExecuteChanged();
157 this.ShareCommand.NotifyCanExecuteChanged();
158 break;
159
160 case nameof(this.AmountText):
161 if (CommonTypes.TryParse(this.AmountText, out decimal d) && d > 0)
162 {
163 this.Amount = d;
164 this.AmountOk = true;
165 }
166 else
167 this.AmountOk = false;
168
169 this.AmountAndCurrency = this.AmountText + " " + this.Currency;
170 break;
171
172 case nameof(this.AmountExtraText):
173 if (string.IsNullOrEmpty(this.AmountExtraText))
174 {
175 this.AmountExtra = null;
176 this.AmountExtraOk = true;
177 }
178 else if (CommonTypes.TryParse(this.AmountExtraText, out d) && d >= 0)
179 {
180 this.AmountExtra = d;
181 this.AmountExtraOk = true;
182 }
183 else
184 this.AmountExtraOk = false;
185
186 this.AmountExtraAndCurrency = this.AmountExtraText + " " + this.Currency;
187 break;
188 }
189 }
190
194 [ObservableProperty]
195 [NotifyCanExecuteChangedFor(nameof(PayOnlineCommand))]
196 [NotifyCanExecuteChangedFor(nameof(GenerateQrCodeCommand))]
197 [NotifyCanExecuteChangedFor(nameof(SendPaymentCommand))]
198 private string? amountText;
199
203 [ObservableProperty]
204 private string? amountAndCurrency;
205
209 [ObservableProperty]
210 private bool amountPreset;
211
215 [ObservableProperty]
216 private decimal? amountExtra;
217
221 [ObservableProperty]
222 [NotifyCanExecuteChangedFor(nameof(PayOnlineCommand))]
223 [NotifyCanExecuteChangedFor(nameof(GenerateQrCodeCommand))]
224 [NotifyCanExecuteChangedFor(nameof(SendPaymentCommand))]
225 private bool amountExtraOk;
226
230 [ObservableProperty]
231 [NotifyCanExecuteChangedFor(nameof(PayOnlineCommand))]
232 [NotifyCanExecuteChangedFor(nameof(GenerateQrCodeCommand))]
233 [NotifyCanExecuteChangedFor(nameof(SendPaymentCommand))]
234 private string? amountExtraText;
235
239 [ObservableProperty]
240 private string? amountExtraAndCurrency;
241
245 [ObservableProperty]
246 private bool amountExtraPreset;
247
251 [ObservableProperty]
252 private string? currency;
253
257 [ObservableProperty]
258 private DateTime created;
259
263 [ObservableProperty]
264 private DateTime expires;
265
269 [ObservableProperty]
270 private string? expiresStr;
271
275 [ObservableProperty]
276 private Guid id;
277
281 [ObservableProperty]
282 private string? from;
283
287 [ObservableProperty]
288 private EntityType fromType;
289
293 [ObservableProperty]
294 private string? to;
295
299 [ObservableProperty]
300 private bool toPreset;
301
305 [ObservableProperty]
306 private EntityType toType;
307
311 [ObservableProperty]
312 private string? friendlyName;
313
317 [ObservableProperty]
318 private bool complete;
319
323 [ObservableProperty]
324 private string? message;
325
329 [ObservableProperty]
330 private bool encryptMessage;
331
335 [ObservableProperty]
336 private bool canEncryptMessage;
337
341 [ObservableProperty]
342 private bool hasMessage;
343
347 [ObservableProperty]
348 private bool messagePreset;
349
353 [ObservableProperty]
354 [NotifyCanExecuteChangedFor(nameof(PayOnlineCommand))]
355 [NotifyCanExecuteChangedFor(nameof(GenerateQrCodeCommand))]
356 [NotifyCanExecuteChangedFor(nameof(SendPaymentCommand))]
357 private bool notPaid;
358
362 [ObservableProperty]
363 private string? eDalerFrontGlyph;
364
368 [ObservableProperty]
369 private string? eDalerBackGlyph;
370
371 #endregion
372
376 [RelayCommand]
377 private async Task FromClick()
378 {
379 try
380 {
381 string? Value = this.From;
382 if (Value is null)
383 return;
384
385 if ((Value.StartsWith("http://", StringComparison.CurrentCultureIgnoreCase) ||
386 Value.StartsWith("https://", StringComparison.CurrentCultureIgnoreCase)) &&
387 System.Uri.TryCreate(Value, UriKind.Absolute, out Uri? Uri) && await Launcher.TryOpenAsync(Uri))
388 {
389 return;
390 }
391
392 if (System.Uri.TryCreate("https://" + Value, UriKind.Absolute, out Uri) && await Launcher.TryOpenAsync(Uri))
393 return;
394
395 await Clipboard.SetTextAsync(Value);
396 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.SuccessTitle)],
397 ServiceRef.Localizer[nameof(AppResources.TagValueCopiedToClipboard)]);
398 }
399 catch (Exception ex)
400 {
401 ServiceRef.LogService.LogException(ex);
403 }
404 }
405
409 [RelayCommand(CanExecute = nameof(IsConnected))]
410 private async Task Accept()
411 {
412 try
413 {
414 if (this.Uri is null)
415 return;
416
417 if (!await App.AuthenticateUser(AuthenticationPurpose.AcceptEDalerUri, true))
418 return;
419
420 (bool succeeded, Transaction? Transaction) = await ServiceRef.NetworkService.TryRequest(
421 () => ServiceRef.XmppService.SendEDalerUri(this.Uri));
422
423 if (succeeded)
424 {
425 await this.GoBack();
426 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.SuccessTitle)],
427 ServiceRef.Localizer[nameof(AppResources.TransactionAccepted)]);
428 }
429 else
430 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
431 ServiceRef.Localizer[nameof(AppResources.UnableToProcessEDalerUri)]);
432 }
433 catch (Exception ex)
434 {
435 ServiceRef.LogService.LogException(ex);
437 }
438 }
439
443 [RelayCommand(CanExecute = nameof(CanPayOnline))]
444 private async Task PayOnline()
445 {
446 try
447 {
448 if (!this.NotPaid)
449 {
450 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
451 ServiceRef.Localizer[nameof(AppResources.PaymentAlreadySent)]);
452 return;
453 }
454
455 if (!await App.AuthenticateUser(AuthenticationPurpose.PayOnline, true))
456 return;
457
458 string Uri;
459
460 if (this.EncryptMessage && this.ToType == EntityType.LegalId)
461 {
462 try
463 {
464 LegalIdentity LegalIdentity = await ServiceRef.XmppService.GetLegalIdentity(this.To);
465 Uri = await ServiceRef.XmppService.CreateFullEDalerPaymentUri(LegalIdentity, this.Amount ?? 0, this.AmountExtra,
466 this.Currency ?? string.Empty, 3, this.Message ?? string.Empty);
467 }
468 catch (ForbiddenException)
469 {
470 // This happens if you try to view someone else's legal identity.
471 // When this happens, try to send a petition to view it instead.
472 // Normal operation. Should not be logged.
473
474 this.NotPaid = true;
475
476 MainThread.BeginInvokeOnMainThread(async () =>
477 {
478 bool Succeeded = await ServiceRef.NetworkService.TryRequest(() => ServiceRef.XmppService.PetitionIdentity(
479 this.To, Guid.NewGuid().ToString(), ServiceRef.Localizer[nameof(AppResources.EncryptedPayment)]));
480
481 if (Succeeded)
482 {
483 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.PetitionSent)],
484 ServiceRef.Localizer[nameof(AppResources.APetitionHasBeenSentForEncryption)]);
485 }
486 });
487
488 return;
489 }
490 }
491 else
492 {
493 Uri = await ServiceRef.XmppService.CreateFullEDalerPaymentUri(this.To!, this.Amount ?? 0, this.AmountExtra,
494 this.Currency!, 3, this.Message ?? string.Empty);
495 }
496
497 // TODO: Validate To is a Bare JID or proper Legal Identity
498 // TODO: Offline options: Expiry days
499
500 this.NotPaid = false;
501
502 (bool succeeded, Transaction? Transaction) = await ServiceRef.NetworkService.TryRequest(
503 () => ServiceRef.XmppService.SendEDalerUri(Uri));
504
505 if (succeeded)
506 {
507 await this.GoBack();
508 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.SuccessTitle)],
509 ServiceRef.Localizer[nameof(AppResources.PaymentSuccess)]);
510 }
511 else
512 {
513 this.NotPaid = true;
514 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
515 ServiceRef.Localizer[nameof(AppResources.UnableToProcessEDalerUri)]);
516 }
517 }
518 catch (Exception ex)
519 {
520 this.NotPaid = true;
521 ServiceRef.LogService.LogException(ex);
523 }
524 }
525
529 [RelayCommand(CanExecute = nameof(CanGenerateQrCode))]
530 private async Task GenerateQrCode()
531 {
532 if (!this.NotPaid)
533 {
534 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
535 ServiceRef.Localizer[nameof(AppResources.PaymentAlreadySent)]);
536 return;
537 }
538
539 if (!await App.AuthenticateUser(AuthenticationPurpose.PayOffline, true))
540 return;
541
542 try
543 {
544 string Uri;
545
546 if (this.EncryptMessage && this.ToType == EntityType.LegalId)
547 {
548 LegalIdentity LegalIdentity = await ServiceRef.XmppService.GetLegalIdentity(this.To);
549 Uri = await ServiceRef.XmppService.CreateFullEDalerPaymentUri(LegalIdentity, this.Amount ?? 0, this.AmountExtra,
550 this.Currency ?? string.Empty, 3, this.Message ?? string.Empty);
551 }
552 else
553 {
554 Uri = await ServiceRef.XmppService.CreateFullEDalerPaymentUri(this.To ?? string.Empty, this.Amount ?? 0, this.AmountExtra,
555 this.Currency ?? string.Empty, 3, this.Message ?? string.Empty);
556 }
557
558 // TODO: Validate To is a Bare JID or proper Legal Identity
559 // TODO: Offline options: Expiry days
560
561 if (this.IsAppearing)
562 {
563 MainThread.BeginInvokeOnMainThread(async () =>
564 {
565 this.GenerateQrCode(Uri);
566
567 if (this.shareQrCode is not null)
568 await this.shareQrCode.ShowQrCode();
569 });
570 }
571 }
572 catch (Exception ex)
573 {
574 ServiceRef.LogService.LogException(ex);
576 }
577 }
578
579 private bool CanPayOnline => this.AmountOk && this.AmountExtraOk && !this.HasQrCode && this.IsConnected && this.NotPaid; // TODO: Add To field OK
580 private bool CanGenerateQrCode => this.AmountOk && this.AmountExtraOk && !this.HasQrCode && this.NotPaid; // TODO: Add To field OK
581 private bool CanShare => this.HasQrCode;
582
586 [RelayCommand(CanExecute = nameof(CanShare))]
587 private async Task Share()
588 {
589 if (this.QrCodeBin is null)
590 return;
591
592 try
593 {
594 string? Message = this.Message?? this.AmountAndCurrency;
595
596 ServiceRef.PlatformSpecific.ShareImage(this.QrCodeBin,
597 string.Format(CultureInfo.CurrentCulture, Message ?? string.Empty, this.Amount, this.Currency),
598 ServiceRef.Localizer[nameof(AppResources.Share)], "RequestPayment.png");
599 }
600 catch (Exception ex)
601 {
602 ServiceRef.LogService.LogException(ex);
604 }
605 }
606
610 [RelayCommand(CanExecute = nameof(IsConnected))]
611 private async Task Submit()
612 {
613 if (this.Uri is null)
614 return;
615
616 try
617 {
618 if (!await App.AuthenticateUser(AuthenticationPurpose.SubmitEDalerUri))
619 return;
620
621 (bool succeeded, Transaction? Transaction) = await ServiceRef.NetworkService.TryRequest(() => ServiceRef.XmppService.SendEDalerUri(this.Uri));
622 if (succeeded)
623 {
624 await this.GoBack();
625 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.SuccessTitle)],
626 ServiceRef.Localizer[nameof(AppResources.PaymentSuccess)]);
627 }
628 else
629 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
630 ServiceRef.Localizer[nameof(AppResources.UnableToProcessEDalerUri)]);
631 }
632 catch (Exception ex)
633 {
634 ServiceRef.LogService.LogException(ex);
636 }
637 }
638
642 [RelayCommand]
643 private async Task ShowCode()
644 {
645 if (this.Uri is null)
646 return;
647
648 if (!await App.AuthenticateUser(AuthenticationPurpose.ShowUriAsQr, true))
649 return;
650
651 try
652 {
653 if (this.IsAppearing)
654 {
655 MainThread.BeginInvokeOnMainThread(async () =>
656 {
657 this.GenerateQrCode(this.Uri);
658
659 if (this.shareQrCode is not null)
660 await this.shareQrCode.ShowQrCode();
661 });
662 }
663 }
664 catch (Exception ex)
665 {
666 ServiceRef.LogService.LogException(ex);
668 }
669 }
670
671 private bool CanSendPayment()
672 {
673 return this.uriToSend is not null && this.AmountOk && this.AmountExtraOk && this.NotPaid;
674 }
675
679 [RelayCommand(CanExecute = nameof(CanSendPayment))]
680 private async Task SendPayment()
681 {
682 if (!this.NotPaid)
683 {
684 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
685 ServiceRef.Localizer[nameof(AppResources.PaymentAlreadySent)]);
686 return;
687 }
688
689 if (!await App.AuthenticateUser(AuthenticationPurpose.SendPayment, true))
690 return;
691
692 try
693 {
694 string Uri;
695
696 if (this.EncryptMessage && this.ToType == EntityType.LegalId)
697 {
698 LegalIdentity LegalIdentity = await ServiceRef.XmppService.GetLegalIdentity(this.To);
699 Uri = await ServiceRef.XmppService.CreateFullEDalerPaymentUri(LegalIdentity, this.Amount ?? 0, this.AmountExtra,
700 this.Currency!, 3, this.Message ?? string.Empty);
701 }
702 else
703 {
704 Uri = await ServiceRef.XmppService.CreateFullEDalerPaymentUri(this.To!, this.Amount ?? 0, this.AmountExtra,
705 this.Currency!, 3, this.Message ?? string.Empty);
706 }
707
708 // TODO: Validate To is a Bare JID or proper Legal Identity
709 // TODO: Offline options: Expiry days
710
711 this.uriToSend?.TrySetResult(Uri);
712 await this.GoBack();
713 }
714 catch (Exception ex)
715 {
716 ServiceRef.LogService.LogException(ex);
718 }
719 }
720
725 [RelayCommand]
726 public async Task OpenCalculator(object Parameter)
727 {
728 try
729 {
730 switch (Parameter?.ToString())
731 {
732 case "AmountText":
733 CalculatorNavigationArgs AmountArgs = new(this, nameof(this.AmountText));
734
735 await ServiceRef.UiService.GoToAsync(nameof(CalculatorPage), AmountArgs, BackMethod.Pop);
736 break;
737
738 case "AmountExtraText":
739 CalculatorNavigationArgs ExtraArgs = new(this, nameof(this.AmountExtraText));
740
741 await ServiceRef.UiService.GoToAsync(nameof(CalculatorPage), ExtraArgs, BackMethod.Pop);
742 break;
743 }
744 }
745 catch (Exception ex)
746 {
748 }
749 }
750
751 #region ILinkableView
752
756 public override Task<string> Title => Task.FromResult<string>(ServiceRef.Localizer[nameof(AppResources.Payment)]);
757
758 #endregion
759
760 }
761}
Represents a transaction in the eDaler network.
Definition: Transaction.cs:36
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
static Task< bool > AuthenticateUser(AuthenticationPurpose Purpose, bool Force=false)
Authenticates the user using the configured authentication method.
Definition: App.xaml.cs:981
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:103
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
static string ToString(decimal Money)
Converts a monetary value to a string, removing any round-off errors.
bool IsAppearing
Returns true if the view model is shown.
virtual async Task GoBack()
Method called when user wants to navigate to the previous screen.
A page that allows the user to calculate the value of a numerical input field.
A view model that holds the XMPP state.
Holds navigation parameters specific to eDaler URIs.
string? FriendlyName
Optional Friendly Name associated with URI
TaskCompletionSource< string?>? UriToSend
Task Completion Source in case the URI being built is to be returned to the parent page.
The view model to bind to for when displaying the contents of an eDaler URI.
override async Task OnInitialize()
Method called when view is initialized for the first time. Use this method to implement registration ...
async Task OpenCalculator(object Parameter)
Opens the calculator for calculating the value of a numerical property.
EDalerUriViewModel(IShareQrCode? ShareQrCode, EDalerUriNavigationArgs? Args)
The view model to bind to for when displaying the contents of an eDaler URI.
override Task< string > Title
Title of the current view
override async Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:46
Abstract base class for contractual parameters
Definition: Parameter.cs:17
The requesting entity does not possess the necessary permissions to perform an action that only certa...
Task DisplayException(Exception Exception, string? Title=null)
Displays an alert/message box to the user.
Task GoToAsync(string Route, BackMethod BackMethod=BackMethod.Inherited, string? UniqueId=null)
Navigates the AppShell to the specified route, with page arguments to match.
Task< bool > DisplayAlert(string Title, string Message, string? Accept=null, string? Cancel=null)
Displays an alert/message box to the user.
Interface for pages with a share button.
Definition: IShareQrCode.cs:7
EntityType
Type of entity referred to in transaction.
Definition: Transaction.cs:15
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.