Introduction
The ObjectFormatter
class described in this article allows you to build up neatly formatted strings (or lists of formatted strings) with the minimum of fuss. It eliminates the need to manually build up strings using string
concatenations and if
/then
s.
The formatted strings are built from a template string which describes which properties need to be extracted from a target object, how they are formatted, how they are combined, and so on.
The predecessor to this code, PropertyFormatter
, I have had in my toolbox for many years and it has proved very useful. Recently, I have added some new functionality, reorganized, documented, renamed, and optimized the code ready for publishing here on CodeProject.
Why "Yet another..." and Some Credits
"Yet another..." because this is not the only code to provide custom formatting and string templates - far from it - but I think there are one or two features in this version that are not found in others.
I would particularly like to mention and give credit to two articles from CodeProject:
- The first, Ader Template Engine, showed me how to write a hand-built lexer/parser system (I still have nightmares about making modifications to it though!).
- The second is Custom Formatting in .NET by Scott Rippey from which I have shamelessly pinched the Conditional Formatting idea and the syntax for Complex Conditions. His original version was written in VB (otherwise I might have pinched some code too!) but there is now a C# version available too.
Both are definitely worth a look.
Background
Quite a few years ago, I was evaluating some library software titles and one of the things I noticed was that several of them required the user to enter the book title twice - once for the real title and once for how it would be displayed in a sorted catalogue. E.g.: Title="The Importance of Being Earnest" and DisplayText="Importance of Being Earnest, The". This duplication seemed somewhat wasteful to me - surely a title should only be stored once - and I idly wondered whether storing certain prefixes ('The', 'An', 'A', etc.) separately from the rest of the title would be more efficient and flexible. It certainly seemed easier to compose multiple items into one than to try to parse out and rearrange portions of the title on the fly. Similarly, authors also needed to appear in various formats for different reports - "Isaac Asimov", "Asimov, Isaac", "ASIMOV, Isaac (1920-1992)". I also noticed plenty of examples of lazy formatting. Additional spaces at the start or end of a name which looked strangely indented on the reports; surname or forename missing but the separating comma was still present; missing dates but still showing the now empty braces. The old "1 items found" chestnut on searching.
So the first version of this class was written to ensure that all of these issues could be solved. It ignored null
and empty fields, trimmed leading and trailing spaces from formatted strings, provided a way to show specific singular or plural text depending on a numeric value and, importantly, had an option to specify optional separators as part of the expression - they would only be used if the expression evaluated to something displayable.
More features were added as and when required. An important feature was to specify Dependency Expressions - if the dependency was not satisfied, then the expression would be ignored. This allowed an especially useful feature when processing lists - a Dependency Expression that only applied to duplicate formatted strings within a list.
The Code
Although the code download contains quite a few classes, this is simply because they are a subset of my Framework
class which is intended to be included in an application as a referenced DLL. A number of the classes are marked internal
and there are a few helper classes too because I like to reuse code wherever possible.
There is a separate project included in the solution containing nearly 250 unit tests. Jetbrains' dotCover shows virtually 100% coverage (some statements are impossible to reach so I'll claim the 100%), and while this is no guarantee of correctness, it does give some degree of confidence. There are also some timing tests to get an idea of the throughput achievable.
Only the ObjectFormatter
class is the subject of this article and that is quite simple to use at the public level. It has just one constructor and two public
methods, everything else is an implementation detail.
public ObjectFormatter(string template, IFormatProvider formatProvider = null,
bool globalDependency = false, Dictionary<string, object> namedTargets = null,
params object[] additionalTargets)
public string Format(object target, int maximumLength = 0)
public string[] FormatList(IList targets, int maximumLength = 0)
As you can see, only one argument is mandatory for each constructor/method. The Format
method takes your target object, applies the formatting according to the template, and returns the resulting string. It will never return null
, at worst it will return an empty string.
The FormatList
method is similar but applies the template to a list of target objects, returning a list of identical size containing the formatted strings for each. But why can't I call Format
within a loop to produce the same effect, you may ask? Well, you could, of course, but FormatList
has a special feature which allows you to produce lists of unique strings automatically. An in-depth example of this is shown later in the article.
Templates
All the functionality is based around a string template. This contains a mix of literal text and Member Expressions. The template is parsed into its elements and will throw an exception if malformed. The elements will always be the same for a given string template and so are cached internally. Literal text will always be included in the formatted string but since a '$
' symbol is used to define a Member Expression, you need to write it twice to get a single dollar symbol.
Member Expressions
Member Expressions are used to include parts of the target object into the formatted string. It is so called because the usual properties, fields, and simple methods (those that take no arguments) can also be used, hence the term Member.
A Member Expression always starts and ends with a "$
" symbol. Within the dollars, only a Member Name is mandatory.
Member Name
The Member Name part of a Member Expression is usually used to identify which property/field/method from the target to use. However, it can also identify a path from the target object ("$MyProp.MyField$
") or the target object itself ("$.$
"); or an alternative target to use ("$#1.MyProp$
"); or a named target("$#Invoices.Count$
"); or, for a list, certain well-known internal values ("$#ListPosition#$
").
(The Alternative Targets and Named Targets were those items optionally passed in via the constructor arguments.)
For example, a simple template might be:
Format("Hello, I'm $FirstName$.", Simon) => "Hello, I'm Simon."
Everything outside the dollar symbols is literal text and is always included.
This example is fine for this target object...
FormatHelper.Format("$LastName$, $FirstName$", Simon) => "Hewitt, Simon"
... but can be a problem when a field evaluates to empty:
FormatHelper.Format("$LastName$, $FirstName$", Madonna) => ", Madonna"
FormatHelper.Format("$LastName$, $FirstName$", Gandhi) => "Gandhi, "
(Madonna is my test person without a FirstName
and Gandhi is my test person without a LastName
- Yes I know Gandhi's first name is Mahatma or Mohandas, but at the time, I couldn't think of anything else!)
This is exactly the problem we need to get around and to do that, we need to use some of the other sections. The other sections of a Member Expression are all optional but they must be included in a certain order, otherwise the parser will throw an exception. It is also important to note that the contents of a Member Expression are processed as a logical unit - either the whole expression produces some string output or it produces nothing.
From left to right, the optional 'sections' are:
Leading Separator Section
This is the text between the '[
' and ']
' symbols and must be positioned to the left of the Member Name. The text will be inserted before the expression result (if the expression result is non-empty) but it is also conditional on there already having been some output from a previous expression. It is designed to put things like commas between this expression and a previous expression but ignored if there was no previous expression output.
Suffix/Prefix Section
This is text contained between single quotes and must be positioned to the left of the Member Name for a Suffix and to the right of the Member Name for a Prefix. The text will be inserted before/after the expression result (if the expression result is non-empty). It is designed to add text such as brackets around the evaluated expression but nothing if there is no output to wrap.
Trailing/Pending Separator Section
This is similar to a Leading Separator in that it is text between the '[
' and ']
' symbols but it is placed after the Member Name. It is ignored if the expression result is empty, otherwise it is stored internally as pending but not used until a later expression produces some non-empty output. In that case, it will be inserted before the latter expression's output. Should the latter expression have a Leading Separator then the longest of the two is used.
To show what we have so far, let's rewrite the problem template from before:
Format("$LastName[, ]$$FirstName$", Simon) => "Hewitt, Simon"
Format("$LastName[, ]$$FirstName$", Madonna) => "Madonna"
Format("$LastName[, ]$$FirstName$", Gandhi) => "Gandhi"
Because we moved the comma and made it Pending Separator in the LastName
Expression, it will now only be included when both Expressions have some output. Problem solved!
Conditional Formatting
This is the newest feature and again thanks to Scott Rippey for the ideas and samples. This section starts with a ';
' coming after the Member Name (and Suffix and Pending Separator if present) and is then a list of one of more Conditional Formatting Items. Each Item is separated with either another ';
' or a '|
' symbol.
I'll describe Conditional Formatting in detail later but as an example:
Format("$LastName$$[, ]FirstName$$'
('IsMarried')';Married$", Simon) => "Hewitt, Simon (Married)"
Format("$LastName$$[, ]FirstName$$'
('IsMarried')';Married$", Madonna) => "Madonna (Married)"
Format("$LastName$$[, ]FirstName$$' ('IsMarried')';Married$", Gandhi) => "Gandhi"
Here we've added another Member Expression which has a Prefix and a Suffix and a single Simple Conditional Format of "Married
". Since the value IsMarried
is boolean, the Conditional Formatting Evaluation will use the first Format Item in the list for a True
value and the second Format Item in the list for a False
value. As we didn't provide a template for the False
value, for Gandhi's case, it evaluates to Null
which in turn causes the expression evaluation to be empty which in turn means that the brackets in the Prefix and Suffix are not used. We get a nicely formatted string for all cases.
Options Section
This section comes after any Conditional Formatting section and, if present, the last section before the closing dollar. Options are case-sensitive single letters (legacy reasons!) which can have a following '+
' or '-
' to switch them on or off. They can also contain expressions by following them with an '=
' sign and then a single-quoted string. If you have several options, you can include whitespace between them to make them easier to read. There is a static
class called ObjectFormatterOptions
which lists all of the option constants and their usage.
Here is an example of a template with some options included (this is based on Scott's sample but I like it!):
FormatHelper.Format("$FirstName$ has $Friends.Count' friend':
P z='no'$$':\r\n'Friends:l=', ' L=' and 'f='$FirstName$'$.", Simon)
=> "Simon has 4 friends:\r\nMichael, Dwight, Jim and Pam."
=> "Simon has 3 friends:\r\nMichael, Dwight and Jim."
=> "Simon has 2 friends:\r\nMichael and Dwight."
=> "Simon has 1 friend:\r\nMichael."
=> "Simon has no friends."
You can see that the Friends.Count
expression has two options: the <code><code>PluralOption
('P') pluralises the suffix ' friend
' unless it numerically evaluates to 1
. The ZeroOption('z')
is used when an expression numerically evaluates to 0
and is evaluated before the PluralOption
. The last expression produces a member list. This is based on the Friends
property; ListSeparatorOption('l')
specifies the separator to use between items; the ListLastSeparatorOption('L')
option specifies the separator to use for the last item; FormatOption('f')
specifies the format to use on each item in the list and, since it is an expression in its own right, will produce the FirstName
property for each item. Had it been omitted, then ToString()
would have been called on each Person
in the list.
We've now seen how the template is laid out and some of the options available. Here are descriptions and uses for the other simpler options:
NullOption ('n')
| Used when the value being evaluated produces a null . It can contain an expression.
Format("$thispropdoesnotexist:n='$LastName$
as replacement'$", Simon) => "Hewitt as replacement"
|
ListSeparatorOption ('l') | Specifies the separator to use between items in a member list. ", " is used if omitted. |
ListLastSeparatorOption ('L') | Specifies an alternate separator to use before the last item in a list. If omitted, it will be the same as the ListSeparatorOption . |
FormatOption ('f') | This option lets you specify a normal .NET formatting string for values. In the case of a member list, it can be a Member Expression in its own right and will be applied to each item in the list. |
FullFormatOption ('F') | This option's expression can be used to produce the fully formatted string as an alternative to using the normal Member Name with formatting options. It allows the string value to be composed of multiple properties from the target value rather than creating multiple expressions showing and formatting one property each. It also allows the value specified by the Member Name to be used exclusively to decide which conditional format to be used and then the formatted output be included in the conditional format using the '@ ' placeholder.
Format("My birthday $.;was @ ago;will be " +
"in @:F='$Duration.Days' days'$'$.",
TimeSpan.FromDays(-40)) =>
"My birthday was 40 days ago."
|
MaximumWidthOption ('w') | Used to specify a maximum width for the formatted string. If omitted or zero, then the formatted string is not truncated. |
ListItemWidthOption ('i') | Used to specify a maximum width for each item in a member list. If omitted or zero, then the formatted item is not truncated. Useful to create a list of initials.
Format("$SimpleListProperty:i='1'$", Simon) => "F,S,T"
|
SortSourceItemsOption ('S') | If this option is present for a member list expression, the source list will be sorted before producing the formatted strings. Use 'S ' or 'S+ ' to sort ascending or 'S- ' to sort descending. If the items are not sortable, then this step is skipped. |
SortTargetStringsOption ('s') | If this option is present for a member list expression, the formatted strings will be sorted. Use 's ' or 's+ ' to sort ascending or 's- ' to sort descending. |
CaseOption ('c') | Changes the case of the formatted string. Use 'c ' or 'c+ ' to change to upper case or 'c- ' to change to lower case. |
Cap italiseOption ('C') | Capitalises the words in a formatted string. Use 'C ' or 'C+ ' to capitalise each word and change all other letters to lower case. Use 'C- ' to do the same but preserve any words that are all capitals. |
SingularOption ('p') and PluralOption('P') | These options can be used individually or together to specify text to use when a value evaluates to Singular (1 ) or Plural (not 1 ). When both are present, one or other will be used and any suffix will also be included. When just one is present, it is assumed to be the inverse to the suffix and will replace the suffix - i.e., specifying just a SingularOption will assume that any suffix is the text for plural values. So using "matches" as the suffix and "match" as the SingularOption will produce the correct results. There is one special case: when just 'P ' is specified as the PluralOption but without any text, it will add an 's' to whatever the suffix text is. Useful for the normal case like "item" (suffix). |
Z ero Option ('z') | This option allows replacement text when the value evaluates to a zero.
Format("$ZeroValue:z='None'$", Simon) => "None"
|
Trim Option ('t') | Unlike the other options, this option is active by default and will remove leading and trailing spaces from a formatted string. Use 't- ' to switch it off. |
The remaining five options are all DependencyOptions
. DependencyOptions
are always processed first and if they 'fail' the dependency, processing of that Member Expression stops immediately and string.Empty
is returned.
DependencyOption ('d') | This option takes a Member Expression as its parameter (typically a boolean property on the target object) and evaluates it. If the result is not an empty string and is either "True " or not "False ", then the dependency is considered met and processing continues. You can also invert a dependency by using ":d-='<expr> '". In this case, the dependency is met if the evaluation produces an empty string or "False " or not "True ". |
BlankDependencyOption ('b') | If this option is present, then it will only pass the dependency if no output has been produced so far from previous expressions.
Format("$LastName$$.:bF='(No Last Name)'$",
Simon) => "Hewitt"
Format("$LastName$$.:bF='(No Last Name)'$", Simon) =>
"(No Last Name)"
Format("$LastName$$.:bF='(No Last Name)'$",
Simon) => "Gandhi"
|
GlobalDependencyOption ('D') | This Dependency Option simple looks at the external boolean passed in as an argument when creating the ObjectFormatter .
"$LastName$$[, ]FirstName$$' ('IsMarried')';
Married|Unmarried:D$"
The IsMarried expression in this example will only be evaluated if the globalDependency argument passed to the ctor happened to be true.
|
The remaining two DependencyOptions
are list-specific. That is, they will always 'fail' whilst not processing a list and thus the Member Expression they are in will be ignored. When using FormatList
however, the constructor will find and create a list of all the Member Expressions containing either of these options. If any are present, then the special list-uniquing feature will be switched on.
List-Uniquing Feature
After the list is processed, all of the strings are checked and any duplicates and their indexes are found. If duplicates are found, then the following happens:
- An index is maintained into the list of
UniqueDependency
-containing expressions - the Currently Active index - and this starts at 0 being the first item on the list. - All of the duplicate strings are reformatted with the hope that they will produce different results now that a new Expression has come into play.
- Duplicates are again searched for and, if any are found, the
CurrentlyActive
index is incremented. - This process continues until all the strings are unique or the end of the list is reached.
UniqueDependencyOption ('U') | This Dependency is considered met if it is at the Currently Active index or anywhere before it. In other words, once on, it stays on even if the Currently Active Index moves to the next item. |
WeakUniqueDependencyOption ('u') | This Dependency is only considered met if it is at the Currently Active Index. If the Member Expression containing this option cannot entirely remove the duplicates from its Scope, then they are reset to their original values. The Currently Active index moves on and this Expression is ignored once again. The idea is that this Expression is tried and used where possible, but if it cannot work, then the next in the list is tried and so on.
Using 'u- ' defines Group Scope for this expression - that is, it is considered to have succeeded if it makes unique a single set of duplicate values. Say "AAA" appears twice and "BBB" appears three times. If this expression becomes active and these values then become "AAA_1" and "AAA_2", then that group is now considered unique and won't be reevaluated. But if the "BBB" group becomes "BBB_1", "BBB", and "BBB", then it isn't unique and all will be reverted back to "BBB" and the process continues.
Using 'u+ ' defines the List Scope for this expression - that is, it is considered to have succeeded only if all the groups of duplicates are made unique. In the previous example, all five duplicates would be reverted to their original values because one Group but not the whole List became unique.
|
It is not guaranteed that the final list returned will be unique unless the final UniqueDependency
-containing expression contains one of the special Member Names. '#ListIndex#
' returns the 0-based index of the item being processed. '#ListPosition#
' returns the 1-based index of the item being processed. '#
' returns a 1-based index of the item being processed but is context-based. If there are any UniqueDependency
options currently active, then the context list will be just the current group of duplicates being processed; otherwise it returns the same as '#ListPosition#
'.
This sounds complicated, so here is an example (the names with a 2 suffix have the same names as those without, but have a different date of birth):
FormatHelper.FormatList("$LastName$$[, ]FirstName$$' ('DateOfBirth')':u-f='yyyy'$$'
#'#:U$", new[] { Simon, Madonna, Gandhi, Simon2, Gandhi2, Simon2 }) =>
Hewitt, Simon #1
Madonna
Gandhi (1910)
Hewitt, Simon #2
Gandhi (1922)
Hewitt, Simon #3
The first pass ignores the last two expressions as they were not active and so produced Groups: 3 x "Hewitt, Simon
"; 1 x "Madonna
"; 2 x "Gandhi
".
The DateOfBirth
expression has a 'u-
' option and so is a WeakUniqueDependency
with Group Scope. It becomes the Current Active and is applied to the two groups containing duplicates. At this point, the Gandhi
group is now unique and is complete. The other group however has "Hewitt, Simon (1969)
" but 2 x "Hewitt, Simon (1975)
". This isn't unique and so is rolled back. The final Expression now becomes Current Active and the DateOfBirth
expression is no longer active because it is weak. A formatting pass is made over the last group and, since we are using the Context List Position, cannot fail and produces the unique output above.
Now have a look at the difference if we change "u-
" to "u+
". This is still a WeakUniqueDependency
but now has List scope. When it was active, although it made one group unique, it couldn't make the whole list unique and so all group contents were reverted and the next Member Expression was tried producing this output.
Hewitt, Simon #1
Madonna
Gandhi #1
Hewitt, Simon #2
Gandhi #2
Hewitt, Simon #3
Finally, if we had changed the "u-
" to a "U
" thus making it a strong dependency, we would see the cumulative effect as the first UniqueDependency
adds the Dates of Birth and then the second tie-breaks the final two duplicates.
Hewitt, Simon (1969)
Madonna
Gandhi (1910)
Hewitt, Simon (1975) #1
Gandhi (1922)
Hewitt, Simon (1975) #2
Conditional Formatting
As previously mentioned, this section allows you to specify one or more Conditional Formatting Items in a list.
There are two types of Conditional Formatting Items: Simple and Complex.
Simple Conditional Formatting Item
This can either be just literal text or a single-quoted string. It also supports a '@
' placeholder to position output from the FullFormatOption
.
Complex Conditional Formatting Item
This is a list of one or more numeric Comparison Expressions followed by a '?
' delimiter and then a Simple Conditional Formatting Item. Complex Conditional Format Items are only used for numeric values.
Each Comparison Expression is made up of a Comparison Operator ('=
', '<
', '>
', '!
', '==
', '<=
', '>=
', '!=
') immediately followed by a decimal number.
Multiple Comparison Expressions can be included as a list and they are separated by either a '/
' (logical OR) or a '&
' (logical AND). Each Comparison Expression will be evaluated from left to right.
When the first item in a list is recognized as being Complex, then all the Formatting Items except the last must also be Complex. They cannot be mixed and matched. One Simple Item is allowed at the end and will be used if none of the preceding Complex Items match. (If no Simple Item is included, a virtual one consisting of just '@
' is included.)
Selecting a Conditional Format
How the particular Conditional Formatting Item to use is chosen depends on the target object's type and value. If the type is numeric and the Conditional Formatting Items are Complex, then they are evaluated from left to right until a match is found. This is the Format Item to be used.
For Simple Conditional Formatting Items, the Format Item is selected depending on the object type/value as per this table:
Built-In Numeric Type | ;<Negative>;<Zero>;<One>;<Default> | |
| ;<Zero>;<One>;<Default> | |
| ;<One>;<Default> | |
| ;<Default> | |
Boolean | ;<True>[;<False>] | The <False> format can be omitted and will return Empty. |
DateTime | ;<Past>;<Today>;<Future> | If the DateTime contains a non-zero time, then <Today> cannot match. |
| ;<Past or Today>;<Future> | |
| ;<Default> | |
TimeSpan | ;<Negative>;<Zero>;<Positive> | |
| ;<Negative or Zero>;<Positive> | |
Once a Format Item has been selected, it will be processed if it is a Member Expression in its own right or the literal text is used.
If it contains the '@
' placeholder symbol, then this will be replaced by the current value evaluation or FullFormatOption
evaluation if present.
Here are a few sample templates using Conditional Formatting:
"My birthday $#0;was on @|is Today!|will be on @:f='MMMM d'$"
"There $.;is|are$ $.$ $.;item|items$ remaining..."
"$Age;=5?Kindergarten;<11?Elementary;>10&<=12?Middle
School;>12&<=18?High School;None$"
"Enabled? $.;Yes|No$"
Summary of the Formatting Process
- Any Dependency Options are checked. If any fails, then
string.Empty
is immediately returned as the result. - The value to format is obtained. This is most likely a property on the target object but could be the target itself, an alternate source object, or any combination. Any
DBNull
is converted to a null
. A DataRow
/DataRowView
will use the member path to obtain a column value. - The value is converted into a string using one of these methods (they are listed in priority order):
- A
null
value will take the string specified by the NullOption
. - An enumerable value will become a member list using the various member list options.
- Conditional Formatting will be applied.
- A zero value will use the
ZeroOption
. - The
FormatOption
or FullFormatOption
will be used. ToString()
will be called.
- The value is trimmed. Unless the
TrimOption
is switched off, the string value will be trimmed. If Empty
at this point, it will be returned immediately. - Post-format processing is applied.
- It will be cased according to any
CaseOption
or CapitaliseOption
. - It will be truncated if
MaximumWidthOption
is specified.
- The final result is combined with Separator/Prefix/Suffix as appropriate and returned.
Other Items (Maybe) of Interest
- All the access to the properties/fields etc., is done via the
GenericGetter
delegate. For the most part, this delegate is created using a DynamicMethod
/IL generation and then compiled for speed. It is also cached within MemberPathCache
for reuse. - For instance, where a Member Path is used and there is no corresponding field/property available (say a property returns an
object
but there are still more properties on the path), normal Reflection is used. MemberPathCache
uses a Dictionary
to cache already created GenericGetter
delegates. The key is a custom TypeAndPath
class and is about as fast as I can make it. However, I wanted more and added a LRU cache around that. Yes, I have a cache on a cache! - The Conditional Formatting uses a
struct
called NumberState
which can take an object (usually numeric) and classify its content as Zero
/Positive
/Negative
/IsOne
/IsNegativeOne
etc. This refactoring may seem a bit over the top but I seem to remember having to do something similar for my Fast Serialization code so I created this class hoping to use it again. - The
FormatHelper static
class has the Format
and FormatList static
methods so you don't even need to create a local ObjectFormatter
if you don't want to reuse it. - The
StringHelper
and DateTimeHelper static
classes may contain a few snippets of code you might find useful. - The
TimingTestFixture
class in Framework.UnitTest
has proved very helpful whilst optimizing the code. It is fully documented and you can see how I've used it in ObjectFormatterSpeedTests.cs.
Future Plans
There are no specific plans at the moment, just some vague ideas.
- Move the
additionalTargets
argument from the constructor to the Format
/FormatList
methods. The dictionary-based named targets is probably OK being fixed, but it might be better to let the additional targets be changeable on each call? - Add a custom
Format
event. A lot of the stuff I do requires dates to be formatted. I have a super fast datetime-format class for fixed-format date/times but haven't worked out how to fit it in. - See if there is a need for an
Empty
replacement option (similar to the Null
option) when a Member Expression evaluates to an empty string. - See if the Conditional Formatting could deal with
Null
/Empty
items using additional Simple Conditional Format Items. And/or use NumberStat
e's PseudoNumber
to select templates. - Add support for brackets in Complex Conditional Format Items to be more specific than the left-to-right priority.
History
- v1.0 - First release to CodeProject
Simon Hewitt is a freelance IT consultant and is MD of Hunton Information Systems Ltd.
He is currently looking for contract work in London.
He is happily married to Karen (originally from Florida, US), has a lovely daughter Bailey, and they live in Kings Langley, Hertfordshire, UK.