XML Stream verarbeiten

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
mke
User
Beiträge: 3
Registriert: Samstag 19. Januar 2019, 17:38

Hallo Zusammen,
ich versuche seit einiger Zeit vergebens einen xml stream welchen ich über tcp empfange sinnvoll mit python weiter zu verarbeiten. Akutell versuche ich ihn mit lxml zu parsen, klappt aber leider nicht.
So siehr mein code aus:

Code: Alles auswählen

import socket
import sys
from lxml import etree

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the port
server_address = ('localhost', 4000)
print(sys.stderr, 'connecting to %s port %s' % server_address)
sock.connect(server_address)

while 1:
        chunk = sock.recv(4096)
        print(chunk)
        parser = etree.XMLParser(recover=False, remove_blank_text=True)
        root = etree.fromstring(chunk, parser=parser)
        print(etree.iselement(root))
        print(etree.tostring(root))
        
        
Und so die Ausgabe:

<Isu_Osis Part="1" version="2.0" Counter="1" IsuCalcFs="3.4.12" Database="11147"><Segment_Running Segment_ID="3"><Action Command="NAM" Current_Participant_ID="392" Current_Start_Number="1" Next_Participant_ID="395" Prev_Participant_ID="" Next_Participant_StNum="2" LastScored_Participant_StNum="1"/></Segment_Running></Isu_Osis>

Traceback (most recent call last):
File "C:/Users/mke/PycharmProjects/xml-reciever/main.py", line 16, in <module>
root = etree.fromstring(chunk, parser=parser)
File "src\lxml\etree.pyx", line 3211, in lxml.etree.fromstring
File "src\lxml\parser.pxi", line 1877, in lxml.etree._parseMemoryDocument
File "src\lxml\parser.pxi", line 1765, in lxml.etree._parseDoc
File "src\lxml\parser.pxi", line 1127, in lxml.etree._BaseParser._parseDoc
File "src\lxml\parser.pxi", line 601, in lxml.etree._ParserContext._handleParseResultDoc
File "src\lxml\parser.pxi", line 711, in lxml.etree._handleParseResult
File "src\lxml\parser.pxi", line 640, in lxml.etree._raiseParseError
File "<string>", line 1
lxml.etree.XMLSyntaxError: Start tag expected, '<' not found, line 1, column 1

Process finished with exit code 1

Kann es sein das da noch ein Steruerzeichen vorne dran ist welche ich erst noch entfernen muss? Wenn ja, wie stelle ich das an?

Danke und Gruß!
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mke: Der Code ist fehlerhaft. ``sock.recv(4096)`` liefert 1 bis 4096 Bytes aus dem TCP-Datenstrom. Und Dein Code muss damit klarkommen das das eine *beliebige* Anzahl ist. Also auch wenn die Gegenseite 20 Bytes in einem Aufruf gesendet hat, muss `recv()` die nicht in einem Aufruf liefern. Im Extremfall musst Du damit rechnen das jeder Aufruf nur ein einzelnes Byte liefert. Das heisst wenn Du über die TCP-Verbindung mehrere XML-Dokumente empfangen möchtest, dann brauchst Du ein Protokoll welches sicherstellt das Du ein XML-Dokument komplett empfangen hast, bevor Du das parsen und weiterverarbeiten kannst.

Und wenn das kein Vollduplex-Protokoll ist, dann musst Du auch damit rechnen das mit dem `recv()` wo das Ende einer Nachricht enthalten ist, auch schon der Anfang der nächsten drin steckt.

Grundsätzlich würde ich in Frage stellen warum Du überhaupt auf Socket-Ebene ansetzt. Das ist kompliziert. Es gibt bereits Protokolle die funktionieren und für die schon Code gibt den man verwenden kann, der einem all die Fallstricke von Socket-Programmierung abnimmt.

Warum gibst Du eigentlich am Anfang das `sys.stderr`-Dateiobjekt aus? Das macht keinen Sinn.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie Du schon schriebst, hast Du einen Stream, wo also das XML Stück für Stück kommt, etree.from_string erwartet aber das gesamte XML. Und wenn der Stream keine Information sendet, wo das XML beginnt und endet, ist das nochmal eine Stufe schwieriger.

Ob da ein Steuerzeichen vorne dran ist, sehen wir nicht, da solltest Du mal die Repräsentation der Empfangenen bytes posten. Woher kommt eigentlich dieser TCP-Stream?
mke
User
Beiträge: 3
Registriert: Samstag 19. Januar 2019, 17:38

Hallo und danke für eure Antworten!

also, laut Doku gibt es ein steuerzeichen jeweils am Anfang und am Ende jeder XML Botschaft:
<STX>xml message<ETX><STX>xml message<ETX><STX>xml message<ETX>
Hab ich jetzt auch gesehen als ich den XML Stream aus dem Python Log Fenster rauskopiert und in ein Notepad Editor eingefügt habe.

Das das mit anzahl der Bytes noch nicht ideal gelöst ist war mir auch schon bewusst. Wie bekomme ich das besser hin? Vermutlich könnten mir die Steuerzeichen dabei helfen, oder?

Hintergrund was ich machen möchte. Ich habe hier eine Software der swiss-timing, ein Bewertungssystem für Sportveranstaltungen. Diese bietet eine xml Schnitstelle um live ergebnisse auf Anzeigen zu streamen. Diese würde ich gerne abfragen und auswerten.
Die Doku sagt folgendes:
"The program provides one TCP/IP or serial connection with the OSIS interface. We recommend the TCP/IP connection."
Da habe ich halt mal eine soket verbindung auf den entsprechenden Port aufgebaut was soweit auch gut funktioniert. Natürlich bin ich auf für andere Lösungen offen.
Danke euch!
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Kannst du die Daten mal in ein Pastebin tun? Und ggf. die Dokumentation verlinken, so das man die mal anschauen kann?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Sowas könnte primitiv so aussehen:

Code: Alles auswählen

import socket
from lxml import etree

class TCPReader:
    STX = b"\0x02"
    ETX = b"\0x03"

    def __init__(server_address):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect(server_address)
        self.sock = sock
        self.buf = b""
    
    def close(self):
        self.sock.close()

    def read_message(self):
        while True:
            if self.ETX in self.buf:
                message, self.buf = self.buf.split(self.ETX, 1)
                if message[0] != self.STX:
                    raise RuntimeError("STX missing", message)
                return message[1:]
            chunk = self.sock.recv(1024)
            if not chunk:
                return None
            self.buf += chunk
    
    def __iter__(self):
        while True:
            message = self.read_message()
            if message is None:
                return
            yield message


server_address = ('localhost', 4000)
reader = TCPReader(server_address)
for message in reader:
    parser = etree.XMLParser(recover=False, remove_blank_text=True)
    root = etree.fromstring(message, parser=parser)
    print(etree.iselement(root))
    print(etree.tostring(root))
mke
User
Beiträge: 3
Registriert: Samstag 19. Januar 2019, 17:38

@Sirius3: Danke für deinen Code, nach ein paar kleinen Änderungen funktioniert der sehr gut!

Jetzt habe ich noch eine Frage zur weiterverarbeitung:

Code: Alles auswählen

  print(etree.tostring(root, pretty_print=True))

    for e in root:
        print(e.tag)
        print(e.attrib)

    #for e in root.Segment_Running:
    #   print(e.tag)

    for a in root.getchildren():
        print(a.attrib)
        for b in a.getchildren():
            print(b.attrib)
            for c in b.getchildren():
                print(c.attrib)
                
Der auskommentierte Teil funktioniert nicht was ich aber nicht hanz verstehe. Denn die Ausgabe sieht wie folgt aus:
b'<Isu_Osis Part="1" version="2.0" Counter="1" IsuCalcFs="3.4.12" Database="11147">\n <Segment_Running Segment_ID="3">\n <Action Command="NAM" Current_Participant_ID="395" Current_Start_Number="2" Next_Participant_ID="100" Prev_Participant_ID="392" Next_Participant_StNum="3" LastScored_Participant_StNum="1"/>\n </Segment_Running>\n</Isu_Osis>\n'
Segment_Running
{'Segment_ID': '3'}
{'Segment_ID': '3'}
{'Command': 'NAM', 'Current_Participant_ID': '395', 'Current_Start_Number': '2', 'Next_Participant_ID': '100', 'Prev_Participant_ID': '392', 'Next_Participant_StNum': '3', 'LastScored_Participant_StNum': '1'}

Also "Segment_Running" ist doch ein Element von "root" , wieso kann ich das dann nicht so ansprechen?
Fehlermeldung ist :
AttributeError: 'lxml.etree._Element' object has no attribute 'Segment_Running'
Das es nicht ein Attribut ist ist mir auch klar, aber das will ich ja auch nicht.

Andere Frage, wie greife ich am besten auf Atribute zu die einige Ebenen tiefer liegen.

Danke euch!
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mke: Wenn Du weisst, dass es kein Attribut ist, warum greifst Du dann darauf zu als wäre es eines, und wunderst Dich dann wieso Du *so* nicht darauf zugreifen kannst?

Du braucht halt eine der Methoden zum Suchen zum Beispiel die `iter()`-Methode wie man gleich am Anfang des Abschnitts Finding interesting elements im Tutorial zu der API in der Python-Dokumentation findet. Bei `lxml` hat man dann noch XPath vollständig, statt nur einer Untermenge, wenn es kompliziertere Suchausdrücke als einfach nur der Tag/Name des Elements sein sollen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sirius3: Gleichfalls nur rudimentär und schnell dahingeschrieben, würde ich einen möglichen RuntimeError zu beginn des Iterierens ignorieren wollen. Denn möglicherweise klingt sich der TCPReader in einen laufenden Stream ein, hat daher ein vorhergehendes STX nicht erhalten und muss sich erst einmal im Stream synchronisieren.

Code: Alles auswählen

    def __iter__(self):
        try:
            message = self.read_message()
        except RuntimeError:
            pass
        else:
            yield message
        while True:
            message = self.read_message()
            if message is None:
                return
            yield message
Antworten