【问题标题】:inheriting xdocument/xelement namespace issues (google contacts api)继承 xdocument/xelement 命名空间问题(谷歌联系人 api)
【发布时间】:2012-04-09 00:09:41
【问题描述】:

我正在尝试解析 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?按照现在的方式,Google 拒绝接受返回的数据,因为所有命名空间都变为“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

【问题讨论】:

  • 为什么要从 XDocument 和 XElement 继承?这对我来说听起来是个坏主意 - 为什么不直接创建组合现有对象的类?
  • 几个原因:最初是因为我过去在类似的情况下这样做但没有命名空间,而且它自然适合以 xml sn-ps 形式出现的东西。但此外,因为最后我有时需要调用 ToString 以获取确切的 XML 结构以发回 Google 以获取更新
  • 这些对我来说都不是很好的理由。联系人自然不是 XML 元素 - 这正是它可能的表示方式。您始终可以覆盖 ToString()(或创建一个单独的方法)来获取 XML。
  • 我不想在 ToString 中重建整个 xml,因为我可以在整个代码中保留原始 xml,只添加一些属性
  • 您不必全部“重建” - 您只需在仍存储XElement 的成员变量上调用ToString。重点是使用 composition 而不是 inheritance

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


【解决方案1】:

仍然不完全清楚实际发生了什么错误 - 如果您在任何地方都指定了正确的命名空间,那么一切都应该没问题。但是,您不需要到处重复名称 - 您可能希望创建 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();
    

【讨论】:

  • 我会公开问你,在这一点上,因为我们可能非常接近弄清楚,你认为我应该放弃所有并尝试他们的 dll 吗?我想如果你这么说,我会走那条路。我不想浪费你的时间。但如果我可以让我自己的代码做到这一点,我将不胜感激。我也很欣赏你在最后发布的LINQ。关于继承,我确实喜欢这种方式,但我认为它不相关。但这一切并不能帮助我们在必要时为它们注入命名空间?你有什么建议?感谢您抽出这么多时间和您的洞察力(PS。随时在 C# 中发帖
  • @Yisman:是的,您绝对应该花一点时间使用旨在简化此操作的库。是的,继承部分与其他所有内容都有些正交,但我仍然强烈建议您仔细考虑它(不仅在这里,而且在其他地方)。
  • 好的,所以现在我不接受你的回答,我再试一次。如果它不会解决,我们会看到。感谢您的工作。您的帖子数量绝对令人印象深刻!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-11
  • 1970-01-01
  • 1970-01-01
  • 2016-10-08
  • 1970-01-01
相关资源
最近更新 更多