1 #Region "Microsoft.VisualBasic::c81e2e8d7a1bcde57d9e7266006eefa4, Microsoft.VisualBasic.Core\Text\Xml\Linq\Linq.vb"
2
3     ' Author:
4     
5     '       asuka (amethyst.asuka@gcmodeller.org)
6     '       xie (genetics@smrucc.org)
7     '       xieguigang (xie.guigang@live.com)
8     
9     ' Copyright (c) 2018 GPL3 Licensed
10     
11     
12     ' GNU GENERAL PUBLIC LICENSE (GPL3)
13     
14     
15     ' This program is free software: you can redistribute it and/or modify
16     ' it under the terms of the GNU General Public License as published by
17     ' the Free Software Foundation, either version 3 of the License, or
18     ' (at your option) any later version.
19     
20     ' This program is distributed in the hope that it will be useful,
21     ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22     ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     ' GNU General Public License for more details.
24     
25     ' You should have received a copy of the GNU General Public License
26     ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30     ' /********************************************************************************/
31
32     ' Summaries:
33
34     '     Module Data
35     
36     '         FunctionGetNodeNameDefine, GetTypeName, InternalIterates, LoadUltraLargeXMLDataSet, LoadXmlDataSet
37     '                   LoadXmlDocument, NodeInstanceBuilder, UltraLargeXmlNodesIterator
38     
39     
40     ' /********************************************************************************/
41
42 #End Region
43
44 Imports System.Runtime.CompilerServices
45 Imports System.Text
46 Imports System.Xml
47 Imports System.Xml.Serialization
48 Imports Microsoft.VisualBasic.Language
49
50 Namespace Text.Xml.Linq
51
52     ''' <summary>
53     ''' Using large xml file as Linq data source
54     ''' </summary>
55     Public Module Data
56
57         ''' <summary>
58         ''' Load a specific xml file from a file location <paramref name="pathOrDoc"/>/ or 
59         ''' a xml text document data into a <see cref="XmlDocument"/> object.
60         ''' </summary>
61         ''' <param name="pathOrDoc"></param>
62         ''' <returns></returns>
63         ''' <remarks>
64         ''' using internally XDocument.Load to parse whole XML at once
65         ''' </remarks>
66         <Extension>
67         Public Function LoadXmlDocument(pathOrDoc$, Optional preprocess As Func(Of StringString) = NothingAs XmlDocument
68             Dim xmlDoc As New XmlDocument()
69             Dim doc$
70
71             If pathOrDoc.FileExists Then
72                 If Not preprocess Is Nothing Then
73                     doc = preprocess(pathOrDoc.ReadAllText)
74                     xmlDoc.LoadXml(doc)
75                 Else
76                     Call xmlDoc.Load(pathOrDoc)
77                 End If
78             Else
79                 If Not preprocess Is Nothing Then
80                     doc = preprocess(pathOrDoc)
81                 Else
82                     doc = pathOrDoc
83                 End If
84
85                 Call xmlDoc.LoadXml(doc)
86             End If
87
88             Return xmlDoc
89         End Function
90
91         ''' <summary>
92         ''' Using <paramref name="default"/> string name or <see cref="Type.Name"/>
93         ''' </summary>
94         ''' <param name="type"></param>
95         ''' <param name="default$">
96         ''' If this parameter value is <see cref="StringEmpty"/>, then <see cref="Type.Name"/> will be use as the xml node name.
97         ''' </param>
98         ''' <returns></returns>
99         <Extension>
100         Public Function GetTypeName(type As Type, default$) As String
101             If [default].StringEmpty Then
102                 Return type.Name
103             Else
104                 Return [default]
105             End If
106         End Function
107
108         ''' <summary>
109         ''' 分别解析<see cref="XmlTypeAttribute"/>,<see cref="XmlRootAttribute"/>,如果这两个定义都不存在的话就返回<see cref="Type.Name"/>
110         ''' </summary>
111         ''' <param name="type"></param>
112         ''' <returns></returns>
113         <Extension>
114         Public Function GetNodeNameDefine(type As Type) As String
115             With type.GetCustomAttributes(GetType(XmlTypeAttribute), inherit:=True)
116                 If Not .IsNullOrEmpty Then
117                     With DirectCast(.First, XmlTypeAttribute).TypeName
118                         If Not .StringEmpty Then
119                             Return .ByRef
120                         End If
121                     End With
122                 End If
123             End With
124
125             With type.GetCustomAttributes(GetType(XmlRootAttribute), inherit:=True)
126                 If Not .IsNullOrEmpty Then
127                     With DirectCast(.First, XmlRootAttribute).ElementName
128                         If Not .StringEmpty Then
129                             Return .ByRef
130                         End If
131                     End With
132                 End If
133             End With
134
135             Return type.Name
136         End Function
137
138         Private Iterator Function InternalIterates(XML$, nodeName$) As IEnumerable(Of String)
139             Dim XmlNodeList As XmlNodeList = XML _
140                 .LoadXmlDocument _
141                 .GetElementsByTagName(nodeName)
142             Dim sb As New StringBuilder
143
144             For Each xmlNode As XmlNode In XmlNodeList
145                 Call sb.Clear()
146                 Call sb.Append($"<{nodeName} xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""")
147                 Call sb.Append(" ")
148
149                 For Each attr As XmlAttribute In xmlNode.Attributes
150                     Call sb.Append($"{attr.Name}=""{attr.Value}""")
151                     Call sb.Append(" ")
152                 Next
153
154                 Call sb.AppendLine(">")
155                 Call sb.AppendLine(xmlNode.InnerXml)
156                 Call sb.AppendLine($"</{nodeName}>")
157
158                 Yield sb.ToString
159             Next
160         End Function
161
162         ''' <summary>
163         ''' Only works for the xml file that contains a list or array of xml element, and then this function using this list element as linq data source.
164         ''' (这个函数只建议在读取比较小的XML文件的时候使用,并且这个XML文件仅仅是一个数组或者列表的序列化结果)
165         ''' </summary>
166         ''' <typeparam name="T"></typeparam>
167         ''' <param name="XML$">超大的XML文件的文件路径</param>
168         ''' <param name="typeName">
169         ''' 列表之中的节点在XML之中的tag标记名称,假若这个参数值为空的话,则会默认使用目标类型名称<see cref="Type.Name"/>
170         ''' </param>
171         ''' <param name="xmlns">
172         ''' Using for the namespace replacement.
173         ''' (当这个参数存在的时候,目标命名空间申明将会被替换为空字符串,数据对象才会被正确的加载)
174         ''' </param>
175         ''' <returns></returns>
176         <Extension>
177         Public Function LoadXmlDataSet(Of T As Class)(XML$, Optional typeName$ = NothingOptional xmlns$ = NothingOptional forceLargeMode As Boolean = FalseAs IEnumerable(Of T)
178             Dim nodeName$ = GetType(T).GetTypeName([default]:=typeName)
179             Dim source As IEnumerable(Of String)
180
181             If forceLargeMode OrElse XML.FileLength > 1024 * 1024 * 128 Then
182                 ' 这是一个超大的XML文档
183                 source = NodeIterator.IterateArrayNodes(XML, nodeName)
184                 xmlns = Nothing
185             Else
186                 source = InternalIterates(XML, nodeName)
187             End If
188
189             Return source.NodeInstanceBuilder(Of T)(xmlns, xmlNode:=nodeName)
190         End Function
191
192         ''' <summary>
193         ''' 
194         ''' </summary>
195         ''' <typeparam name="T"></typeparam>
196         ''' <param name="nodes"></param>
197         ''' <param name="replaceXmlns$"></param>
198         ''' <param name="xmlNode$">文件之中的节点名称</param>
199         ''' <returns></returns>
200         <Extension>
201         Private Iterator Function NodeInstanceBuilder(Of T As Class)(nodes As IEnumerable(Of String), replaceXmlns$, xmlNode$) As IEnumerable(Of T)
202             Dim o As T
203             Dim sb As New StringBuilder
204             Dim TnodeName$ = GetType(T).GetNodeNameDefine
205             Dim process As Func(Of StringString)
206
207             ' 2017-12-22
208             ' 假若对象是存储在一个数组之中的,那么,可能会出现的一种情况就是
209             ' 在类型的定义之中,使用了xmlelement重新定义了节点的名字
210             ' 例如 <XmlElement("A")>
211             ' 那么在生成的XML文件之中的节点名称就是A
212             ' 但是元素A的类型定义却是 Public Class B ... End Class
213             ' 因为A不等于B,所以将无法正确的加载XML节点数据
214             ' 在这里进行名称的替换来消除这种错误
215             If TnodeName = xmlNode Then
216                 ' 不需要进行替换
217                 process = Function(s) s
218             Else
219                 Dim leftTag% = 1 + xmlNode.Length
220                 Dim rightTag% = 3 + xmlNode.Length
221
222                 ' 在这里不尝试做直接替换,可能会误杀其他的节点
223                 process = Function(block)
224                               block = block.Trim(ASCII.CR, ASCII.LF, " "c, ASCII.TAB)
225                               block = block.Substring(leftTag, block.Length - leftTag)
226                               block = block.Substring(0, block.Length - rightTag)
227                               block = "<" & TnodeName & block & "</" & TnodeName & ">"
228
229                               Return block
230                           End Function
231             End If
232
233             For Each xml As String In nodes
234
235                 Call sb.Clear()
236                 Call sb.AppendLine("<?xml version=""1.0"" encoding=""utf-16""?>")
237                 Call sb.AppendLine(process(xml))
238
239                 If Not replaceXmlns.StringEmpty Then
240                     Call sb.Replace($"xmlns=""{replaceXmlns}""", "")
241                 End If
242
243                 xml = sb.ToString
244                 o = xml.LoadFromXml(Of T)
245
246                 Yield o
247             Next
248         End Function
249
250         ''' <summary>
251         ''' Apply on a ultra large size XML database, which its data size is greater than 1GB to 100GB or even more.
252         ''' </summary>
253         ''' <typeparam name="T"></typeparam>
254         ''' <param name="path$"></param>
255         ''' <param name="typeName$"></param>
256         ''' <param name="xmlns$"></param>
257         ''' <returns></returns>
258         <Extension>
259         Public Function LoadUltraLargeXMLDataSet(Of T As Class)(path$, Optional typeName$ = NothingOptional xmlns$ = NothingAs IEnumerable(Of T)
260             Dim nodeName$ = GetType(T).GetTypeName([default]:=typeName)
261             Return nodeName _
262                 .UltraLargeXmlNodesIterator(path) _
263                 .NodeInstanceBuilder(Of T)(xmlns, xmlNode:=nodeName)
264         End Function
265
266         <Extension>
267         Private Iterator Function UltraLargeXmlNodesIterator(nodeName$, path$) As IEnumerable(Of String)
268             Dim el As New Value(Of XElement)
269             Dim XML$
270
271             Using reader As XmlReader = XmlReader.Create(path)
272
273                 reader.MoveToContent()
274
275                 Do While (reader.Read()) ' Parse the file And return each of the child_node
276                     If (reader.NodeType = XmlNodeType.Element AndAlso reader.Name = nodeName) Then
277                         If (Not (el = XNode.ReadFrom(reader)) Is NothingThen
278                             XML = el.Value.ToString
279                             Yield XML
280                         End If
281                     End If
282                 Loop
283             End Using
284         End Function
285     End Module
286 End Namespace