im trying to parse the XML returned by Google contacts API
i made some helper classes to give me strongly-typed access to the necessary data, but cant seem to be making them work in tandem.
i made a class GoogleDocument which inherits XDocument and a GoogleContact which inherits XElement
Class GoogleDocument
Inherits XDocument
Dim xnsAtom = XNamespace.Get("http://www.w3.org/2005/Atom")
Sub New()
MyBase.new()
End Sub
Sub New(other As XDocument)
MyBase.New(other)
End Sub
ReadOnly Property Entries As IEnumerable(Of GoogleContact)
Get
Dim feed = Element(xnsAtom + "feed")
Dim ret = New List(Of GoogleContact)
For Each e In feed.Elements(xnsAtom + "entry")
ret.Add(New GoogleContact(e))
Next
Return ret.AsEnumerable
End Get
End Property
End Class
Class GoogleContact
Inherits XElement
Dim xnsGd = XNamespace.Get("http://schemas.google.com/g/2005")
Dim xnsAtom = XNamespace.Get("http://www.w3.org/2005/Atom")
Dim xnsApp = XNamespace.Get("http://www.w3.org/2007/app")
Sub New(other As XElement)
MyBase.new(other)
End Sub
ReadOnly Property ETag As String
Get
Return Attribute(xnsGd + "etag").Value
End Get
End Property
ReadOnly Property ContactID As Integer
Get
Dim uri = Element(xnsAtom + "id").Value
Return uri.Substring(uri.LastIndexOf("/") + 1)
End Get
End Property
ReadOnly Property Edited As DateTime
Get
Return Date.Parse(Element(xnsApp + "edited").Value)
End Get
End Property
End Class
questions:
- isn’t there an easier way to convert all matching elements to GoogleContacts? then adding each 1 via iteration. also it seems that GoogleContact is not really an XElement as in the debugger it shows up as
{<entry....>}instead of<entry....>im not sure what these braces mean here, but its odd - why do i need to declare the namespaces again and again? isn’t there a way to somehow pass over to GoogleContact all relevant namespaces? the way its now, Google refuses to accept the data back as all namespaces become “p1”
i’d appreciate any advice on the subject
thank you very much
EDIT
here’s a more complete code sample, with changes per Jon Skeet’s suggestions
Imports System.Net
Imports System.Text
Imports System.IO
Imports System.Collections.Specialized
Imports System.Runtime.CompilerServices
Module Module1
Dim GUserName As String
Dim GPassword As String
Sub Main()
Dim authRequest As HttpWebRequest = HttpWebRequest.Create("https://www.google.com/accounts/ClientLogin")
authRequest.KeepAlive = True
authRequest.ContentType = "application/x-www-form-urlencoded"
authRequest.Method = "POST"
Dim encoder = New ASCIIEncoding
Dim encodedData = encoder.GetBytes("Email=" & GUserName & "&Passwd=" & GPassword & "&source=Consultor&service=cp&accountType=HOSTED_OR_GOOGLE")
authRequest.ContentLength = encodedData.Length
Dim requestStream = authRequest.GetRequestStream
requestStream.Write(encodedData, 0, encodedData.Length)
requestStream.Close()
Dim authResponse = authRequest.GetResponse
Dim readStream = New StreamReader(authResponse.GetResponseStream, encoder)
Dim body = readStream.ReadToEnd
Dim tokens = TextCollection(body, "=", Chr(10))
Dim req2 = New GoogleClient(tokens("auth"))
body = req2.GetString("default/full?max-results=5000")
Dim gDoc = New GoogleDocument(XDocument.Parse(body))
Dim dcx = DBEntities()
Dim pers = dcx.Persons
For Each ge In gDoc.Entries
Dim entry = ge
Dim id As String = entry.ContactID
Dim p As Object '= (From x In pers Where x.GoogleCode = id).FirstOrDefault' cant ompile iin this demo
If p Is Nothing Then Exit For
If entry.Edited > p.LastEdit Then
p.GoogleCode = entry.ContactID
dcx.SaveChanges()
Else
Dim updClient = New GoogleClient(tokens("auth"))
updClient.ETag = entry.ETag
Dim updResp = updClient.PutString("http://www.google.com/m8/feeds/contacts/" & GUserName & "/base/" & entry.ContactID, entry.UpdateXml)
End If
Next
End Sub
Class GoogleClient
Inherits WebClient
Property ETag As String
Const UrlStart = "https://www.google.com/m8/feeds/contacts/"
Sub New(AuthToken As String)
Headers.Add("Content-Type", "application/atom+xml; charset=UTF-8")
Headers.Add("User-Agent", "G-Consultor/GDataGAuthRequestFactory-CS-Version=1.9.0.23118--IEnumerable")
Headers.Add("Authorization", "GoogleLogin auth=" & AuthToken)
Headers.Add("GData-Version", "3.0")
End Sub
Function GetString(Path As String) As String
Return DownloadString(UrlStart & Path)
End Function
Public Function PutString(address As String, data As String) As String
If ETag <> "" Then
Headers.Add("Etag", ETag)
Headers.Add("If-Match", ETag)
End If
Return UploadString(address, "PUT", data)
End Function
End Class
Function TextCollection(Text As String, FieldDelimiter As String, Optional RowDelimiter As String = vbCrLf) As NameValueCollection
Text = Text.RightCut(RowDelimiter)
Dim ret = New NameValueCollection
Dim rows = Text.Split(RowDelimiter)
For Each cl In rows
ret.Add(cl.Substring(0, cl.IndexOf(FieldDelimiter)), cl.Substring(cl.IndexOf(FieldDelimiter) + FieldDelimiter.Length))
Next
Return ret
End Function
Class GoogleDocument
Inherits XDocument
Dim xnsAtom = XNamespace.Get("http://www.w3.org/2005/Atom")
Sub New()
MyBase.new()
End Sub
Sub New(other As XDocument)
MyBase.New(other)
End Sub
ReadOnly Property Entries As IEnumerable(Of GoogleContact)
Get
Dim feed = Element(xnsAtom + "feed")
Dim ret = New List(Of GoogleContact)
For Each e In feed.Elements(xnsAtom + "entry")
ret.Add(New GoogleContact(e))
Next
Return ret.AsEnumerable
End Get
End Property
End Class
Function DBEntities() As Object 'really should return my EF data model
Return Nothing
End Function
<Extension()> Function RightCut(value As String, CutString As String) As String
If Right(value, CutString.Length) = CutString Then value = value.Substring(0, value.Length - CutString.Length)
Return value
End Function
Class GoogleContact
Dim xnsGd = XNamespace.Get("http://schemas.google.com/g/2005")
Dim xnsAtom = XNamespace.Get("http://www.w3.org/2005/Atom")
Dim xnsApp = XNamespace.Get("http://www.w3.org/2007/app")
Dim xContact As XElement
Sub New(entry As XElement)
xContact = entry
End Sub
ReadOnly Property ETag As String
Get
Return xContact.Attribute(xnsGd + "etag").Value
End Get
End Property
ReadOnly Property ContactID As Integer
Get
Dim uri = xContact.Element(xnsAtom + "id").Value
Return uri.Substring(uri.LastIndexOf("/") + 1)
End Get
End Property
ReadOnly Property Edited As DateTime
Get
Return Date.Parse(xContact.Element(xnsApp + "edited").Value)
End Get
End Property
ReadOnly Property UpdateXml
Get
Return "<?xml version=""1.0"" encoding=""utf-8""?>" & xContact.ToString
End Get
End Property
Overrides Function ToString() As String
Return xContact.ToString
End Function
End Class
End Module
It’s still not entirely clear what’s actually going wrong – if you specify the right namespace everywhere, it should all be fine. However, you don’t need to repeat the names all over the place – you may well want to create shared readonly fields of type
XNameto avoid typos. I’d also use the implicit conversion fromStringtoXNamefor simplicity. In C# I’d write this as something like:Then you can use those “constants” everywhere – it doesn’t matter that they’ve got namespaces; that doesn’t end up getting used within your code, because you’re just referring to “the element name” or “the attribute name” appropriately.
In general terms, ways to improve your code:
WebClient,XDocumentorXElement. Your code will be clearer without the inheritance here, as you can create just the members which are relevant for the object you’re try to model.You can use LINQ to query the
XDocumentand create a collection of contacts. For example (C# again, but VB would be similar):