Przetwarzanie pliku SIMC.xml
Plik SIMC.xml zawiera zbiór identyfikatorów i nazw miejscowości. Plik ten, został pobrany do bieżącego katalogu bazy danych, do folderu o nazwie TERYT. Na stronie Opis struktury zbioru SIMC możemy zapoznać się ze strukturą tego pliku.
- Opis struktury zbioru SIMC
- WOJ - symbol województwa - 2 zn. C
- POW - symbol powiatu - 2 zn. C
- GMI - symbol gminy - 2 zn. C
- RODZ_GMI (*) - symbol rodzaju jednostki - 1 zn. C
cyfra określa symbol rodzaju jednostki i oznacza:- 1 - gmina miejska,
- 2 - gmina wiejska,
- 3 - gmina miejsko-wiejska,
- 4 - miasto w gminie miejsko-wiejskiej,
- 5 - obszar wiejski w gminie miejsko-wiejskiej,
- 8 - dzielnica w m.st. Warszawa,
- 9 - delegatury miast: Kraków, Łódź, Poznań i Wrocław
- RM - rodzaj miejscowości - 2 zn. C
obecnie funkcjonuje 12 określeń rodzajowych, którym nadano dwucyfrowe symbole:- 00 - część miejscowości
- 01 - wieś
- 02 - kolonia
- 03 - przysiółek
- 04 - osada
- 05 - osada leśna
- 06 - osiedle
- 07 - schronisko turystyczne
- 95 - dzielnica m. st. Warszawy
- 96 - miasto
- 98 - delegatura
- 99 - część miasta
- MZ - występowanie nazwy zwyczajowej (0-tak,1-nie) - 1 zn. C
- NAZWA - nazwa miejscowości -
56100 zn. C - SYM - identyfikator miejscowości - 7 zn. C
-
Każda miejscowość wiejska tzn. każda wieś, kolonia, osada, przysiółek, itp. oraz integralna
część miejscowości wiejskiej, a także każde miasto i część miasta otrzymały niepowtarzalny
siedmiocyfrowy identyfikator.
Identyfikator miejscowości jest stały. Miejscowość zachowuje swój raz nadany identyfikator nawet wtedy, gdy w wyniku zmian administracyjnych zmieniła swą przynależność do gminy czy powiatu oraz gdy zmieniła swój charakter, np. nadano jej status miasta. Zmiana nazwy i rodzaju również nie wpływa na identyfikator. Identyfikator ten posiada wyłącznie identyfikujący charakter i nie spełnia funkcji informacyjnych
-
Każda miejscowość wiejska tzn. każda wieś, kolonia, osada, przysiółek, itp. oraz integralna
część miejscowości wiejskiej, a także każde miasto i część miasta otrzymały niepowtarzalny
siedmiocyfrowy identyfikator.
- SYMPOD - identyfikator miejscowości podstawowej - 7 zn. C
- - dla części miejscowości wiejskich - identyfikator miejscowości, do której dana część należy,
- - dla części miast - identyfikator danego miasta (w miastach posiadających dzielnice/delegatury - identyfikator tej jednostki).
-
W system identyfikatorów i nazw miejscowości (SIMC) relacja identyfikatora miejscowości SYM
i identyfikatora miejscowości podstawowej SYMPOD określa, czy miejscowości jest samodzielna, czy niesamodzielna,
- SYM = SYMPOD - miejscowość samodzielna
- SYM ≠ SYMPOD - miejscowość niesamodzielna
- STAN_NA - data aktualizacji danych w systemie TERC w formacie RRRR-MM-DD - 10 zn. C
(*) - RODZ_GMI - w opisie struktury zbioru TERC element nazwany jest: RODZ.
Struktura pliku SIMC.xml
Ponieważ struktura pliku SIMC.xml została zmieniona, funkcja przetwarzający plik SIMC.xml
musiała zostać dostosowana do obowiązującej struktury pliku (nazw zmienionych elementów) przetwarzanego pliku SIMC.xml.
Jeżeli chodzi o tabele to zmiana dotyczy elementu [NAZWA] określającego nazwę miejscowości
dla którego zwiększono ilość znaków z 56 do 100 znaków.
W tabeli tblSIMC_Miejscowosci należy zmienić rozmiar pola [tNazwa]
z 56 znaków na 100 znaków.
Plik SIMC.xml zawiera 102 940 elementów <row>, a każdy z nich zawiera 10 elementów <col>,
co daje 1 029 400 pojedynczych elementów. Każdy element <col> zawiera dane (nie jest pusty).
Poniżej struktura starego pliku SIMC.xml z dnia 2015-01-01 i struktura aktualnego pliku SIMC.xml
dla dwóch pierwszych elementów <row> zbioru SIMC.xml.
<?xml version="1.0" encoding="UTF-8"?> <teryt> <catalog name="SIMC" type="all" date="2015-01-01"> <row> <col name="WOJ">18</col> <col name="POW">16</col> <col name="GMI">13</col> <col name="RODZ_GMI">2</col> <col name="RM">00</col> <col name="MZ">1</col> <col name="NAZWA">Na Polu</col> <col name="SYM">0664326</col> <col name="SYMPOD">0664310</col> <col name="STAN_NA">2015-01-01</col> </row> <row> <col name="WOJ">28</col> <col name="POW">06</col> <col name="GMI">05</col> <col name="RODZ_GMI">2</col> <col name="RM">03</col> <col name="MZ">1</col> <col name="NAZWA">Majerka</col> <col name="SYM">0761615</col> <col name="SYMPOD">0761609</col> <col name="STAN_NA">2015-01-01</col> </row> </catalog> </teryt>
<?xml version="1.0" encoding="utf-8"?> <SIMC> <catalog name="SIMC" type="ALL" date="2018-01-02"> <row> <WOJ>18</WOJ> <POW>16</POW> <GMI>13</GMI> <RODZ_GMI>2</RODZ_GMI> <RM>00</RM> <MZ>1</MZ> <NAZWA>Na Polu</NAZWA> <SYM>0664326</SYM> <SYMPOD>0664310</SYMPOD> <STAN_NA>2018-01-02</STAN_NA> </row> <row> <WOJ>28</WOJ> <POW>06</POW> <GMI>05</GMI> <RODZ_GMI>2</RODZ_GMI> <RM>03</RM> <MZ>1</MZ> <NAZWA>Majerka</NAZWA> <SYM>0761615</SYM> <SYMPOD>0761609</SYMPOD> <STAN_NA>2018-01-02</STAN_NA> </row> </catalog> </SIMC>
Struktura tabeli tblSIMC_Miejscowosci.
Znając strukturę zbioru SIMC musimy utworzyć tabelę na przyjęcie danych z pliku SIMC.xml. Struktura tabeli przedstawiona jest poniżej.
| Tabela tblSIMC_Miejscowosci - struktura | |||||
| Nazwa pola*) | Typ | Rozmiar**) | Wymagane | Zerowa długość | Uwagi |
| ID_Sym | Tekst | 7 | Tak | Nie | PrimaryKey |
| Id_Gmi | Tekst | 7 | Tak | Nie | Indeks. Duplikaty (OK) |
| Id_RM | Tekst | 2 | Tak | Nie | Indeks. Duplikaty (OK) |
| tMZ | Tekst | 1 | Tak | Nie | |
| tNazwa | Tekst | Tak | Nie | Indeks. Duplikaty (OK) | |
| tSymPod | Tekst | 7 | Tak | Nie | |
| tStan_Na | Tekst | 10 | Tak | Nie | |
| ID_Gmi = jest połączeniem elementów: <WOJ> & <POW> & <GMI> & <RODZ_GMI> ze zbioru SIMC | |||||
| Uwagi dodatkowe. Dotyczą wszystkich tabel na tej stronie. |
|
*) - Dla pola będącego kluczem głównym, stosuję prefiks „ID_”, - Klucz obcy zawsze poprzedzam prefiksem „Id_” - Nazwy pól nie będące kluczem głównym i kluczem obcym poprzedzam prefiksem „t” |
| **) Minimalna długość pola. Można użyć większego rozmiaru pola. MS Access nie dopełnia pól tekstowych do zadeklarowanej długości. |
Tworzenie tabeli tblSIMC_Miejscowosci.
Tabelę tblSIMC_Miejscowosci możemy utworzyć korzystając z metod klasy clsTeryt ze strony Klasa clsTeryt
Dim clsSIMC As clsTeryt
Set clsSIMC = New clsTeryt
With clsSIMC
.terytDeleteTable "tblSIMC_Miejscowosci"
.terytCreateTable "tblSIMC_Miejscowosci"
.terytCreateField "ID_Sym", dbText, 7
.terytCreateField "Id_Gmi", dbText, 7
.terytCreateField "Id_RM", dbText, 2
.terytCreateField "tMZ", dbText, 1
.terytCreateField "tNazwa", dbText, 100
.terytCreateField "tSymPod", dbText, 7
.terytCreateField "tStan_Na", dbText, 10
.terytAppendTable
' utwórz Indeks Primary
.terytCreateIndex "ID_Sym", True
' utwórz Indeksy (duplikaty OK)
.terytCreateIndex "Id_Gmi"
.terytCreateIndex "Id_RM"
.terytCreateIndex "tNazwa"
End With
Set clsSIMC = Nothing
Skoro mamy utworzoną nową tabelę, to możemy pobrać dane z pliku SIMC.xml i zapisać je w tabeli tblSIMC_Miejscowosci. Jak to zrobić ? Po prostu skorzystać z gotowej funkcji fXmlSimcToTable_DOM(...) zamieszczonej poniżej.
Funkcja wczytująca dane z pliku SIMC.xml do tabeli MS Access
• Stan na dzień 03 marca 2018 roku
Public Function fXmlSimcToTable_DOM( _
ByVal sFileXmlPath As String, _
ByVal sTblSIMC As String) As Boolean
#If pbl_fEarly = True Then
Dim xmlDoc As MSXML2.DOMDocument60
Dim oNode As MSXML2.IXMLDOMNode
Dim oChildNode As MSXML2.IXMLDOMNode
#Else
Dim xmlDoc As Object
Dim oNode As Object
Dim oChildNode As Object
#End If
Dim colNodesValue As Collection
Dim sXPath As String
Dim dbs As DAO.Database
Dim rstSimc As DAO.Recordset
Const cParser_XML As String = "MSXML2.DOMDocument"
Const cModule_Name As String = "fXmlSimcToTable_DOM (...)"
On Error GoTo Err_Handler
' Utwórz obiekt analizatora składni XML (parsera XML)
#If pbl_fEarly = True Then
' Wczesne wiązanie (early binding)
Set xmlDoc = New MSXML2.DOMDocument60
#Else
' Późne wiązanie (late binding)
Set xmlDoc = CreateObject("MSXML2.DOMDocument")
#End If
With xmlDoc
.Load (sFileXmlPath)
' sprawdź poprawność wczytanego XML'a
If (.parseError.errorCode <> 0) Then
Err.Raise .parseError.errorCode, cParser_XML, .parseError.reason
End If
' utwórz adres do elementów <row>
sXPath = "/teryt/catalog/row"
' utwórz zmienną obiektową odwołującą się do pojedynczego elementu <row>
Set oNode = .selectSingleNode(sXPath)
Set dbs = CurrentDb
Set rstSimc = dbs.OpenRecordset(sTblSIMC, dbOpenDynaset, dbAppendOnly)
Do Until oNode Is Nothing
' utwórz tymczasową kolekcję colNodesValue
Set colNodesValue = New Collection
' przejdź po elementach <row> i zapisz wartości poszczególnych elementów do tabeli
For Each oChildNode In oNode.childNodes
' i wypełnij tymczasową kolekcję colNodesValue danymi:
colNodesValue.Add oChildNode.Text, oChildNode.Attributes(0).Text
Next
rstSimc.AddNew
With colNodesValue
rstSimc!ID_Gmi = .Item("WOJ") & .Item("POW") & .Item("GMI") & .Item("RODZ_GMI")
rstSimc!Id_RM = .Item("RM")
rstSimc!tMZ = .Item("MZ")
rstSimc!tNazwa = .Item("NAZWA")
rstSimc!ID_Sym = .Item("SYM")
rstSimc!tSymPod = .Item("SYMPOD")
rstSimc!tStan_Na = .Item("STAN_NA")
End With
rstSimc.Update
' utwórz zmienną obiektową odwołującą się do natępnego elementu <row>
Set oNode = oNode.nextSibling
Loop
End With
fXmlSimcToTable_DOM = True
Exit_Here:
' zniszcz zmienne obiektowe
If Not (rstSimc Is Nothing) Then rstSimc.Close
Set rstSimc = Nothing
Set dbs = Nothing
Set colNodesValue = Nothing
Set oNode = Nothing
Set oChildNode = Nothing
Set xmlDoc = Nothing
Exit Function
Err_Handler:
MsgBox "Błąd nr " & Err.Number & vbNewLine & _
Err.Description & vbNewLine & _
"Źródło: " & Err.Source & vbNewLine & _
"Moduł: " & cModule_Name
Resume Exit_Here
End Function
Czas przetwarzania przez funkcję fXmlSIMCToTable_DOM (...) pliku SIMC.xml, wielkości 28 MB, zawierającego 103 940 elementów <row> w sumie ponad 1 000 000 elementarnych danych wynosi:
- ok. 2260 sekund dla „wczesnego wiązania” (ok. 40 minut)
- ok. 3900 sekund dla „późnego wiązania” (ok. 65 minut)
Testowałem zapis pierwszych 10 000 rekordów do tabeli MS Access i wynik pomnożyłem przez 10.
Co prawda, plik SIMC.xml wczytujemy jednokrotnie, ale czekać na przetworzenie pliku
ok. 1 godziny to trochę za dużo.
Chcąc się upewnić, że w miarę poprawnie napisałem funkcję fXmlSIMCToTable_DOM (...),
znalazłem na stronie Dariusza Żelazko
Import pliku xml do bazy MyAQL za pomocą metody LOAD XML,
że czas przetwarzania pliku ULIC.xml o wielkości 80 MB trwał ok. 6 godzin.

Nie jest tak źle. U mnie 100 000 rekordów w 1 godzinę, więc przetworzenie pliku ULIC.xml o podobnej strukturze,
zawierający ok. 270 000 rekordów zajmie ok. 2 do 3 godzin.
Wydaje mi się, że wniosek może być tylko jeden. Sposób przetwarzania dużych plików *.xml z wykorzystaniem
biblioteki Microsoft XML,v6.0 nie zdaje egzaminu i należy z niej zrezygnować
na rzecz czegoś innego☺.
Jeżeli zrezygnujemy całkowicie z obiektu "MSXML2.DOMDocument", zyskamy ok. 8 sekund na wczytanie pliku, co nie rekompensuje wygody jaką daje metodą Load obiektu "MSXML2.DOMDocument", m.in. konwersja zawartości pliku na aktualną stronę kodową oraz obsługa ewentualnych błędów (nieprawidłowa ścieżka do pliku, uprawnienia do pliku, nieprawidłowa struktury pliku XML itp.).
Wydaje mi się, że wniosek może być tylko jeden. Sposób przetwarzania dużych plików *.xml z wykorzystaniem biblioteki Microsoft XML,v6.0 nie zdaje egzaminu i należy ograniczyć jej zastosowanie wyłącznie do wczytania pliku *.xml, a przetwarzanie wczytanego tekstu zrobić za pomocą VBA☺.
• VBA. Nowa funkcja wczytująca plik SIMC.xml do tabeli MS Access
By sobie ograniczyć kłopoty z dostępem do biblioteki obiektów Microsoft XML,v6.0, skorzystam z „późnego wiązania”. Po wczytaniu plikuSIMC.xml metodą Load, źródłowy .xml rozdzielimy na poszczególne linie, względem znaku końca linii za pomocą funkcji Split
Split(expression[, delimiter[, limit[, compare]]])
zwracającej indeksowaną od zera jednowymiarową tablicę, zawierającą podciągi, które były rozdzielone, w wejściowym ciągu znaków expression, separatorem delimiter .
' Utwórz obiekt analizatora składni XML (parsera XML)
Set xmlDoc = CreateObject("MSXML2.DOMDocument")
With xmlDoc
.Load (sFileXmlPath)
' sprawdź poprawność wczytanego XML'a
If (.parseError.errorCode <> 0) Then
Err.Raise .parseError.errorCode, cParser_XML, .parseError.reason
End If
' rozdziel wczytany tekst .xml na elementy (linie) względem znaku końca linii
aLines() = Split(.xml, vbNewLine, , vbBinaryCompare)
End With
' obiekt "MSXML2.DOMDocument" jest już nam niepotrzebny
Set xmlDoc = Nothing
i dalej przetwarzać elementy tablicy aLines() metodami VBA, za pomocą dodatkowej funkcji własnej Function getTagValue(...), korzystającej z dwóch funkcji VBA:
InStr([start, ]string1, string2[, compare])
- zwracającej pozycję pierwszego wystąpienia szukanego ciągu string2 w ciągu wejściowym string1
Mid$(string, start[, length])
- zwracającą określoną liczbę znaków (length) z ciągu wejściowego string począwszy od pozycji start
Czas najwyższy pokazać funkcję zapisującą dane z pliku SIMC.xml do tabeli MS Access metodą mieszaną:
• „późne wiązanie” obiektu "MSXML2.DOMDocument" i wczytanie pliku
• przetwarzanie wczytanego tekstu za pomocą funkcji VBA i zapis danych do tabeli.
Public Function fXmlSimcToTable_VBA( _
ByVal sFileXmlPath As String, _
ByVal sTblSIMC As String) As Boolean
Dim xmlDoc As Object ' MSXML2.DOMDocument60
Dim dbs As DAO.Database
Dim rstSimc As DAO.Recordset
Dim aLines() As String
Dim i As Long
Const cParser_XML As String = "MSXML2.DOMDocument"
Const cModule_Name As String = "Function fXmlSimcToTable_VBA (...)"
On Error GoTo Err_Handler
' Utwórz obiekt analizatora składni XML (parsera XML)
Set xmlDoc = CreateObject("MSXML2.DOMDocument")
With xmlDoc
.Load (sFileXmlPath)
' sprawdź poprawność wczytanego XML'a
If (.parseError.errorCode <> 0) Then
Err.Raise .parseError.errorCode, cParser_XML, .parseError.reason
End If
' rozdziel wczytany tekst .xml na elementy (linie) względem znaku końca linii
aLines = Split(.xml, vbNewLine, , vbBinaryCompare)
End With
' obiekt "MSXML2.DOMDocument" jest już nam niepotrzebny
Set xmlDoc = Nothing
Set dbs = CurrentDb
Set rstSimc = dbs.OpenRecordset(sTblSIMC, dbOpenDynaset, dbAppendOnly)
' nie przetwarzaj trzech pierwszych i dwóch ostatnich linii
For i = LBound(aLines) + 3 To UBound(aLines) - 5 Step 12
With rstSimc
' zapisz poszczególne wartości elementów składowych <row> do tabeli
.AddNew
' ID_Gmi = WOJ & POW & GMI & RODZ_GMI
!ID_Gmi = getTagValue(aLines(i + 1)) & _
getTagValue(aLines(i + 2)) & _
getTagValue(aLines(i + 3)) & _
getTagValue(aLines(i + 4))
!ID_RM = getTagValue(aLines(i + 5))
!tMZ = getTagValue(aLines(i + 6))
!tNazwa = getTagValue(aLines(i + 7))
!Id_Sym = getTagValue(aLines(i + 8))
!tSymPod = getTagValue(aLines(i + 9))
!tStan_Na = getTagValue(aLines(i + 10))
.Update
End With
Next
fXmlSimcToTable_VBA = True
Exit_Here:
' zniszcz zmienne obiektowe
If Not (rstSimc Is Nothing) Then rstSimc.Close
Set rstSimc = Nothing
Set dbs = Nothing
Exit Function
Err_Handler:
MsgBox "Błąd nr " & Err.Number & vbNewLine & _
Err.Description & vbNewLine & _
"Źródło: " & Err.Source & vbNewLine & _
"Moduł: " & cModule_Name
Resume Exit_Here
End Function
Pomocnicza funkcja getTagValue (...) zwracająca wartości elementów pliku .xml
Private Function getTagValue( _
ByRef sLineText As String) As String
Dim lStart As Long
Dim lEnd As Long
Const cStartText As String = ">"
Const cEndText As String = "<"
Const cModule_Name As String = "Function getTagValue (...)"
Const ERR_NOT_NODE As Long = vbObjectError + 500
Const ERR_NOT_NODE_DSCR As String = "Nie można znaleźć węzła."
lStart = InStr(1, sLineText, cStartText, vbBinaryCompare)
If lStart = 0 Then
Err.Raise ERR_NOT_NODE, cModule_Name, ERR_NOT_NODE_DSCR
End If
lEnd = InStr(lStart + 1, sLineText, cEndText, vbBinaryCompare)
' Pusty element
If lEnd = 0 Then Exit Function
' pobierz wartość elementu
lStart = lStart + 1
getTagValue = Mid$(sLineText, lStart, lEnd - lStart)
End Function
Mogę powiedzieć jeszcze raz: Jak na razie, nie ma się chyba do czego przyczepić ☺.
Czas przetwarzania pliku SIMC.xml przez funkcję fXmlSimcToTable_VBA (...), wynosi
ok. 22 sekund, co w porównaniu do 40 minut, dla funkcji fXmlSimcToTable_DOM(...)
to przepaść.
Funkcja fXmlSimcToTable_DOM(...) korzystająca tylko i wyłącznie z parsera "MSXML2.DOMDocument"
jest ok. 100x wolniejsza.
- Całkowity czas przetwarzania pliku SIMC.xml wynosi ok. 22 sekundy
- Załadowanie i sprawdzenie pliku SIMC.xml przez parser "MSXML2.DOMDocument" wynosi ok. 4,7 sekundy
- Rozdzielenie tekstu .xml na poszczególne linie za pomocą funkcji Split(...) wynosi ok. 5,2 sekundy
- Przetworzenie i zapis danych do tabeli wynosi ok. 12 sekund
Ale może być znacznie gorzej podczas przetwarzania 80 MB pliku ULIC.xml, który zawiera 267 028 elementów <row>, a każdy z nich zawiera 10 elementów <col> co w\ sumie daje 2 567 028 pojedynczych elementów. O przetwarzaniu zbioru ULIC.xml i problemach z tym związanych będzie na stronie: Zbiór ULIC z rejestru TERYT
Wydaje mi się, że wniosek może być tylko jeden. Sposób przetwarzania dużych plików *.xml z wykorzystaniem
biblioteki Microsoft XML,v6.0 nie zdaje egzaminu i należy ograniczyć jej zastosowanie wyłącznie
do wczytania pliku *.xml, a przetwarzanie wczytanego tekstu zrobić za pomocą VBA