/// <summary> /// Write element text /// </summary> /// <param name="writer">XML writer</param> protected virtual void WriteText(XmlWriter writer) { // Write text string text = string.Empty; foreach (PropertyInfo c in GetType().GetProperties()) { foreach (XsAttributeAttribute ab in CustomAttributeHelper.All <XsAttributeAttribute>(c)) { if (ab.Name == string.Empty) { object v = c.GetValue(this, null); text += v; break; } } } if (text.Length > 0) { if (text.IndexOfAny("><&".ToCharArray()) != -1) { writer.WriteCData(text); } else { writer.WriteValue(text); } } }
/// Resolve collection property and node for the current XML node. Exception is thrown if nothing is found. protected void ResolveCollectionAndTypeForNode(XmlReader reader, IXsContext context, out PropertyInfo collection, out Type type) { foreach (PropertyInfo c in GetType().GetProperties()) { foreach (XsElementAttribute e in CustomAttributeHelper.All <XsElementAttribute>(c)) { if (e.Name.Length == 0 && (string.IsNullOrEmpty(e.CollectionItemElementName) || string.Compare(e.CollectionItemElementName, reader.LocalName, StringComparison.OrdinalIgnoreCase) == 0)) { collection = c; type = e.CollectionItemType; if (context != null && (type == null || type.IsInterface || type.IsAbstract)) { type = context.ResolveType(reader); } if (type == null || type.IsInterface || type.IsAbstract) { continue; } return; } } } collection = null; type = null; throw new XsException(reader, string.Format("Unknown xml element '{0}'", reader.Name)); }
/// <summary> /// Get properties of the specified type /// </summary> /// <param name="type"></param> /// <returns></returns> public static PropertyInfo[] GetOrderedElementProperties(Type type) { var sequenceElements = new List <KeyValuePair <PropertyInfo, int> >(); PropertyInfo[] prop = type.GetProperties(); for (int i = 0; i < prop.Length; ++i) { PropertyInfo pi = prop[i]; var atn = (CustomAttributeHelper.First <XsElementAttribute>(pi)); if (atn == null || atn.Name.StartsWith("_", StringComparison.Ordinal)) { continue; } int order = (atn.Ordering) * 10000 + i; sequenceElements.Add(new KeyValuePair <PropertyInfo, int>(pi, order)); } sequenceElements.Sort((x1, x2) => (x1.Value - x2.Value)); PropertyInfo[] ret = new PropertyInfo[sequenceElements.Count]; for (int i = 0; i < sequenceElements.Count; ++i) { ret[i] = sequenceElements[i].Key; } return(ret); }
private bool processText(string text) { foreach (PropertyInfo c in GetType().GetProperties()) { foreach (XsAttributeAttribute a in CustomAttributeHelper.All <XsAttributeAttribute>(c)) { if (a.Name.Length == 0) { string s = (string)c.GetValue(this, null) ?? string.Empty; if (_textFound) { s = s.TrimEnd() + Environment.NewLine + text.TrimStart(); } else { s = text; } _textFound = true; c.SetValue(this, s, null); return(true); } } } return(false); }
/// Determine if the specified object is empty and should not be saved if <see cref="SkipIfEmpty"/> is set public bool IsEmpty(object value) { if (value == null) { return(true); } IEnumerable e = (value as IEnumerable); if (e != null) { IEnumerator en = e.GetEnumerator(); if (en != null) { if (!AtLeastOneChildForNotEmpty || en.MoveNext()) { return(false); } } } // element is not empty if it has a not empty sub-element var props = value.GetType().GetProperties(); foreach (PropertyInfo c in props) { var ee = CustomAttributeHelper.First <XsElementAttribute>(c); if (ee != null && !ee.IsEmpty(c.GetValue(value, null))) { return(false); } } // Or any non-empty attributes object defValue = null; foreach (PropertyInfo c in props) { var n = XsAttributeAttribute.GetNames(c, false); if (n == null) { continue; } object v = c.GetValue(value, null); if (v != null) { if (defValue == null) { defValue = Utils.CreateInstance(value.GetType()); } object vdef = c.GetValue(defValue, null); if (!v.Equals(vdef)) { return(false); } } } return(true); }
private static bool hasText(PropertyInfo p) { foreach (XsAttributeAttribute a in CustomAttributeHelper.All <XsAttributeAttribute>(p)) { if (a.Name == string.Empty) // Text { return(true); } } return(false); }
/// <summary> /// Write element to the output stream /// </summary> /// <param name="writer">Where to write</param> /// <param name="nameOverride">Local name to be used, or null if name should be retirevent from <see cref="XsTypeAttribute"/> of the type.</param> public virtual void WriteXml(XmlWriter writer, string nameOverride) { string namesp = null; if (nameOverride == null) { foreach (XsTypeAttribute a in CustomAttributeHelper.All <XsTypeAttribute>(GetType())) { nameOverride = a.Name; namesp = a.Namespace; break; } if (nameOverride == null) { return; } } writer.WriteStartElement(nameOverride, namesp); WriteAttributes(writer); WriteText(writer); foreach (PropertyInfo c in GetOrderedElementProperties(GetType())) { object v = c.GetValue(this, null); IXsElement o = v as IXsElement; XsElementAttribute ab = CustomAttributeHelper.First <XsElementAttribute>(c); if (ab == null) { continue; } if (ab.Name.Length == 0) { IEnumerable e = (v as IEnumerable); if (e != null) { foreach (IXsElement action in e) { if (action != null) { action.WriteXml(writer, ab.CollectionItemElementName); } } } } else if (o != null && !(ab.SkipIfEmpty && ab.IsEmpty(v))) { o.WriteXml(writer, ab.Name); } } writer.WriteEndElement(); }
/// Try to find property matching the current XmlReader node or return null if not found. protected PropertyInfo FindRelatedProperty(XmlReader reader) { foreach (PropertyInfo c in GetType().GetProperties()) { foreach (XsElementAttribute elements in CustomAttributeHelper.All <XsElementAttribute>(c)) { if (string.Compare(elements.Name, reader.LocalName, StringComparison.OrdinalIgnoreCase) == 0) { return(c); } } } return(null); }
/// Get collection property associated with the current XML node, or null protected PropertyInfo FindCollectionForNode(XmlReader reader) { foreach (PropertyInfo c in GetType().GetProperties()) { foreach (XsElementAttribute e in CustomAttributeHelper.All <XsElementAttribute>(c)) { if (e.Name.Length == 0 && (string.IsNullOrEmpty(e.CollectionItemElementName) || string.Compare(e.CollectionItemElementName, reader.LocalName, StringComparison.OrdinalIgnoreCase) == 0)) { return(c); } } } return(null); }
private static bool allowsText(Type t) { foreach (var pi in t.GetProperties()) { foreach (var att in CustomAttributeHelper.All <XsAttributeAttribute>(pi)) { if (string.IsNullOrEmpty(att.Name)) { return(true); } } } return(false); }
/// <summary> /// Find action by ID /// </summary> /// <typeparam name="T">Type of action to search</typeparam> /// <param name="id">Action ID</param> /// <param name="throwIfNotFound">true if ScriptRuntimeException is thrown if the ID is not found</param> /// <returns>Found action or null, if not found</returns> public T Find <T>(string id, bool throwIfNotFound) where T : class, IScriptAction { T t = CallStack.FindTree <T>(id); if (throwIfNotFound && t == null) { var v = CustomAttributeHelper.First <XsTypeAttribute>(typeof(T)); if (v == null || v.Name == null) { throw new ScriptRuntimeException("Action with id='" + id + "' was not found"); } throw new ScriptRuntimeException(v.Name + " with id='" + id + "' was not found"); } return(t); }
private static void createEnum(PropertyInfo pi, Dictionary <Type, XmlSchemaType> xmlTypes, XmlSchema xmlSchema) { // Create enum var res = new XmlSchemaSimpleTypeRestriction(); res.BaseTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.NmToken).QualifiedName; foreach (var o in Enum.GetNames(pi.PropertyType)) { res.Facets.Add(new XmlSchemaEnumerationFacet { Value = (o.Substring(0, 1).ToLower() + o.Substring(1)) }); } XmlSchemaSimpleType st = new XmlSchemaSimpleType { Content = res }; // For flags must create a union of the values & string if (CustomAttributeHelper.Has <System.FlagsAttribute>(pi.PropertyType)) { XmlSchemaSimpleType st2 = new XmlSchemaSimpleType(); st2.Name = getXmlTypeName(pi.PropertyType); var union = new XmlSchemaSimpleTypeUnion(); XmlSchemaSimpleType st3 = new XmlSchemaSimpleType(); var res3 = new XmlSchemaSimpleTypeRestriction(); res3.BaseTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName; st3.Content = res3; union.BaseTypes.Add(st); union.BaseTypes.Add(st3); st2.Content = union; xmlSchema.Items.Add(st2); xmlTypes[pi.PropertyType] = st2; } else { st.Name = getXmlTypeName(pi.PropertyType); xmlSchema.Items.Add(st); xmlTypes[pi.PropertyType] = st; } }
/// Return names of XML attributes associated with this property, or null if no attributes are found public static string[] GetNames(PropertyInfo pi, bool includingDeprecated) { var v = CustomAttributeHelper.All <XsAttributeAttribute>(pi); if (v != null && v.Length > 0) { int cnt = 0; for (int i = 0; i < v.Length; ++i) { if (includingDeprecated || v[i].Deprecated == false) { cnt++; } } if (cnt == 0) { return(null); } var ret = new string[cnt]; var n = 0; for (int i = 0; i < v.Length; ++i) { if (includingDeprecated || v[i].Deprecated == false) { ret[n++] = v[i].Name; } } return(ret); } if (CustomAttributeHelper.Has <XmlIgnoreAttribute>(pi) || !pi.CanRead || !pi.CanWrite || pi.GetIndexParameters().Length != 0 || pi.GetSetMethod() == null) { return(null); } if (CustomAttributeHelper.Has <XsElementAttribute>(pi)) { return(null); } return(new string[] { (pi.Name.Substring(0, 1).ToLowerInvariant() + pi.Name.Substring(1)) }); }
/// Add action to the code public void Add(IScriptAction action) { string methodId; for (int n = 0; ; ++n) { if (string.IsNullOrEmpty(action.Id)) { methodId = "_" + CustomAttributeHelper.First <XsTypeAttribute>(action.GetType()).Name; } else { methodId = action.Id; } if (n != 0) { methodId += n; } if (!_methodNames.ContainsKey(methodId)) { _methodNames[methodId] = true; break; } } if (action is Script || methodId == action.Id || action is Sub) { Methods.Add(action); } else { Sequence b = new Sequence { Id = methodId }; b.Add(action); Methods.Add(b); Value += Environment.NewLine + "{ object __ret=" + methodId + "_inline(); if (__ret!=null) return __ret;}"; } }
/// <summary> /// Initialize action /// </summary> public virtual void Initialize() { foreach (PropertyInfo c in GetType().GetProperties()) { bool valSet = false; object val = null; foreach (XsElementAttribute e in CustomAttributeHelper.All <XsElementAttribute>(c)) { if (!valSet) { val = c.GetValue(this, null); valSet = true; } ScriptActionBase bl = val as ScriptActionBase; if (bl != null && bl._elementName == null) { bl._elementName = e.Name; } continue; } if (CustomAttributeHelper.Has <XsRequiredAttribute>(c)) { if (!valSet) { if (c.GetIndexParameters().Length > 0) { continue; } val = c.GetValue(this, null); } if (val == null) { throw new ParsingException(string.Format("Required attribute '{0}' is not set", XsAttributeAttribute.GetNames(c, false)[0])); } } if ((c.PropertyType == typeof(object) || c.PropertyType == typeof(string)) && !CustomAttributeHelper.Has <XsNotTransformed>(c) && !CustomAttributeHelper.Has <XmlIgnoreAttribute>(c) && c.GetSetMethod() != null) { if (!valSet) { if (c.GetIndexParameters().Length > 0) { continue; } val = c.GetValue(this, null); } if (val != null) { if (val is string) { Context.AssertGoodTransform((string)val, Transform); } else { Context.AssertGoodTransform(val.ToString(), Transform); } } } } }
private static XmlSchemaObjectCollection createComplexType(Type type, XmlSchemaComplexType ct, string ns, Dictionary <Type, XmlSchemaGroup> intfs) { ct.IsMixed = allowsText(type); XmlSchemaSequence sequence = new XmlSchemaSequence(); foreach (var pi in XsElement.GetOrderedElementProperties(type)) { var atn = (CustomAttributeHelper.First <XsElementAttribute>(pi)); if (atn.CollectionItemType != null && intfs.ContainsKey(atn.CollectionItemType)) { sequence.Items.Add(new XmlSchemaGroupRef { MinOccurs = 0, MaxOccursString = "unbounded", RefName = new XmlQualifiedName(getXmlTypeName(atn.CollectionItemType), ns) }); } else if (atn.CollectionItemType == null) { if (atn.Name == "") { sequence.Items.Add(new XmlSchemaAny { MinOccurs = 0, MaxOccursString = "unbounded", ProcessContents = XmlSchemaContentProcessing.Skip }); } else { sequence.Items.Add(new XmlSchemaElement { MinOccurs = 0, MaxOccurs = 1, Name = atn.Name, SchemaTypeName = new XmlQualifiedName(getXmlTypeName(pi.PropertyType), ns) }); } } else { sequence.Items.Add(new XmlSchemaElement { MinOccurs = 0, MaxOccursString = "unbounded", Name = atn.CollectionItemElementName, SchemaTypeName = new XmlQualifiedName(getXmlTypeName(atn.CollectionItemType), ns) }); } } if (sequence.Items.Count > 0) { ct.Particle = sequence; } else { ct.Particle = null; } return(ct.Attributes); }
///<summary>Generate XML schema for the given types ///</summary> ///<param name="ns">Default namespace</param> ///<param name="types">Types to include into schema</param> ///<param name="root">Root element</param> ///<param name="interfaces">Interface types</param> ///<returns>Built schema</returns> public static XmlSchema BuildSchema(string ns, Type[] types, Type root, Type[] interfaces) { XmlSchema xmlSchema = new XmlSchema(); xmlSchema.Namespaces.Add("xsd", "http://www.w3.org/2001/XMLSchema"); xmlSchema.Namespaces.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance"); xmlSchema.ElementFormDefault = XmlSchemaForm.Qualified; xmlSchema.AttributeFormDefault = XmlSchemaForm.Unqualified; xmlSchema.Namespaces.Add("ns", ns); xmlSchema.TargetNamespace = ns; // Comment XmlSchemaAnnotation annotation = new XmlSchemaAnnotation(); XmlSchemaDocumentation documentation = new XmlSchemaDocumentation(); XmlDocument helperDocument = new XmlDocument(); string comment = String.Format(" XML schema for {0} , generated at {1} ", ns, DateTime.Now.ToString()); documentation.Markup = new XmlNode[1] { helperDocument.CreateComment(comment) }; annotation.Items.Add(documentation); xmlSchema.Items.Add(annotation); // Create group "action" to refer to any action var ints = new Dictionary <Type, XmlSchemaGroup>(); if (interfaces != null) { foreach (var intf in interfaces) { var action = new XmlSchemaGroup(); action.Name = getXmlTypeName(intf); action.Particle = new XmlSchemaChoice(); xmlSchema.Items.Add(action); ints.Add(intf, action); } } Dictionary <Type, XmlSchemaType> xmlTypes = new Dictionary <Type, XmlSchemaType>(); foreach (var type in types) { // If it does not have our XML header - skip it var na = (CustomAttributeHelper.First <XsTypeAttribute>(type)); if (na == null) { continue; } // Check if it is complex or simple XmlSchemaComplexType ct = new XmlSchemaComplexType(); ct.Name = getXmlTypeName(type); XmlSchemaObjectCollection attr = createComplexType(type, ct, ns, ints); // Add the new element as an option to the "action" group foreach (var i in ints) { bool isAction = (type.FindInterfaces((tp, nu) => tp == i.Key, null).Length != 0); if (isAction) { foreach (var tp in CustomAttributeHelper.All <XsTypeAttribute>(type)) { if (!string.IsNullOrEmpty(tp.Name)) { i.Value.Particle.Items.Add(new XmlSchemaElement { Name = tp.Name, MinOccurs = 0, SchemaTypeName = new XmlQualifiedName(ct.Name, ns) }); } } } } // Work with attributes foreach (var o in generateAttributes(xmlSchema, type, xmlTypes, ns)) { attr.Add(o); } if (na.AnyAttribute) { ct.AnyAttribute = new XmlSchemaAnyAttribute { ProcessContents = XmlSchemaContentProcessing.Skip }; } // Add type to the list xmlTypes.Add(type, ct); xmlSchema.Items.Add(ct); if (root.IsAssignableFrom(type)) { // Add all variations of Script names as element foreach (var o in CustomAttributeHelper.All <XsTypeAttribute>(root)) { xmlSchema.Items.Add(new XmlSchemaElement { Name = o.Name, SchemaTypeName = new XmlQualifiedName(xmlTypes[typeof(Script)].Name, ns) }); } } } return(xmlSchema); }
private static List <XmlSchemaAttribute> generateAttributes(XmlSchema xmlSchema, Type type, Dictionary <Type, XmlSchemaType> xmlTypes, string ns) { var def = Utils.CreateInstance(type); var ret = new List <XmlSchemaAttribute>(); foreach (var pi in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)) { var attnames = XsAttributeAttribute.GetNames(pi, false); if (attnames == null) { continue; } foreach (var nameAttr in attnames) { if (string.IsNullOrEmpty(nameAttr)) { continue; } XmlSchemaAttribute xsa = new XmlSchemaAttribute(); xsa.Name = nameAttr; if (pi.PropertyType == typeof(bool)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Boolean).QualifiedName; } else if (pi.PropertyType == typeof(int)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Integer).QualifiedName; } else if (pi.PropertyType == typeof(float)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Float).QualifiedName; } else if (pi.PropertyType == typeof(double)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Double).QualifiedName; } else if (pi.PropertyType == typeof(uint)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.UnsignedInt).QualifiedName; } else if (pi.PropertyType == typeof(ulong)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.UnsignedLong).QualifiedName; } else if (pi.PropertyType == typeof(long)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Long).QualifiedName; } else if (pi.PropertyType == typeof(decimal)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Decimal).QualifiedName; } else if (pi.PropertyType.IsEnum) { if (!xmlTypes.ContainsKey(pi.PropertyType)) { createEnum(pi, xmlTypes, xmlSchema); } xsa.SchemaTypeName = new XmlQualifiedName(xmlTypes[pi.PropertyType].Name, ns); } else if (pi.PropertyType == typeof(string)) { xsa.SchemaTypeName = XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).QualifiedName; } var req = CustomAttributeHelper.First <XsRequiredAttribute>(pi); if (req != null && (string.IsNullOrEmpty(req.Name) || req.Name == nameAttr)) { xsa.Use = XmlSchemaUse.Required; } else { object v = pi.GetValue(def, null); if (v != null) { string value = v.ToString(); if ((pi.PropertyType.IsPrimitive || pi.PropertyType.IsEnum)) { value = (value.Substring(0, 1).ToLower() + value.Substring(1)); } xsa.DefaultValue = value; } } ret.Add(xsa); } } return(ret); }
private static object convertString(string text, Type pt) { if (pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable <>)) { if (String.IsNullOrEmpty(text)) { return(null); } return(convertString(text, pt.GetGenericArguments()[0])); } if (String.IsNullOrEmpty(text)) { if (pt.IsPrimitive || pt.IsEnum || pt == typeof(decimal)) { text = "0"; } } if (pt == typeof(bool)) { int t; if (Int32.TryParse(text, out t)) { return(t != 0); } return(Boolean.Parse(text)); } if (pt == typeof(byte) || pt == typeof(sbyte) || pt == typeof(short) || pt == typeof(ushort) || pt == typeof(int) || pt == typeof(uint) || pt == typeof(long) || pt == typeof(ulong) || pt == typeof(decimal) || pt == typeof(float) || pt == typeof(double)) { object o = ParsingReader.TryParseNumber(text); if (o == null) { throw new InvalidCastException("Cannot cast '" + text + "' to " + pt.FullName); } return(Convert.ChangeType(o, pt)); } if (pt == typeof(char?)) { if (string.IsNullOrEmpty(text)) { return(null); } return(text[0]); } if (pt == typeof(char)) { return(text[0]); } if (pt == typeof(DateTime)) { return(DateTime.Parse(text)); } if (pt == typeof(Guid)) { return(new Guid(text)); } if (pt == typeof(string)) { return(text); } if (pt.IsEnum) { bool hasFlags = CustomAttributeHelper.Has <FlagsAttribute>(pt); bool hasDigits = !string.IsNullOrEmpty(text) && text.IndexOfAny("0123456789".ToCharArray()) != -1; bool isempty = string.IsNullOrEmpty(text); if (isempty || hasFlags || hasDigits) { long val = 0; var names = Enum.GetNames(pt); var dictionary = new Dictionary <string, long>(StringComparer.OrdinalIgnoreCase); var values = Enum.GetValues(pt); for (int i = 0; i < names.Length; ++i) { long vv = (long)Convert.ChangeType(values.GetValue(i), typeof(long)); dictionary[names[i]] = vv; if (isempty && vv == 0) { return(Enum.ToObject(pt, vv)); } } if (isempty) { throw new InvalidCastException(String.Format("Unexpected empty enum value")); } int step = 0; foreach (string str in text.Split(s_enumDelimiters)) { if (String.IsNullOrEmpty(str)) { continue; } step++; if (!hasFlags && step > 1) { throw new InvalidCastException(String.Format("Unexpected enum value {0}", str)); } long v; if (char.IsDigit(str[0])) { val |= ParsingReader.ParseNumber <long>(str); } else if (dictionary.TryGetValue(str, out v)) { val |= v; } else { throw new InvalidCastException(String.Format("Unexpected enum value {0}", str)); } } return(Enum.ToObject(pt, val)); } return(Enum.Parse(pt, text, true)); } throw new InvalidCastException(String.Format("'{0}' cannot be converted to {1}", text, pt.ToString())); }
/// <summary> /// Returns a <see cref="System.String"/> that represents this instance. /// </summary> /// <returns> /// A <see cref="System.String"/> that represents this instance. /// </returns> public override string ToString() { StringBuilder sb = new StringBuilder(); XsTypeAttribute v = CustomAttributeHelper.First <XsTypeAttribute>(GetType()); if (_elementName != null) { sb.Append(_elementName); } else { sb.Append((v == null) ? GetType().FullName : v.Name); } sb.Append("("); bool first = true; if (!string.IsNullOrEmpty(Id)) { sb.AppendFormat("id=\"{0}\"", Id); first = false; } object def = null; foreach (PropertyInfo pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty)) { var n = XsAttributeAttribute.GetNames(pi, false); if (n == null || n[0] == "id" || n[0] == "password") { continue; } object o = pi.GetValue(this, null); if (def == null) { def = Utils.CreateInstance(GetType()); } if (o == null || o.Equals(pi.GetValue(def, null)) || (n[0] == string.Empty && n.Length == 1)) { continue; } if (!first) { sb.Append(", "); } first = false; sb.Append(n[0]); sb.Append("="); sb.Append("\""); string s = o.ToString().Trim(); s = s.Replace("\r", "\\r"); s = s.Replace("\n", "\\n"); s = s.Replace("\t", "\\t"); int maxW = (n[0] == "from" || n[0] == "location") ? 50 : 30; var fw = (n[0] == "from" || n[0] == "location") ? FitWidthOption.EllipsisStart : FitWidthOption.EllipsisEnd; sb.Append(Utils.FitWidth(s, maxW, fw)); sb.Append("\""); } sb.Append(")"); return(sb.ToString()); }