ארכיון תגיות: XML

דיסיריליזציה של XML מקוננים באמצעות XmlReader + XmlSerialization ב- C#

האתגר  – דיסיריליזציה מאוד מהירה של XML בכל גודל שהוא – בלי צריכת משאבים גבוהה.

כאשר עובדים עם קבצי XML גדולים , נרצה להפוך אותם לאובייקטים שלנו, שאיתם נוכל לעשות פעולות רגילות.

יחד עם זאת, המחלקה הרגילה לצורך כך, יכולה להתמודד עם קבצי XML בגודל קטן, אך בקבצי XML גדולים – היא צורכת משאבים רציניים (זיכרון וכו' )

בנוסף, קריאת XML עם מחלקת XmlReader היא הרבה יותר מהירה מאשר מחלקת XmlDocument

המטרה של הפוסט הזה היא לא להחליף את התיעוד הרשמי , אלא לרכז יחד כמה נקודות מעניינות שיכולות לחסוך זמן למי שעושה זאת בהמשך.

הפתרון – שימוש ב – XmlReader + XmlSerialization

נחלק את ההסבר ל-2 חלקים, חלק 1 יעסוק ב-Reader , וחלק שני בדיסיריליזציה.

חלק 1 – Xml.Reader

החלק של ה-Reader הולך כך : ניצור אוביקט Reader בתחילת הפעולה, ונשחרר את המשאבים בסוף הפעולה.

 /** Init XML reader */
 XmlReader reader = XmlReader.Create(fileName);


... Your code here
...
...

/** Close **/ 
 reader.Dispose();
  • הערה לגבי .net core : עדיף להשתמש במתודה Dispose ולא במתודה Close
    כי רק Dispose קיימת ב- .net core.

 

  • אם יש לנו בלוקים גדולים, אפשר ומומלץ ליצור אוביקט Reader נפרד עבורם – שמכיל רק את השורות של הבלוק הגדול שלנו, ולא מכיל את שאר השורות בקובץ

הנה דוגמא מלאה :

 // Position the reader on the second book node
 reader.ReadToFollowing("MyBigElement");
 
 // Create another reader that contains just the second book node.
 XmlReader readerMyBigElement = reader.ReadSubtree();

 // init new xmlSerlizer - To new instance of class MyWantedClass
 // Be Aware - MyWantedClass is the class that will get into 
 // the values from MyBigElement in the XML.
 XmlSerializer xmlSerlizer = new XmlSerializer(typeof(MyWantedClass));
 
 MyWantedClass myWantedClass;
 // Deserialize
 myWantedClass = (MyWantedClass)xmlSerlizer.Deserialize(readerMyBigElement);

 readerKoteretKovetz.Dispose();

הפתרון הזה מצריך יצירה מראש מחלקות עבור הבלוקים השונים ב-XML.

חלק 2 – דיסירליזציה

כאשר צריך להשתמש ב-Attributes  ( תיעוד על שימוש ב-attributes הספציפיים  – בקישור הזה ).

אני מתמצת כאן נקודות חשובות :

  • אם שם האלמנט בתוך ה-XML הוא בדיוק כמו שם המשתנה – אין צורך להשתמש ב-Attribute
  • בכל מקרה אחר – נשתמש ב-Attribute
  • דוגמא פשוטה ל- String
[XmlElement(ElementName = "My-Xml-Element-Name", IsNullable = true)]
public string myVariable;
  • משתנים במחלקה שלא קשורים לקובץ ה-XML, נסמן אותם ב- Attribute של XmlIgnore

 

  • טיפול ב-Null   :   בכל אלמנט שלא ידוע לנו אם הוא יגיע בוודאות, או שיגיע ללא ערך , או שיגיע null \ nil
    אז נוסיף את ההגדרה

    IsNullable = true
  • טיפול ב-Null עבור ערכים מספריים :  – אם המשתנה שצריך לקבל ערך הוא מסוג int \ decimal , אז עבור המקרים שבהם הערך יהיה ריק , צריך להגדיר אותם עם סימן שאלה בסוף, כדי שיוכלו לקבל ערך null
    לדוגמא
public int myVar
  • טיפול ב-Null עבור אובייקטים – לייצר את האובייקט מראש בזמן ההגדרה :
    MyObject myObject = new() myObject();
  • טיפול ב- Null עבור ערכי תאריך  – במקרה של תאריך, נצטרך לשים getter\setter מתאימים.
    בפירוט : נניח שהתאריך מגיע כ ddmmyyyy
    אז ניצור

    • משתנה מסוג string שמקבל את הערך המקורי
    • משתנה מסוג date עם getter + setter
    • ה-setter – בודק אם הערך המקורי הוא null ורק אם לא, אז הוא עושה לו Parse
    • ה-getter – מחזיר את התאריך.
[XmlIgnore]
 public DateTime dtRealDate { get; set; }

 
 [XmlElement(ElementName = "MyDateInXml", IsNullable = true)]
 public string MyDateBlaBla
 {
 get
 {
 return this.dtRealDate.ToString();
 }
 set
 {
 if (value != null)
 {
 this.dtRealDate = DateTime.ParseExact(value, "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture);
 }
 }
 }
  • קינון :
    • נניח שיש לנו כמה בלוקים של XML בתוך הקובץ, כל בלוק עוסק בישות אחרת.
    • אז כל בלוק – הוא מחלקה נפרדת.
    • זאת אומרת שבתוך המחלקה של הבלוק הראשי, יהיו משתנים מסוג אוביקט הבלוק הרלוונטי 
    • ואז – כדי להתמודד עם Null , כמו שכבר כתבתי למעלה , ניצור כבר new instance בזמן הגדרת המשתנה.
      כך שבכל מקרה שהבלוק הזה יגיע ריק או null, לא נקבל Exception
    • אפשר גם לקנן לתוך List !!! וזה עובד היטב.
 [XmlElement(ElementName = "ElementThatArriveMultipleTimes", IsNullable=true)]
 public List<Product>productsList = new List<Product>();

 

כלים נחמדים לטיפול ב-XML וליצירת המחלקות

 

  • מיקרוסופט מציעה למי שמוריד את Windows SDK , כלי קטן שנקרא xsd.exe
    ובמידה ויש לכם קובץ xsd שמתאר את ה-XML שלכם, אז הכלי הזה יכול לייצר אוטומטית את המחלקות.
    אני לא מצאתי שהוא שימושי, כי הוא מייצר הרבה קוד זבל, ואין לי שליטה על כל המאפיינים שלו, אבל אולי למישהו אחר זה יכול לעזור ( שוב…בהנחה שיש לכם קובץ XSD שמתאר את ה-XML).
  • כדי לקרוא XML גדולים מאוד בצורה נוחה
    לא צריך עורך טקסט או IDE \ דפדפן – שמן הסתם יקרסו באמצע.
    אפשר להשתמש בכלי XML Explorer  שעובד ממש יפה , וגם מאפשר שאילתות XPATH בקלות.
    זה כלי אחד לדוגמא, ובטוח יש עוד כאלו https://xmlexplorer.codeplex.com/ 

בהצלחה!

 

באגים ב-PHP בטיפול ב-XML באמצעות DomDocument \ SimpleXml

אני לא שותף למלחמות דת על "שפת התיכנות הטובה ביותר"

לרוב, מלחמות כאלו חסרות משמעות.

יחד עם זאת, הציפיה שלי משפת תיכנות, היא שהפקודות בה יעבדו כמו שצריך, בלי שאצטרך למצוא דרכים עוקפות כיוון שפקודה מסוימת לא עובדת בכל מצב.

כבר כמה שנים שאני מתעסק עם קבצי XML ענקים שמגיעים מגופים גדולים.

והסתבר לי עם הזמן שלספריות ש-PHP מציעה כדי לפענח XML, יש חסרונות עצומים.

  • אם הקובץ לא מגיע בקידוד המצופה ( למשל, הקובץ מוצהר כ-UTF-8 אבל בפועל הוא UTF-16LE )
    אז צריך לאתר זאת מראש, כי הפקודות שטוענות את הקובץ ומפרשות את ה-XML, לא יודעות לפענח את הקידוד השונה בעצמן.
  • שאילתות XPATH, לעיתים לא עובדות – מאוד תלוי "במצב הרוח" של הספריה.
    לדוגמא – DomDocument מסוגלת לבצע שאילתות XPATH בצורה טובה, ואז בלי סיבה מיוחדת – לא לעבוד.
  • לעיתים, ספריה אחרת יודעת לפרש את ה-XML, והספריה השניה לא מצליחה להתמודד איתו, ומחזירה false.

הפתרון שמצאתי בסופו של דבר קורא את הקובץ בספריה אחת ושומר אותו מחדש כדי שהספריה השניה תואיל בטובה לקרוא אותו בכל מצב.

זה אומנם עובד, אך גובה מחיר יקר בזמן עיבוד שבמקרה שלי הוא אקוטי כיוון שהלקוח יושב וממתין…

המצב הזה הוא ממש גרוע. ולצערי במקרה הזה – כל קובץ שהייתי צריך לבצע מניפולציות עליו ב-PHP, כאשר ניסיתי לפרש אותו עם VB , בספריה עתיקה ביותר של מיקרוסופט (MSXML 6 )  – זה הצליח תמיד , הרבה יותר מהיר, וללא שום בעיות.

במקרה הזה, יש לי רק מחמאות למיקרוסופט, הספריה העתיקה שלהם, עובדת מעולה.

מצבים כאלו, יוצרים תיסכול, כיוון שאילו זו הייתה ספריה אחת מתוך כמה – אז הייתי משתמש באחרות. אבל ב-PHP, נוצר מצב ש-2 הספריות העיקריות לא עושות את העבודה. ברוב הקבצים – ברור ששתיהן עובדות כמו שצריך.  אבל כשמגיעים קבצים עם קידוד בעייתי ועוד…כל ספריה מתנהגת בצורה בלתי צפויה בפקודות שלה.

מה המסקנה ?

אז אומנם php היא שפה מאוד כייפית לפיתוח, ומכילה כמות עצומה של פקודות וספריות, קהילה תומכת וכו' וכו'

אבל כנראה שיש צדדים מסויימים שבהם יש לה חסרונות ולגביהם כנראה צריך לבחור דרך להתגבר עליהם בשפות אחרות.

חבל …אבל זה מה יש.

אם יש לכם קבצי xml שאתם הייצרנים שלהם , ו- php היא שפת הפיתוח שלכם, אז תישארו איתה. אבל כשיש קבצי xml שאתם לא שולטים במקור שלהם, ויש סיכוי לקבצים עם בעיות …תבחרו שפה אחרת כדי לקרוא אותם. זו ההמלצה שלי.

בניה/יצירה של XML פשוט ב-MSXML (VBA)

ככה יוצרים אוביקט XML פשוט ב-MSXML
הדוגמא היא ב-VBA

הדוגמא יוצרת
1. מסמך
2. root node – כלומר הענף המרכזי של העץ
3. ענף "בן" אחד
4. מאפיין/attritube עבור ה-root node

בסוף היא מחזירה את ה-XML, אבל אם רוצים לשמור צריך להשתמש במתודה SAVE.


  Dim mydata As Object, pi As Object
  Dim Node As Object, Child As Object, att As Object
   'create an object, Late Binding
   Set mydata = CreateObject("Msxml2.DOMDocument.6.0")
   'create the first line, before the root element
   Set pi = mydata.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'")
   'now writes the the first line to the XML object
   mydata.InsertBefore pi, mydata.FirstChild
   
   'create root node
   Set Node = mydata.createElement("smart")
   'set attribute to the root node
    Node.setAttribute "goal", "login"
    'create child node
    Set Child = mydata.createElement("time")
    'set a text value to the child node
    Child.Text = Now
    'put the child under the first=root Node
    Node.appendchild Child
    'put the root node under the documnet object
   mydata.appendchild Node
   'return all the xml as string
   XMLobj = mydata.XML
End Function

פענוח פשוט של XML שמועבר ב-POST באמצעות simpleXML של PHP

ה-XML מועבר ב-POST
כאשר ההגדרה *במקור* ששלח את ה-XML היא כזו :

yourXMLobject.setRequestHeader "Content-type", "text/xml"

אז לכן משתמשים ב-raw post
כי – אין פה משתנים למעשה, אלא הכל XML אחד גדול.

זו הפונקציה

un . "
"; echo $xml->pw . "
"; // echo $xml_file; // echo $xml_post; } else { echo "it is not what you want";} ?>

איך לשלוח מידע ב-POST דרך msxml ( Late Binding

הדוגמא הבאה מראה שליחת מידע ב-POST דרך אוביקט xmsml
הדוגמא על VBA

יש לקחת בחשבון שצריך כמובן גם לפתח את הדף שמקבל את הנתונים שיחזיר תשובה כלשהיא – כדי לבדוק את הנתונים.

נקודה חשובה : במשלוח בפוסט, מחברים את הערכים באמצעות סימן &
כלומר

yourVariable=yourValue&anotherVariable=anotherValue

וזה הקוד VBA

Function PostXmlData(vUrl As String, xmlText As String)
Dim XMLHttp As Object
Set XMLHttp = CreateObject("MSXML2.XMLHTTP")

XMLHttp.Open "POST", vUrl, False
XMLHttp.setRequestHeader "Content-type", "application/x-www-form-urlencoded" 'charset=utf-8

XMLHttp.send (xmlText)

PostXmlData = XMLHttp.responseText
Debug.Print PostXmlData
End Function

התמודדות עם EntityRef: expecting ';' in Entity (מחרוזת XML שעוברת מ-MSXML אל PHP XML DOM )

יש לי מודול באחד התוכנות
שמשדר XML מתוך אקסל באמצעות MSXML גירסה 2 של MICROSOFT

אל Web Service ב-PHP

והשירות מחזיר XML בחזרה, כאשר ה-XML נשמר כרשומות בדאטאבייס.

עכשיו – מסתבר שלמרות שיש תקן מאוד ברור ל-XML

אז MSXML – לפעמים מתעלם ממנו.

היתה לי שורה שהכילה את מדד המניות האמריקאי – S&P

כמו שאתם רואים בין האותיות יש את סימן ה- &

אז ה-MSXML מכניס אותו למחרוזת בלי בעיה, למרות שזה לא תקין,

כי לפי ההגדרות – זהו סימן שמור שאמור להפוך לamp

זאת ההגדרה :

'&' (ampersand) becomes '&'

טוב… מה שקרה הוא כמובן תקלה, כי המרכיב ב-PHP שמקבל את זה , XML DOM (אוביקט DOMDocument )

לא ידע לאכול את זה….

הפתרון הוא ממש פשוט במקרה זה ,

רגע לפני שטוענים את ה-מחרוזת XML אל תוך האוביקט

צריך להפוך את הסימני & למשהו אחר.

ככה זה ב-php :

$tmp = str_replace('&','-',$tmp);

בהצלחה!

שמירת רשימת ערכים ממערך בתוך XML (פונקצית VBA )

הרבה פעמים אנחנו צריכים לשמור רשימה משתנה של ערכים.

בצורה של
מפתח = ערך
(key = value )

להלן פונקציה פשוטה שכתבתי שלוקחת מערך דו מימדי
כאשר בכל שורה יש 2 איברים
איבר מספר 1 – שם המאפיין
איבר מספר 2 – ערך המאפיין

הפונקציה מחזירה XML שמכיל את כל הערכים במערך

Public Function ArrayToXML(yAry As Variant) As String
    'This Function Get a 2 dimenesions array, and return XML string
    'On each row of the array need to be with 2 columns :
    '     1 - the name
    '     2 - the value
  Dim XmlObj As Object, pi As Object, I As Integer
  Dim Node As Object, Child As Object, att As Object
   'create an object, Late Binding
   Set XmlObj = CreateObject("Msxml2.DOMDocument.6.0")
   'create the first line, before the root element
   Set pi = XmlObj.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'")
   'now writes the the first line to the XML object
   XmlObj.InsertBefore pi, XmlObj.FirstChild
   'create root node
   Set Node = XmlObj.createelement("YourXmlRootNodeName")
        'Loop on the array
        For I = LBound(yAry, 1) To UBound(yAry, 1)
            'Create a Child node
             Set Child = XmlObj.createelement(yAry(I, 1))
            'Put the value
             Child.Text = yAry(I, 2)
            'put this Child node under the root node
            Node.appendchild Child
        Next I
    
    
    'put the root node under the documnet object
   XmlObj.appendchild Node
   'return all the xml as string
   ArrayToXML = XmlObj.xml
   
End Function

לולאה על ענפים של XML בפונקצית VBA

הפונקציה הבאה יכולה לשמש בסיס להרבה דברים

אם יש לך XML,

ואתה יודע לכתוב את שאילתת ה-XPATH הרצויה

אז בפונקצית VBA הבאה אתה מקבל את כל הענפים של השאילתא ששלחת.

כמובן שאת השורה באמצע הלולאה (שורת ה-Debug.Print ) תחליפו במה שנחוץ אצלכם


    Dim XmlObj As Object, XmlRoot As Object, XPathQuery As String, SingleNode As Object
    
     'îðúç àú äúùåáä
     Set XmlObj = CreateObject("Msxml2.DOMDocument.6.0")
     XmlObj.LoadXML XmlStr
     Set XmlRoot = XmlObj.DocumentElement
     
     XPathQuery = "//smart/*"
     Set xmlNodeList = XmlObj.SelectNodes(XPathQuery)
            
    For Each Node In xmlNodeList
        Debug.Print Node.nodename & " = " & Node.Text
     Next