1
/* Copyright (c) 2006-2008 Google Inc.
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
16
* Oct 13 2008 Joe Feser joseph.feser@gmail.com
17
* Converted ArrayLists and other .NET 1.1 collections to use Generics
18
* Combined IExtensionElement and IExtensionElementFactory interfaces
21
#region Using directives
23
using System.Collections;
26
using Google.GData.Client;
29
namespace Google.GData.GoogleBase {
31
///////////////////////////////////////////////////////////////////////
32
/// <summary>Identifiers for standard Google Base attribute types.
33
/// All non-standard types share the type otherType</summary>
34
///////////////////////////////////////////////////////////////////////
35
public enum StandardGBaseAttributeTypeIds
37
/// <summary>Id of the standard type <c>text</c></summary>
39
/// <summary>Id of the standard type <c>boolean</c></summary>
42
/// <summary>Id of the standard type <c>location</c></summary>
45
/// <summary>Id of the standard type <c>url</c></summary>
48
/// <summary>Id of the standard type <c>int</c></summary>
51
/// <summary>Id of the standard type <c>float</c></summary>
54
/// <summary>Id of the standard type <c>number</c></summary>
57
/// <summary>Id of the standard type <c>intUnit</c></summary>
60
/// <summary>Id of the standard type <c>floatUnit</c></summary>
63
/// <summary>Id of the standard type <c>numberUnit</c></summary>
66
/// <summary>Id of the standard type <c>date</c></summary>
69
/// <summary>Id of the standard type <c>dateTime</c></summary>
72
/// <summary>Id of the standard type <c>dateTimeRange</c></summary>
75
/// <summary>Id of the standard type <c>shipping</c></summary>
78
/// <summary>An attribute type that could not be recognized by
79
/// the library. See the attribute name.</summary>
83
///////////////////////////////////////////////////////////////////////
84
/// <summary>Object representation of Google Base attribute types.
86
/// To get GBaseAttributeType instance, use one of the predefined
87
/// constants or call GBaseAttributeType.ForName(string)
89
///////////////////////////////////////////////////////////////////////
90
public class GBaseAttributeType
92
private readonly string name;
93
private readonly StandardGBaseAttributeTypeIds id;
94
private readonly GBaseAttributeType supertype;
96
/// <summary>Text attributes.</summary>
97
public static readonly GBaseAttributeType Text =
98
new GBaseAttributeType(StandardGBaseAttributeTypeIds.textType,
101
/// <summary>Boolean attributes.</summary>
102
public static readonly GBaseAttributeType Boolean =
103
new GBaseAttributeType(StandardGBaseAttributeTypeIds.booleanType,
106
/// <summary>Location attributes, as a free string.</summary>
107
public static readonly GBaseAttributeType Location =
108
new GBaseAttributeType(StandardGBaseAttributeTypeIds.locationType,
111
/// <summary>Url attributes.</summary>
112
public static readonly GBaseAttributeType Url =
113
new GBaseAttributeType(StandardGBaseAttributeTypeIds.urlType,
116
/// <summary>Number attribute: a float or an int.</summary>
117
public static readonly GBaseAttributeType Number =
118
new GBaseAttributeType(StandardGBaseAttributeTypeIds.numberType,
121
/// <summary>Integer attribute, a subtype of Number.</summary>
122
public static readonly GBaseAttributeType Int =
123
new GBaseAttributeType(Number,
124
StandardGBaseAttributeTypeIds.intType,
127
/// <summary>Float attribute, a subtype of Number.</summary>
128
public static readonly GBaseAttributeType Float =
129
new GBaseAttributeType(Number,
130
StandardGBaseAttributeTypeIds.floatType,
133
/// <summary>Number attribute with a unit ("12 km"). A supertype
134
/// of FloatUnit and IntUnit.</summary>
135
public static readonly GBaseAttributeType NumberUnit =
136
new GBaseAttributeType(StandardGBaseAttributeTypeIds.numberUnitType,
139
/// <summary>Int attribute with a unit, a subtype of NumberUnit.</summary>
140
public static readonly GBaseAttributeType IntUnit =
141
new GBaseAttributeType(NumberUnit,
142
StandardGBaseAttributeTypeIds.intUnitType,
145
/// <summary>Float attribute with a unit, a subtype of NumberUnit.</summary>
146
public static readonly GBaseAttributeType FloatUnit =
147
new GBaseAttributeType(NumberUnit,
148
StandardGBaseAttributeTypeIds.floatUnitType,
151
/// <summary>A time range, with a starting and an end date/time, a
152
/// supertype of DateTime and Date. For example:
153
/// "2006-01-01T12:00:00Z 2006-01-02T14:00:00Z"
155
/// Empty time ranges are considered equivalent to DateTime.</summary>
156
public static readonly GBaseAttributeType DateTimeRange =
157
new GBaseAttributeType(StandardGBaseAttributeTypeIds.dateTimeRangeType,
160
/// <summary>A date, a subtype of DateTime and DateTimeRange.</summary>
161
public static readonly GBaseAttributeType Date =
162
new GBaseAttributeType(DateTimeRange,
163
StandardGBaseAttributeTypeIds.dateType,
166
/// <summary>A date and a time, a subtype of DateTimeRange.</summary>
167
public static readonly GBaseAttributeType DateTime =
168
new GBaseAttributeType(Date,
169
StandardGBaseAttributeTypeIds.dateTimeType,
172
/// <summary>A Shipping object.</summary>
173
public static readonly GBaseAttributeType Shipping =
174
new GBaseAttributeType(StandardGBaseAttributeTypeIds.shippingType,
178
/// <summary>All standard attribute types.</summary>
179
public static readonly GBaseAttributeType[] AllStandardTypes = {
180
Text, Boolean, Location, Url,
182
IntUnit, FloatUnit, NumberUnit,
183
Date, DateTime, DateTimeRange
186
private static readonly IDictionary StandardTypesDict = new Hashtable();
187
static GBaseAttributeType()
189
foreach(GBaseAttributeType type in AllStandardTypes)
191
StandardTypesDict.Add(type.Name, type);
195
private GBaseAttributeType(GBaseAttributeType supertype,
196
StandardGBaseAttributeTypeIds id,
199
this.supertype = supertype;
204
private GBaseAttributeType(StandardGBaseAttributeTypeIds id, string name)
205
: this(null, id, name)
210
///////////////////////////////////////////////////////////////////////
211
/// <summary>Get or create an attribute with the given name.
212
/// If the name corresponds to a standard attribute, the global
213
/// instance will be returned. Otherwise, a new GBaseAttributeType
214
/// with Id = otherType will be created.</summary>
215
///////////////////////////////////////////////////////////////////////
216
public static GBaseAttributeType ForName(String name)
220
throw new ArgumentNullException("name");
222
GBaseAttributeType standard =
223
StandardTypesDict[name] as GBaseAttributeType;
224
if (standard != null)
229
return new GBaseAttributeType(StandardGBaseAttributeTypeIds.otherType,
233
///////////////////////////////////////////////////////////////////////
234
/// <summary>Returns the attribute name.</summary>
235
///////////////////////////////////////////////////////////////////////
236
public override string ToString()
241
///////////////////////////////////////////////////////////////////////
242
/// <summary>Compares two types by comparing their names.</summary>
243
///////////////////////////////////////////////////////////////////////
244
public override bool Equals(object o)
246
if (Object.ReferenceEquals(o, this))
251
if (!(o is GBaseAttributeType))
256
GBaseAttributeType other = o as GBaseAttributeType;
260
if (other.id == StandardGBaseAttributeTypeIds.otherType)
262
return name.Equals(other.name);
269
///////////////////////////////////////////////////////////////////////
270
/// <summary>Generates a hash code for this element that is
271
/// consistent with its Equals() method.</summary>
272
///////////////////////////////////////////////////////////////////////
273
public override int GetHashCode()
275
if (id == StandardGBaseAttributeTypeIds.otherType)
277
return 11 + name.GetHashCode();
286
///////////////////////////////////////////////////////////////////////
287
/// <summary>Checks whether this object is a supertype or the
288
/// same as another type.</summary>
289
/// <param name="subtype">other attribute type.</param>
290
///////////////////////////////////////////////////////////////////////
291
public bool IsSupertypeOf(GBaseAttributeType subtype)
297
GBaseAttributeType otherSupertype = subtype.Supertype;
298
if (otherSupertype == null)
302
return IsSupertypeOf(otherSupertype);
305
///////////////////////////////////////////////////////////////////////
306
/// <summary>Type name</summary>
307
///////////////////////////////////////////////////////////////////////
316
///////////////////////////////////////////////////////////////////////
317
/// <summary>Type identifier, otherType for nonstandard types.</summary>
318
///////////////////////////////////////////////////////////////////////
319
public StandardGBaseAttributeTypeIds Id
327
///////////////////////////////////////////////////////////////////////
328
/// <summary>This type's supertype or null.</summary>
329
///////////////////////////////////////////////////////////////////////
330
public GBaseAttributeType Supertype
338
///////////////////////////////////////////////////////////////////////
339
/// <summary>Comparison based on the Equals() method.</summary>
340
///////////////////////////////////////////////////////////////////////
341
public static bool operator ==(GBaseAttributeType a, GBaseAttributeType b)
343
if (Object.ReferenceEquals(a, b))
348
if (((object)a == null) || ((object)b == null))
356
///////////////////////////////////////////////////////////////////////
357
/// <summary>Comparison based on the Equals() method.</summary>
358
///////////////////////////////////////////////////////////////////////
359
public static bool operator !=(GBaseAttributeType a, GBaseAttributeType b)
366
///////////////////////////////////////////////////////////////////////
367
/// <summary>Extension element corresponding to a g: tag.
368
/// This element contains the logic to convert a g: tag to and from
371
/// It is usually not accessed through
372
/// <see href="GBaseAttributes">GBaseAttributes</see>.
374
///////////////////////////////////////////////////////////////////////
375
public class GBaseAttribute : IExtensionElementFactory
377
private static readonly char[] kXmlWhitespaces = { ' ', '\t', '\n', '\r' };
379
private GBaseAttributeType type;
380
private bool isPrivate;
381
/// <summary>Content, null if subElements != null.</summary>
382
private string content;
383
private string adjustedValue;
384
private string adjustedName;
386
/// <summary>Dictionary of: subElementName x subElementValue.
387
/// Null if content != null</summary>
388
private IDictionary subElements;
391
///////////////////////////////////////////////////////////////////////
392
/// <summary>Creates an empty GBaseAttribute.</summary>
393
///////////////////////////////////////////////////////////////////////
394
public GBaseAttribute()
398
///////////////////////////////////////////////////////////////////////
399
/// <summary>Creates a GBaseAttribute with a name and an undefined
401
/// <param name="name">attribute name</param>
402
///////////////////////////////////////////////////////////////////////
403
public GBaseAttribute(String name)
404
: this(name, null, null)
409
///////////////////////////////////////////////////////////////////////
410
/// <summary>Creates a GBaseAttribute with a name and a type.</summary>
411
/// <param name="name">attribute name</param>
412
/// <param name="type">attribute type or null if unknown</param>
413
///////////////////////////////////////////////////////////////////////
414
public GBaseAttribute(String name, GBaseAttributeType type)
415
: this(name, type, null)
419
///////////////////////////////////////////////////////////////////////
420
/// <summary>Creates a GBaseAttribute</summary>
421
/// <param name="name">attribute name</param>
422
/// <param name="type">attribute type or null if unknown</param>
423
/// <param name="content">value</param>
424
///////////////////////////////////////////////////////////////////////
425
public GBaseAttribute(String name, GBaseAttributeType type, String content)
429
this.content = content;
432
///////////////////////////////////////////////////////////////////////
433
/// <summary>Attribute name</summary>
434
///////////////////////////////////////////////////////////////////////
447
///////////////////////////////////////////////////////////////////////
448
/// <summary>Attribute type</summary>
449
///////////////////////////////////////////////////////////////////////
450
public GBaseAttributeType Type
462
///////////////////////////////////////////////////////////////////////
463
/// <summary>Attribute content, as a string.</summary>
464
///////////////////////////////////////////////////////////////////////
465
public string Content
477
///////////////////////////////////////////////////////////////////////
478
/// <summary>Private access (XML attribute access="private")</summary>
479
///////////////////////////////////////////////////////////////////////
480
public bool IsPrivate
493
///////////////////////////////////////////////////////////////////////
494
/// <summary>A better name for this attribute, recommended by
495
/// Google (when adjustments are turned on)</summary>
496
///////////////////////////////////////////////////////////////////////
497
public string AdjustedName
505
adjustedName = value;
510
///////////////////////////////////////////////////////////////////////
511
/// <summary>A better value for this string attribute, recommended by
512
/// Google (when adjustments are turned on)</summary>
513
///////////////////////////////////////////////////////////////////////
514
public string AdjustedValue
518
return adjustedValue;
522
adjustedValue = value;
526
//////////////////////////////////////////////////////////////////////
527
/// <summary>Accesses sub-elements.
529
/// Sub-elements are XML elements put inside the attribute
531
/// <g:shipping>
532
/// <g:country>FR</g:country>
533
/// <g:price>12 EUR</g:price>
534
/// </g:shipping>
536
/// An attribute cannot contain both sub-elements and text (content)
538
//////////////////////////////////////////////////////////////////////
539
public string this[string subElementName]
543
if (subElements == null)
547
return (string)subElements[subElementName];
551
if (subElements == null)
553
subElements = new Hashtable();
558
subElements.Remove(subElementName);
562
subElements[subElementName] = value;
567
//////////////////////////////////////////////////////////////////////
568
/// <summary>Checks whether the attribute has sub-elements.
569
/// You can access sub-element names using the property
570
/// SubElementSames.</summary>
571
//////////////////////////////////////////////////////////////////////
572
public bool HasSubElements
576
return subElements != null && subElements.Count > 0;
580
//////////////////////////////////////////////////////////////////////
581
/// <summary>The list of sub-elements name, which might be empty.
583
/// The sub-elements names are returned in no particular order.
585
/// You can get the value of these sub-elements using this[name].
587
//////////////////////////////////////////////////////////////////////
588
public string[] SubElementNames
592
if (subElements == null)
594
return new string[0];
596
string[] retval = new string[subElements.Count];
597
subElements.Keys.CopyTo(retval, 0);
603
///////////////////////////////////////////////////////////////////////
604
/// <summary>Parses an XML node and create the corresponding
605
/// GBaseAttribute.</summary>
606
///////////////////////////////////////////////////////////////////////
607
public static GBaseAttribute ParseGBaseAttribute(XmlNode node)
611
throw new ArgumentNullException("node");
614
GBaseAttribute attribute = new GBaseAttribute();
615
attribute.Name = FromXmlTagName(node.LocalName);
616
String value = Utilities.GetAttributeValue("type", node);
619
attribute.Type = GBaseAttributeType.ForName(value);
621
value = Utilities.GetAttributeValue("access",node);
622
attribute.IsPrivate = "private".Equals(value);
624
foreach (XmlNode child in node.ChildNodes)
626
if (child.NodeType == XmlNodeType.Element)
629
if (child.NamespaceURI == GBaseNameTable.NSGBaseMeta)
631
object localName = child.LocalName;
632
if (localName.Equals("adjusted_name"))
634
attribute.AdjustedName = child.InnerText;
637
else if (localName.Equals("adjusted_value"))
639
attribute.AdjustedValue = child.InnerText;
643
// Keep everything else as XML
646
attribute[child.LocalName] = child.InnerXml;
651
// If there are sub-elements, set the Content to null unless
652
// there is clearly something in there.
653
string content = ExtractDirectTextChildren(node);
654
if (!"".Equals(content.Trim(kXmlWhitespaces)))
656
attribute.Content = content;
661
///////////////////////////////////////////////////////////////////////
662
/// <summary>Extracts all text nodes inside an element, ignoring
663
/// text nodes inside children (contrary to XmlNode.InnerText).
665
///////////////////////////////////////////////////////////////////////
666
private static string ExtractDirectTextChildren(XmlNode parent)
668
StringBuilder retval = new StringBuilder();
669
for (XmlNode child = parent.FirstChild; child != null; child = child.NextSibling)
671
if (child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA) {
672
retval.Append(child.Value);
675
return retval.ToString();
678
///////////////////////////////////////////////////////////////////////
679
/// <summary>Converts the current GBaseAttribute to XML.</summary>
680
///////////////////////////////////////////////////////////////////////
681
public void Save(XmlWriter writer)
683
writer.WriteStartElement(XmlPrefix,
688
writer.WriteAttributeString("type", type.Name);
692
writer.WriteAttributeString("access", "private");
695
if (adjustedValue != null)
697
writer.WriteStartElement(GBaseNameTable.GBaseMetaPrefix,
699
GBaseNameTable.NSGBaseMeta);
700
writer.WriteString(adjustedValue);
701
writer.WriteEndElement();
704
if (adjustedName != null)
706
writer.WriteStartElement(GBaseNameTable.GBaseMetaPrefix,
708
GBaseNameTable.NSGBaseMeta);
709
writer.WriteString(adjustedName);
710
writer.WriteEndElement();
715
writer.WriteString(content);
717
if (subElements != null)
719
foreach (string elementName in subElements.Keys)
721
string elementValue = (string)subElements[elementName];
722
if (elementValue != null)
724
writer.WriteStartElement(GBaseNameTable.GBasePrefix,
725
ToXmlTagName(elementName),
726
GBaseNameTable.NSGBase);
727
writer.WriteString(elementValue);
728
writer.WriteEndElement();
732
writer.WriteEndElement();
735
///////////////////////////////////////////////////////////////////////
736
/// <summary>A partial text representation, to help
737
/// debugging.</summary>
738
///////////////////////////////////////////////////////////////////////
739
public override string ToString()
741
StringBuilder retval = new StringBuilder();
749
foreach (string elementName in subElements.Keys)
759
retval.Append(elementName);
760
retval.Append(":\"");
761
retval.Append(subElements[elementName]);
765
else if (content != null)
768
retval.Append(content);
771
return retval.ToString();
774
///////////////////////////////////////////////////////////////////////
775
/// <summary>Compares two attribute names, types, content and
776
/// private flag.</summary>
777
///////////////////////////////////////////////////////////////////////
778
public override bool Equals(object o)
780
if (!(o is GBaseAttribute))
785
if (Object.ReferenceEquals(this, o))
790
GBaseAttribute other = o as GBaseAttribute;
791
return name == other.name && type == other.type &&
792
content == other.content && isPrivate == other.isPrivate &&
793
adjustedName == other.adjustedName && adjustedValue == other.adjustedValue &&
794
HashTablesEquals(subElements, other.subElements);
797
/// <summary>Compares two hash tables.</summary>
798
private bool HashTablesEquals(IDictionary a, IDictionary b)
808
ICollection aKeys = a.Keys;
809
ICollection bKeys = b.Keys;
810
if (aKeys.Count != bKeys.Count)
814
foreach (string key in aKeys)
816
object aValue = a[key];
817
object bValue = b[key];
818
if (aValue != bValue && (aValue == null || !aValue.Equals(bValue)))
826
///////////////////////////////////////////////////////////////////////
827
/// <summary>Generates a hash code for this element that is
828
/// consistent with its Equals() method.</summary>
829
///////////////////////////////////////////////////////////////////////
830
public override int GetHashCode()
832
int retval = 49 * (27 + name.GetHashCode()) + type.GetHashCode();
835
retval = 49 * retval + content.GetHashCode();
840
///////////////////////////////////////////////////////////////////////
841
/// <summary>Given an attribute name, with spaces, generates
842
/// the corresponding XML tag name.</summary>
843
/// <param name="name">attribute name</param>
844
/// <returns>the name with all spaces replaced with underscores
846
///////////////////////////////////////////////////////////////////////
847
static string ToXmlTagName(string name)
849
return name.Replace(' ', '_');
852
///////////////////////////////////////////////////////////////////////
853
/// <summary>Given an XML tag name, with underscores, generates
854
/// the corresponding attribute name.</summary>
855
/// <param name="name">XML tag name, without namespace prefix</param>
856
/// <returns>the name with all underscores replaced with spaces
858
///////////////////////////////////////////////////////////////////////
859
static string FromXmlTagName(string name)
861
return name.Replace('_', ' ');
864
#region IExtensionElementFactory Members
866
public string XmlName
870
return ToXmlTagName(Name);
874
public string XmlNameSpace
878
return GBaseNameTable.NSGBase;
882
public string XmlPrefix
886
return GBaseNameTable.GBasePrefix;
890
public IExtensionElementFactory CreateInstance(XmlNode node, AtomFeedParser parser)
892
return ParseGBaseAttribute(node);