继承xdocument / xelement命名空间问题(google contacts api)

时间:2012-03-24 22:04:04

标签: .net xml namespaces linq-to-xml

我试图解析Google通讯录API返回的XML

我制作了一些辅助类来给我强大的类型访问必要的数据,但似乎无法使它们协同工作。

我创建了一个继承XDocument的GoogleDocument类和一个继承XElement的GoogleContact

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

的问题:

  1. 是否有更简单的方法将所有匹配的元素转换为GoogleContacts?然后通过迭代添加每个1。似乎GoogleContact并不是真正的XElement,因为调试器显示为{<entry....>}而不是<entry....>我不知道这些括号在这里是什么意思,但它的奇怪
  2. 为什么我需要一次又一次地声明命名空间?是不是有办法以某种方式传递给GoogleContact所有相关的命名空间?现在,谷歌拒绝接受数据,因为所有命名空间都变成了“p1”
  3. 我很感激有关这个问题的任何建议

    非常感谢

    修改

    这是一个更完整的代码示例,根据Jon Skeet的建议进行更改

    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
    

1 个答案:

答案 0 :(得分:2)

目前还不完全清楚错误的实际情况 - 如果你在任何地方指定正确的命名空间,它应该都没问题。但是,您不需要在整个地方重复这些名称 - 您可能希望创建类型为XName的共享只读字段以避免拼写错误。为简单起见,我还使用从StringXName的隐式转换。在C#中,我将其写为:

private static readonly XNamespace AtomNs = "http://www.w3.org/2005/Atom";
private static readonly XNamespace GoogleDataNs =
    "http://schemas.google.com/g/2005";
private static readonly XNamespace AppNs = "http://www.w3.org/2007/app";

// You should work out where to put these, and their visibility
public static readonly XName FeedElementName = AtomNs + "feed";
public static readonly XName EntryElementName = AtomNs + "entry";
public static readonly XName ETagAttributeName = GoogleDataNs + "etag";
// etc

然后你可以在任何地方使用那些“常量” - 它们重要它们有命名空间;这最终不会在您的代码中使用,因为您只是恰当地引用了“元素名称”或“属性名称”。

一般而言,改进代码的方法:

  • 如果可以的话,我仍然建议使用.NET API;你说你“没有发现它们非常有帮助”,但这可能只是意味着你没有长时间调查它们。一旦你掌握了它们,它们就可以节省你的时间。
  • 我不会在您提供的代码中使用任何继承 - 无需从WebClientXDocumentXElement派生。如果没有继承,您的代码将更加清晰,因为您可以只创建与您尝试建模的对象相关的成员。
  • 您可以使用LINQ查询XDocument并创建联系人集合。例如(再次使用C#,但VB类似):

    List<GoogleContact> = document.Root
               .Elements(GoogleContact.EntryElementName)
               .Select(element => new GoogleContact(element))
               .ToList();