14. august 2023 - 12:56
Der er
32 kommentarer
xml parsing driller - subtree-tag "forsvinder"!?
Hejsa Jeg sidder og parser en kompliceret xml fil, og støder på det underlige problem, at den nægter at læse subtree-tag'et (her, case "Company":). Alle de almindelige tags læses fint. Hvordan kommer man uden om det problem? Min kode (køres i .net 6): public Header ParseHeader(XmlReader reader) { Header header = new Header(); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { string elementName = reader.Name; // Get the fully qualified name, including namespace switch (reader.LocalName) { case "AuditFileVersion": header.AuditFileVersion = reader.ReadElementContentAsString(); break; case "AuditFileCountry": header.AuditFileCountry = reader.ReadElementContentAsString(); break; case "AuditFileRegion": header.AuditFileRegion = reader.ReadElementContentAsString(); break; case "AuditFileDateCreated": header.AuditFileDateCreated = reader.ReadElementContentAsString(); break; case "SoftwareCompanyName": header.SoftwareCompanyName = reader.ReadElementContentAsString(); break; case "SoftwareID": header.SoftwareID = reader.ReadElementContentAsString(); break; case "SoftwareVersion": header.SoftwareVersion = reader.ReadElementContentAsString(); break; case "Company": header.Company = ParseCompany(reader.ReadSubtree()); break; case "DefaultCurrencyCode": header.DefaultCurrencyCode = reader.ReadElementContentAsString(); break; case "SelectionCriteria": header.SelectionCriteria = ParseSelectionCriteria(reader.ReadSubtree()); break; case "TaxAccountingBasis": header.TaxAccountingBasis = reader.ReadElementContentAsString(); break; case "TaxEntity": header.TaxEntity = reader.ReadElementContentAsString(); break; case "UserID": header.UserID = reader.ReadElementContentAsString(); break; } } } return header; } XML fil (starten af den): <n1:Header> <n1:AuditFileVersion>1.0</n1:AuditFileVersion> <n1:AuditFileCountry>DK</n1:AuditFileCountry> <n1:AuditFileRegion>DK-81</n1:AuditFileRegion> <n1:AuditFileDateCreated>2022-10-03</n1:AuditFileDateCreated> <n1:SoftwareCompanyName>MitRegnskabsSystem ApS</n1:SoftwareCompanyName> <n1:SoftwareID>MitRegnskabsSystem</n1:SoftwareID> <n1:SoftwareVersion>2.22.2</n1:SoftwareVersion> <n1:Company> <n1:RegistrationNumber>12345678</n1:RegistrationNumber> <n1:Name>Selskabet ApS</n1:Name> ... </n1:Company> ... </n1:Header>
Annonceindlæg fra Novataris
Vejen til devops med Bavarian Nordic
Bavarian Nordics vækst blev starten på et DevOps-samarbejde med Novataris for hurtigt at kunne tilpasse IT-organisation til forretningen.
7. december 2023
14. august 2023 - 14:52
#1
Inden vi bruger for meget tid på at finde ud af hvad der sker så er spørgsmål: hvor stor er den XML fil? Hvis den er mindre end 100 MB vil jeg foreslå at droppe XmlReader og skifte til XmlDocument.
14. august 2023 - 14:54
#2
Dette er en lille test fil. Men de rigtige vil være i GB størrelsen.
14. august 2023 - 15:06
#3
OK. XmlReader. Som tommelfingerregel bruger et XmlDocument 3-5 gange så meget memory som disk filen. Så en 5 GB fil kræver 15-25 GB RAM og det er lidt heftigt. Jeg prøver om jeg kan få eksemplet parset.
Synes godt om
1 synes godt om dette
14. august 2023 - 15:08
#4
Ja, det var også min konklusion. :) Awesome, tak! :)
14. august 2023 - 15:27
#5
<n1:Header xmlns:n1="
http://eksperten.dk/spm1042338"> <n1:AuditFileVersion>1.0</n1:AuditFileVersion>
<n1:AuditFileCountry>DK</n1:AuditFileCountry>
<n1:AuditFileRegion>DK-81</n1:AuditFileRegion>
<n1:AuditFileDateCreated>2022-10-03</n1:AuditFileDateCreated>
<n1:SoftwareCompanyName>MitRegnskabsSystem ApS</n1:SoftwareCompanyName>
<n1:SoftwareID>MitRegnskabsSystem</n1:SoftwareID>
<n1:SoftwareVersion>2.22.2</n1:SoftwareVersion>
<n1:Company>
<n1:RegistrationNumber>12345678</n1:RegistrationNumber>
<n1:Name>Selskabet ApS</n1:Name>
</n1:Company>
</n1:Header>
using System; using System.IO; using System.Xml; namespace BigXml { public class Program { public static void ParseCompany(XmlReader xr) { while(xr.Read()) { if(xr.NodeType == XmlNodeType.Element) { if(xr.LocalName == "Company") { // nothing } else { Console.WriteLine("ParseCompany : {0} = {1}", xr.Name, xr.ReadElementContentAsString()); } } } } public static void ParseHeader(XmlReader xr) { while(xr.Read()) { if(xr.NodeType == XmlNodeType.Element) { if(xr.LocalName == "Header") { // nothing } else if(xr.LocalName == "Company") { ParseCompany(xr.ReadSubtree()); } else { Console.WriteLine("ParseHeader : {0} = {1}", xr.Name, xr.ReadElementContentAsString()); } } } } public static void Main(string[] args) { using(StreamReader sr = new StreamReader(@"\Work\big.xml")) { XmlReader xr = new XmlTextReader(sr); ParseHeader(xr); } Console.ReadKey(); } } }
ParseHeader : n1:AuditFileVersion = 1.0 ParseHeader : n1:AuditFileCountry = DK ParseHeader : n1:AuditFileRegion = DK-81 ParseHeader : n1:AuditFileDateCreated = 2022-10-03 ParseHeader : n1:SoftwareCompanyName = MitRegnskabsSystem ApS ParseHeader : n1:SoftwareID = MitRegnskabsSystem ParseHeader : n1:SoftwareVersion = 2.22.2 ParseCompany : n1:RegistrationNumber = 12345678 ParseCompany : n1:Name = Selskabet ApS
Det eneste jeg skulle gøre var at skippe element med elementer - det med // nothing markerede.
14. august 2023 - 15:36
#6
Øhm, ikke forstået? Siger du, at det er et enten/eller issue?? Altså enten kan jeg hente data, eller også kan jeg detektere subtrees? Det lyder uhensigtsmæssigt, i og med der er MANGE subtrees, heraf en del med samme navne. :-/
14. august 2023 - 15:51
#7
Slet ikke.
Du kan sagtens bruge både .ReadElementContentAsString() og .ReadSubtree() - det gør min kode.
Men jeg kunne konstatere at det var meget vigtigt ikke at komme til at kalde .ReadElementContentAsString() på noget som indeholder sub elementer (et tree).
Det var det eneste jeg måtte sørge for.
Og derfor:
public static void ParseCompany(XmlReader xr) { while(xr.Read()) { if(xr.NodeType == XmlNodeType.Element) { if(xr.LocalName == "Company") { // nothing } else ... } } public static void ParseHeader(XmlReader xr) { while(xr.Read()) { if(xr.NodeType == XmlNodeType.Element) { if(xr.LocalName == "Header") { // nothing } else ... } } }
Synes godt om
1 synes godt om dette
14. august 2023 - 16:08
#8
Ah, nu forstår jeg! :D Det giver mening. Det gør mit resultat så bare ikke. :P Min kode vil stadig ikke gå ind i company funktionen. :-/ Gør det en forskel at ParseHeader ikke er rod parser funktionen? Her er den opdaterede funktion: public Header ParseHeader(XmlReader reader) { Header header = new Header(); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.LocalName == "Header") { // nothing } else if (reader.LocalName == "Company") { ParseCompany(reader.ReadSubtree()); } else if (reader.LocalName == "SelectionCriteria") { ParseSelectionCriteria(reader.ReadSubtree()); } else { switch (reader.LocalName) { case "AuditFileVersion": header.AuditFileVersion = reader.ReadElementContentAsString(); break; case "AuditFileCountry": header.AuditFileCountry = reader.ReadElementContentAsString(); break; case "AuditFileRegion": header.AuditFileRegion = reader.ReadElementContentAsString(); break; case "AuditFileDateCreated": header.AuditFileDateCreated = reader.ReadElementContentAsString(); break; case "SoftwareCompanyName": header.SoftwareCompanyName = reader.ReadElementContentAsString(); break; case "SoftwareID": header.SoftwareID = reader.ReadElementContentAsString(); break; case "SoftwareVersion": header.SoftwareVersion = reader.ReadElementContentAsString(); break; case "DefaultCurrencyCode": header.DefaultCurrencyCode = reader.ReadElementContentAsString(); break; case "TaxAccountingBasis": header.TaxAccountingBasis = reader.ReadElementContentAsString(); break; case "TaxEntity": header.TaxEntity = reader.ReadElementContentAsString(); break; case "UserID": header.UserID = reader.ReadElementContentAsString(); break; } //Console.WriteLine("ParseHeader : {0} = {1}", reader.Name, reader.ReadElementContentAsString()); } } } return header; }
14. august 2023 - 16:22
#9
Jeg tror at det er tid for at indsætte de sædvanelige 2 dusin debug statements. :-) else if (reader.LocalName == "Company") { Console.WriteLine("before ParseCompany); ParseCompany(reader.ReadSubtree()); Console.WriteLine("after ParseCompany); } og public static void ParseCompany(XmlReader reader) { while(reader.Read()) { Console.WriteLine("{0} {1}", reader.NodeType, reader.Name for at se hvad pokker der sker. (man kunne naturligvis også bruge debugger, men jeg er gammeldags)
14. august 2023 - 16:30
#10
Hehe, vi bruger db logger funktion. :P Det er sorteret fra, så chefen ikke bliver hys. XD Rest asured, der ER logning. Både i ParseHeader og ParseCompany - og det er kun ParseHeader der kommer ud.
14. august 2023 - 16:47
#11
Interessant. Nu prøvede jeg at bruge dine funktioner, og fik denne fejl fra "rodparser" funktionen: ReadElementContentAs() methods cannot be called on an element that has child elements. Den kommer "sjovt nok" lige efter "n1:RegistrationNumber " Er vi montros ude i et async issue? Vi streamer filen fra en server...
14. august 2023 - 16:52
#12
Ja. Men hvad sker der? Bliver ParseCompany kaldt? Hvis ja: * hvad sker der i den? Læser den noget? Hvis nej: * bliver noget efter company processet? Der må jo være en eller anden forklaring. Er der en catch som sluger exceptions uden at logge? Er XML valid? Matcher stavning af "Company" i XML og C#?
14. august 2023 - 17:05
#13
Ok. ParseCompany bliver IKKE kaldt. Ja, når jeg bruger min kode fortsætter i header funktionen. Nej, når jeg bruger din kode, stopper den ved Company Nej, alle exptions jeg laver, logges. Er XML valid? Jeg må indrømme, at jeg ikke har tjekket den, men siden den kommer fra SKAT, så... Matcher stavning af "Company" i XML og C#? Yup.
14. august 2023 - 17:08
#14
...nu har jeg så lige tjekket filen på xmlvalidation.com - ingen fejl.
14. august 2023 - 17:08
#15
re #11) Så ParseCompany læser n1:RegistrationNumber men ikke n1:Namen for: <n1:Company> <n1:RegistrationNumber>12345678</n1:RegistrationNumber> <n1:Name>Selskabet ApS</n1:Name> </n1:Company> ? Mystisk. Streaming burde ikke gøre en forskel. XML parser bør vente indtil input er klart. Men et eller andet mystisk foregår der.
14. august 2023 - 17:12
#16
Men måske skulle du bruge en "sladre stream". En LogStreamReader der: * arver fra passende interface/klasse * har et member der er den rigtige StreamReader * har metoder som læser fra den rigtige StreamReader og logger det læste Så kan man se hvad der parses på.
14. august 2023 - 17:12
#17
Nej, ParseHeader læser n1:RegistrationNumber. ParseCompany gør intet.
14. august 2023 - 17:14
#18
Se det er interessant!! n1:RegistrationNumber er inden i n1:Company ikke? så den if der skal kalde ParseCompany bliver ikke aktiveret og derfor er det ParseHeader som processer n1:RegistrationNumber og ikke ParseHeader.
Synes godt om
1 synes godt om dette
14. august 2023 - 17:16
#19
Netop, nu er du med! :D
14. august 2023 - 17:20
#20
Er "Company" stavet ens i kode og XML? Med samme kapitalisering?
14. august 2023 - 17:24
#21
Yup.
14. august 2023 - 17:30
#22
Problemet er det samme med alle subtrees
14. august 2023 - 17:37
#23
Nu skete der noget!! Jeg fjernede XmlReaderSettings settings = new XmlReaderSettings { IgnoreWhitespace = true }; fra stream kaldet
14. august 2023 - 17:38
#24
Nu ser loggen således ud: XMLParsing.ParseXML: Getting stream XMLParsing.ParseXML: Reading stream Start XMLParsing.ParseHeader XMLParsing.ParseHeader: n1:AuditFileVersion = 1.0 XMLParsing.ParseHeader: n1:AuditFileCountry = DK XMLParsing.ParseHeader: n1:AuditFileRegion = DK-81 XMLParsing.ParseHeader: n1:AuditFileDateCreated = 2022-10-03 XMLParsing.ParseHeader: n1:SoftwareCompanyName = MitRegnskabsSystem ApS XMLParsing.ParseHeader: n1:SoftwareID = MitRegnskabsSystem XMLParsing.ParseHeader: n1:SoftwareVersion = 2.22.2 XMLParsing.ParseHeader: before ParseCompany Start XMLParsing.ParseCompany XMLParsing.ParseCompany: n1:RegistrationNumber = 12345678 XMLParsing.ParseCompany: n1:Name = Selskabet ApS XMLParsing.ParseXML: XML error: 'Element' is an invalid XmlNodeType.
14. august 2023 - 17:42
#25
Kører jeg "hybrid versionen" hvor jeg har brugt din kode i mine funktioner, ser loggen således ud: XMLParsing.ParseXML: Getting stream XMLParsing.ParseXML: Reading stream Start XMLParsing.ParseHeader XMLParsing.ParseHeader: reader.LocalName == Header XMLParsing.ParseHeader: reader.LocalName == AuditFileVersion XMLParsing.ParseHeader: reader.LocalName == AuditFileCountry XMLParsing.ParseHeader: reader.LocalName == AuditFileRegion XMLParsing.ParseHeader: reader.LocalName == AuditFileDateCreated XMLParsing.ParseHeader: reader.LocalName == SoftwareCompanyName XMLParsing.ParseHeader: reader.LocalName == SoftwareID XMLParsing.ParseHeader: reader.LocalName == SoftwareVersion XMLParsing.ParseHeader: reader.LocalName == Company Start XMLParsing.ParseCompany XMLParsing.ParseXML: Unknown error: The ReadElementContentAsString method is not supported on node type Whitespace. Line 14, position 15.
14. august 2023 - 18:08
#26
OK. Det sidste skylde vel at med: IgnoreWhitespace = true er: <a> <b>xxx</b> </a> a element med nested b element med nested text xxx med: IgnoreWhitespace = false er det a element med: * text element med "\n " * b element med ... * text element "\n"
14. august 2023 - 18:11
#27
IgnoreWhitespace = true virker som det rigtige, men spørgsmålet er hvorfor den if ikke trigges når den option er sat. Jeg forstår det ikke.
14. august 2023 - 18:45
#28
Med IgnoreWhitespace = true hvad indeholder reader.Name og reader.LocalName ved Company element? Evt. reader.Name.Replace(' ', '_') og reader.LocalName.Replace(' ', '_') for at få mellemrum synlige.
15. august 2023 - 13:14
#29
Ok, nu har jeg eksperimenteret lidt, og nu kan jeg godt løbe funktionerne i gennem, yay. :) Jeg blev dog nødt til at fjerne de der settings med IgnoreWhitespace = true helt og holdent. Data bliver også gemt fint, så jeg vil egentligt erklære problemet løst. :) Sådan ser den færdige funktion ud: public Header ParseHeader(XmlReader reader) { Header header = new Header(); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { string elmLocalName = reader.LocalName; if (elmLocalName == "Header") { } else if (elmLocalName == "Company") { header.Company = ParseCompany(reader.ReadSubtree()); } else if (elmLocalName == "SelectionCriteria") { header.SelectionCriteria = ParseSelectionCriteria(reader.ReadSubtree()); } else { switch (elmLocalName) { case "AuditFileVersion": header.AuditFileVersion = reader.ReadElementContentAsString(); break; case "AuditFileCountry": header.AuditFileCountry = reader.ReadElementContentAsString(); break; case "AuditFileRegion": header.AuditFileRegion = reader.ReadElementContentAsString(); break; case "AuditFileDateCreated": header.AuditFileDateCreated = reader.ReadElementContentAsString(); break; case "SoftwareCompanyName": header.SoftwareCompanyName = reader.ReadElementContentAsString(); break; case "SoftwareID": header.SoftwareID = reader.ReadElementContentAsString(); break; case "SoftwareVersion": header.SoftwareVersion = reader.ReadElementContentAsString(); break; case "DefaultCurrencyCode": header.DefaultCurrencyCode = reader.ReadElementContentAsString(); break; case "TaxAccountingBasis": header.TaxAccountingBasis = reader.ReadElementContentAsString(); break; case "TaxEntity": header.TaxEntity = reader.ReadElementContentAsString(); break; case "UserID": header.UserID = reader.ReadElementContentAsString(); break; } } } } return header; }
16. august 2023 - 01:36
#30
Godt at det virker. Men jeg undrer mig stadig over hvad problemet var med IgnoreWhitespace = true.
Synes godt om
1 synes godt om dette
16. august 2023 - 09:13
#31
Jaw jaw. :) Jamen jeg er helt enig. :D Jeg prøvede dit replace forslag og med/uden IgnoreWhitespace = true. Den eneste forskel jeg så, var om den så Company eller ej. Ingen underscores blev sat nogen steder... Wierd.
16. august 2023 - 17:19
#32
crazy weird
Synes godt om
1 synes godt om dette
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.