.net - 继承 xdocument/xelement 命名空间问题(谷歌联系人 API)

标签 .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

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

  • 我很感激关于这个主题的任何建议

    非常感谢你

    编辑

    这是一个更完整的代码示例,根据 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
    

    最佳答案

    目前还不完全清楚究竟出了什么问题——如果你在任何地方都指定了正确的命名空间,一切都应该没问题。但是,您不需要在所有地方重复这些名称——您可能希望创建类型为 XName 的共享只读字段。以免打错字。我也会使用来自 String 的隐式转换至 XName为简单起见。在 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;你说你“没有发现它们很有帮助”,但这可能只是意味着你没有足够长的时间调查它们。一旦您掌握了它们的窍门,它们很可能会节省您的时间。
  • 我不会在您提供的代码中使用任何继承 - 无需从 WebClient 派生, XDocumentXElement .如果没有这里的继承,您的代码会更清晰,因为您可以只创建与您尝试建模的对象相关的成员。
  • 您可以使用 LINQ 查询 XDocument并创建联系人集合。例如(再次使用 C#,但 VB 会类似):
    List<GoogleContact> = document.Root
               .Elements(GoogleContact.EntryElementName)
               .Select(element => new GoogleContact(element))
               .ToList();
    
  • 关于.net - 继承 xdocument/xelement 命名空间问题(谷歌联系人 API),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9856034/

    相关文章:

    xml - XSLT - 在输出中用转义文本替换撇号

    c++ - 在命名空间中定义已在 cpp 文件中定义的函数

    c# - 我应该接受同一个对象作为参数并返回它还是只接受它作为参数?

    c# - 为 Console.WriteLine() 创建快捷方式

    css - 为什么这个 XML 选择器不能从我试图抓取的网站中获取正确的数据?

    c# - 代码使用错误的命名空间

    .NET - 是否可以声明只能访问命名空间的模块成员?

    c# - 获取 XNA 支持的屏幕分辨率?

    c# - "The process cannot access the file X because it is used by another process"-C#

    java - 第二个 Activity - Android 应用程序。无法实例化 Activity ComponentInfo