Python Forum
XML minidom "Pretty Print" Lost Data
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
XML minidom "Pretty Print" Lost Data
#1
I have code that successfully creates a desired XML file from a provided CSV file. However, I'm trying to format it to be easier to read. I want the list of "triggers" and list of "messages" to be separated by a blank line and each "trigger" and each "message" should have its own line. Trying to use minidom, but the "pretty" file loses the triggers for some reason.

Code:

import csv
import os
import tkinter as tk
from tkinter import filedialog, simpledialog
import xml.etree.ElementTree as ET
from xml.dom import minidom

def process_csv_to_xml():
    # Initialize tkinter
    root = tk.Tk()
    root.withdraw()  # Hide main window

    # Prompt for CSV file
    csv_file_path = filedialog.askopenfilename(title="Select 'Tag Export' CSV file", filetypes=[("CSV files", "*.csv")])
    if not csv_file_path:
        print("No file selected.")
        return

    # Prompt for PLC Shortcut with default value
    plc_shortcut = simpledialog.askstring("Input", "Enter PLC Shortcut (STRING):", initialvalue="SCP", parent=root)
    if not plc_shortcut:
        print("PLC Shortcut not provided.")
        return

    # Prompt for Starting Trigger ID with default value
    starting_trigger_id = simpledialog.askinteger("Input", "Enter Starting Trigger ID (INT):", initialvalue=1, parent=root)
    if starting_trigger_id is None:
        print("Starting Trigger ID not provided.")
        return

    # Prompt for Starting Message ID with default value
    starting_message_id = simpledialog.askinteger("Input", "Enter Starting Message ID (INT):", initialvalue=1, parent=root)
    if starting_message_id is None:
        print("Starting Message ID not provided.")
        return
        
    # Prompt for XML file name with default value
    xml_file_path = filedialog.asksaveasfilename(
        title="Save XML file",
        defaultextension=".xml",
        filetypes=[("XML files", "*.xml")],
        initialfile="Alarms",
        initialdir=os.path.dirname(csv_file_path)
    )
    if not xml_file_path:
        print("XML file not saved.")
        return

    triggers = []
    messages = []
    trigger_number = starting_trigger_id
    message_number = starting_message_id

    with open(csv_file_path, 'r', newline='') as csv_file:
        csv_reader = csv.reader(csv_file)
        for _ in range(6):
            next(csv_reader)  # Skip first 6 lines
        headers = next(csv_reader)  # Line 7 is the headers

        for row in csv_reader:
            tag_type = row[headers.index('TYPE')]
            tag_name = row[headers.index('NAME')]
            data_type = row[headers.index('DATATYPE')]

            # Debug info
            print(f"Processing tag: Name='{tag_name}', DataType='{data_type}'")

            if tag_type == 'TAG':
                if data_type == 'All_DigitalAlm_ActiveResumeDelay':
                    trigger = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                         ack_all_value="0", use_ack_all="false", ack_tag="",
                                         exp=f"{{[{plc_shortcut}]{tag_name}.ALM_IND}}",
                                         message_tag="", message_handshake_exp="",
                                         message_notification_tag="", remote_ack_exp="",
                                         remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                         handshake_tag="")
                    triggers.append(trigger)
                    trigger_number += 1

                elif data_type == 'All_ScaleSignal_2SPFailAlm':
                    trigger = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                         ack_all_value="0", use_ack_all="false", ack_tag="",
                                         exp=f"{{[{plc_shortcut}]{tag_name}.ALM_Display}}",
                                         message_tag="", message_handshake_exp="",
                                         message_notification_tag="", remote_ack_exp="",
                                         remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                         handshake_tag="")
                    triggers.append(trigger)
                    trigger_number += 1

                elif data_type == 'Wtr_Motor_Alm':
                    trigger1 = ET.Element('trigger', id=f"T{trigger_number}", type="bit",
                                          ack_all_value="0", use_ack_all="false", ack_tag="",
                                          exp=f"{{[{plc_shortcut}]{tag_name}.Thermal_Seal_Status}}",
                                          message_tag="", message_handshake_exp="",
                                          message_notification_tag="", remote_ack_exp="",
                                          remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                          handshake_tag="")
                    triggers.append(trigger1)
                    trigger_number += 1

                    trigger2 = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                          ack_all_value="0", use_ack_all="false", ack_tag="",
                                          exp=f"{{[{plc_shortcut}]{tag_name}.Run_Fail_Status}}",
                                          message_tag="", message_handshake_exp="",
                                          message_notification_tag="", remote_ack_exp="",
                                          remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                          handshake_tag="")
                    triggers.append(trigger2)
                    trigger_number += 1

                elif data_type == 'More_Scale_Signal_For_Valve':
                    trigger = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                         ack_all_value="0", use_ack_all="false", ack_tag="",
                                         exp=f"{{[{plc_shortcut}]{tag_name}.Open_Close_Status}}",
                                         message_tag="", message_handshake_exp="",
                                         message_notification_tag="", remote_ack_exp="",
                                         remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                         handshake_tag="")
                    triggers.append(trigger)
                    trigger_number += 1

            # Handle messages
            if data_type == 'All_DigitalAlm_ActiveResumeDelay':
                message = ET.Element('message', id=f"M{message_number}", trigger_value="1",
                                     identifier=str(message_number), trigger=f"#T{trigger_number-1}",
                                     backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                     display="true", print="false", message_to_tag="false",
                                     text=tag_name)
                messages.append(message)
                message_number += 1

            elif data_type == 'All_ScaleSignal_2SPFailAlm':
                for i in range(5):
                    message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+1),
                                         identifier=str(message_number), trigger=f"#T{trigger_number-1}",
                                         backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                         display="true", print="false", message_to_tag="false",
                                         text=f"{tag_name}{'Low Float' if i == 0 else ('Low' if i == 1 else ('Signal Fail' if i == 2 else ('High' if i == 3 else 'High Float')))}")
                    messages.append(message)
                    message_number += 1

            elif data_type == 'Wtr_Motor_Alm':
                for i in range(5):
                    if i < 3:
                        message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+1),
                                             identifier=str(message_number), trigger=f"#T{trigger_number-2+i}",
                                             backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                             display="true", print="false", message_to_tag="false",
                                             text=f"{tag_name}{'Thermal Fail' if i == 0 else ('Seal Fail' if i == 1 else ('Fail to Run' if i == 2 else ('RVSS Fault' if i == 3 else 'VFD Fault')))}")
                    else:
                        message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+1),
                                             identifier=str(message_number), trigger=f"#T{trigger_number-2+i}",
                                             backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                             display="true", print="false", message_to_tag="false",
                                             text=f"{tag_name}{'VFD Fault'}")
                    messages.append(message)
                    message_number += 1

            elif data_type == 'More_Scale_Signal_For_Valve':
                for i in range(3):
                    message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+3),
                                         identifier=str(message_number), trigger=f"#T{trigger_number-1}",
                                         backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                         display="true", print="false", message_to_tag="false",
                                         text=f"{tag_name}{'Position Fail' if i == 0 else ('Fail to Open' if i == 1 else 'Fail to Close')}")
                    messages.append(message)
                    message_number += 1

    # Debugging: Check contents of triggers and messages
    print(f"Total Triggers: {len(triggers)}")
    for t in triggers:
        print(f"Trigger: {ET.tostring(t, 'unicode')}")
    print(f"Total Messages: {len(messages)}")
    for m in messages:
        print(f"Message: {ET.tostring(m, 'unicode')}")

    # Create XML structure
    root_elem = ET.Element('root')

    # Append triggers
    triggers_elem = ET.SubElement(root_elem, 'triggers')
    for trigger in triggers:
        triggers_elem.append(trigger)

    # Append messages
    messages_elem = ET.SubElement(root_elem, 'messages')
    for message in messages:
        messages_elem.append(message)

    # Convert XML to string with proper formatting
    rough_string = ET.tostring(root_elem, 'utf-8')

    # Write the raw XML to a file
    raw_xml_file_path = xml_file_path.replace(".xml", "_RAW.xml")
    with open(raw_xml_file_path, 'wb') as raw_file:
        raw_file.write(rough_string)
    
    print(f"Raw XML file has been saved successfully at {raw_xml_file_path}")

    # Pretty print the XML
    try:
        reparsed = minidom.parseString(rough_string)
        pretty_xml = reparsed.toprettyxml(indent="  ")

        # Insert a blank line between <triggers> and <messages>
        lines = pretty_xml.split('\n')
        for i, line in enumerate(lines):
            if "<triggers>" in line:
                triggers_end = i
            if "<messages>" in line:
                messages_start = i
                break

        formatted_xml = "\n".join(lines[:triggers_end+1] + [""] + lines[messages_start:])
    except Exception as e:
        print(f"Error during pretty printing: {e}")
        formatted_xml = rough_string.decode('utf-8')

    # Write the pretty-printed XML to a file
    pretty_xml_file_path = xml_file_path.replace(".xml", "_PRETTY.xml")
    with open(pretty_xml_file_path, 'w', encoding='utf-8') as pretty_file:
        pretty_file.write(formatted_xml)
    
    print(f"Pretty XML file has been saved successfully at {pretty_xml_file_path}")

if __name__ == "__main__":
    process_csv_to_xml()
Test file and resultant XML files are attached.

Attached Files

Thumbnail(s)
       

.csv   Test_Tags.CSV (Size: 1.18 KB / Downloads: 18)
.xml   Alarms_PRETTY.xml (Size: 5.27 KB / Downloads: 18)
.xml   Alarms_RAW.xml (Size: 7.25 KB / Downloads: 24)
Reply
#2
(Jun-14-2024, 08:36 PM)marksy95 Wrote: I have code that successfully creates a desired XML file from a provided CSV file. However, I'm trying to format it to be easier to read. I want the list of "triggers" and list of "messages" to be separated by a blank line and each "trigger" and each "message" should have its own line. Trying to use minidom, but the "pretty" file loses the triggers for some reason.

Code:

import csv
import os
import tkinter as tk
from tkinter import filedialog, simpledialog
import xml.etree.ElementTree as ET
from xml.dom import minidom

def process_csv_to_xml():
    # Initialize tkinter
    root = tk.Tk()
    root.withdraw()  # Hide main window

    # Prompt for CSV file
    csv_file_path = filedialog.askopenfilename(title="Select 'Tag Export' CSV file", filetypes=[("CSV files", "*.csv")])
    if not csv_file_path:
        print("No file selected.")
        return

    # Prompt for PLC Shortcut with default value
    plc_shortcut = simpledialog.askstring("Input", "Enter PLC Shortcut (STRING):", initialvalue="SCP", parent=root)
    if not plc_shortcut:
        print("PLC Shortcut not provided.")
        return

    # Prompt for Starting Trigger ID with default value
    starting_trigger_id = simpledialog.askinteger("Input", "Enter Starting Trigger ID (INT):", initialvalue=1, parent=root)
    if starting_trigger_id is None:
        print("Starting Trigger ID not provided.")
        return

    # Prompt for Starting Message ID with default value
    starting_message_id = simpledialog.askinteger("Input", "Enter Starting Message ID (INT):", initialvalue=1, parent=root)
    if starting_message_id is None:
        print("Starting Message ID not provided.")
        return
        
    # Prompt for XML file name with default value
    xml_file_path = filedialog.asksaveasfilename(
        title="Save XML file",
        defaultextension=".xml",
        filetypes=[("XML files", "*.xml")],
        initialfile="Alarms",
        initialdir=os.path.dirname(csv_file_path)
    )
    if not xml_file_path:
        print("XML file not saved.")
        return

    triggers = []
    messages = []
    trigger_number = starting_trigger_id
    message_number = starting_message_id

    with open(csv_file_path, 'r', newline='') as csv_file:
        csv_reader = csv.reader(csv_file)
        for _ in range(6):
            next(csv_reader)  # Skip first 6 lines
        headers = next(csv_reader)  # Line 7 is the headers

        for row in csv_reader:
            tag_type = row[headers.index('TYPE')]
            tag_name = row[headers.index('NAME')]
            data_type = row[headers.index('DATATYPE')]

            # Debug info
            print(f"Processing tag: Name='{tag_name}', DataType='{data_type}'")

            if tag_type == 'TAG':
                if data_type == 'All_DigitalAlm_ActiveResumeDelay':
                    trigger = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                         ack_all_value="0", use_ack_all="false", ack_tag="",
                                         exp=f"{{[{plc_shortcut}]{tag_name}.ALM_IND}}",
                                         message_tag="", message_handshake_exp="",
                                         message_notification_tag="", remote_ack_exp="",
                                         remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                         handshake_tag="")
                    triggers.append(trigger)
                    trigger_number += 1

                elif data_type == 'All_ScaleSignal_2SPFailAlm':
                    trigger = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                         ack_all_value="0", use_ack_all="false", ack_tag="",
                                         exp=f"{{[{plc_shortcut}]{tag_name}.ALM_Display}}",
                                         message_tag="", message_handshake_exp="",
                                         message_notification_tag="", remote_ack_exp="",
                                         remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                         handshake_tag="")
                    triggers.append(trigger)
                    trigger_number += 1

                elif data_type == 'Wtr_Motor_Alm':
                    trigger1 = ET.Element('trigger', id=f"T{trigger_number}", type="bit",
                                          ack_all_value="0", use_ack_all="false", ack_tag="",
                                          exp=f"{{[{plc_shortcut}]{tag_name}.Thermal_Seal_Status}}",
                                          message_tag="", message_handshake_exp="",
                                          message_notification_tag="", remote_ack_exp="",
                                          remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                          handshake_tag="")
                    triggers.append(trigger1)
                    trigger_number += 1

                    trigger2 = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                          ack_all_value="0", use_ack_all="false", ack_tag="",
                                          exp=f"{{[{plc_shortcut}]{tag_name}.Run_Fail_Status}}",
                                          message_tag="", message_handshake_exp="",
                                          message_notification_tag="", remote_ack_exp="",
                                          remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                          handshake_tag="")
                    triggers.append(trigger2)
                    trigger_number += 1

                elif data_type == 'More_Scale_Signal_For_Valve':
                    trigger = ET.Element('trigger', id=f"T{trigger_number}", type="value",
                                         ack_all_value="0", use_ack_all="false", ack_tag="",
                                         exp=f"{{[{plc_shortcut}]{tag_name}.Open_Close_Status}}",
                                         message_tag="", message_handshake_exp="",
                                         message_notification_tag="", remote_ack_exp="",
                                         remote_ack_handshake_tag="", label=f"Label{trigger_number}",
                                         handshake_tag="")
                    triggers.append(trigger)
                    trigger_number += 1

            # Handle messages
            if data_type == 'All_DigitalAlm_ActiveResumeDelay':
                message = ET.Element('message', id=f"M{message_number}", trigger_value="1",
                                     identifier=str(message_number), trigger=f"#T{trigger_number-1}",
                                     backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                     display="true", print="false", message_to_tag="false",
                                     text=tag_name)
                messages.append(message)
                message_number += 1

            elif data_type == 'All_ScaleSignal_2SPFailAlm':
                for i in range(5):
                    message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+1),
                                         identifier=str(message_number), trigger=f"#T{trigger_number-1}",
                                         backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                         display="true", print="false", message_to_tag="false",
                                         text=f"{tag_name}{'Low Float' if i == 0 else ('Low' if i == 1 else ('Signal Fail' if i == 2 else ('High' if i == 3 else 'High Float')))}")
                    messages.append(message)
                    message_number += 1

            elif data_type == 'Wtr_Motor_Alm':
                for i in range(5):
                    if i < 3:
                        message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+1),
                                             identifier=str(message_number), trigger=f"#T{trigger_number-2+i}",
                                             backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                             display="true", print="false", message_to_tag="false",
                                             text=f"{tag_name}{'Thermal Fail' if i == 0 else ('Seal Fail' if i == 1 else ('Fail to Run' if i == 2 else ('RVSS Fault' if i == 3 else 'VFD Fault')))}")
                    else:
                        message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+1),
                                             identifier=str(message_number), trigger=f"#T{trigger_number-2+i}",
                                             backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                             display="true", print="false", message_to_tag="false",
                                             text=f"{tag_name}{'VFD Fault'}")
                    messages.append(message)
                    message_number += 1

            elif data_type == 'More_Scale_Signal_For_Valve':
                for i in range(3):
                    message = ET.Element('message', id=f"M{message_number}", trigger_value=str(i+3),
                                         identifier=str(message_number), trigger=f"#T{trigger_number-1}",
                                         backcolor="#800000", forecolor="#FFFFFF", audio="false",
                                         display="true", print="false", message_to_tag="false",
                                         text=f"{tag_name}{'Position Fail' if i == 0 else ('Fail to Open' if i == 1 else 'Fail to Close')}")
                    messages.append(message)
                    message_number += 1

    # Debugging: Check contents of triggers and messages
    print(f"Total Triggers: {len(triggers)}")
    for t in triggers:
        print(f"Trigger: {ET.tostring(t, 'unicode')}")
    print(f"Total Messages: {len(messages)}")
    for m in messages:
        print(f"Message: {ET.tostring(m, 'unicode')}")

    # Create XML structure
    root_elem = ET.Element('root')

    # Append triggers
    triggers_elem = ET.SubElement(root_elem, 'triggers')
    for trigger in triggers:
        triggers_elem.append(trigger)

    # Append messages
    messages_elem = ET.SubElement(root_elem, 'messages')
    for message in messages:
        messages_elem.append(message)

    # Convert XML to string with proper formatting
    rough_string = ET.tostring(root_elem, 'utf-8')

    # Write the raw XML to a file
    raw_xml_file_path = xml_file_path.replace(".xml", "_RAW.xml")
    with open(raw_xml_file_path, 'wb') as raw_file:
        raw_file.write(rough_string)
    
    print(f"Raw XML file has been saved successfully at {raw_xml_file_path}")

    # Pretty print the XML
    try:
        reparsed = minidom.parseString(rough_string)
        pretty_xml = reparsed.toprettyxml(indent="  ")

        # Insert a blank line between <triggers> and <messages>
        lines = pretty_xml.split('\n')
        for i, line in enumerate(lines):
            if "<triggers>" in line:
                triggers_end = i
            if "<messages>" in line:
                messages_start = i
                break

        formatted_xml = "\n".join(lines[:triggers_end+1] + [""] + lines[messages_start:])
    except Exception as e:
        print(f"Error during pretty printing: {e}")
        formatted_xml = rough_string.decode('utf-8')

    # Write the pretty-printed XML to a file
    pretty_xml_file_path = xml_file_path.replace(".xml", "_PRETTY.xml")
    with open(pretty_xml_file_path, 'w', encoding='utf-8') as pretty_file:
        pretty_file.write(formatted_xml)
    
    print(f"Pretty XML file has been saved successfully at {pretty_xml_file_path}")

if __name__ == "__main__":
    process_csv_to_xml()
Test file and resultant XML files are attached.

Seems like the only real issue is trying to join them back together with the newline.
Reply
#3
you can use pandas to both read a csv file, and export to xml with just a few lines of code.
  1. To read csv file see:
  2. To output as xml see
As for finding files in a directory, use pathlib.
To get a list of csv files in any directory:
from pathlib import Path

mycsvdir = Path("./data/csv")
csvfilelist = [filename for filename in mydir.iterdir() if filename.is_file and filename.suffix == '.csv']
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  i tried to install python for the first time today and pretty certain im being remote brianlj 2 716 Oct-03-2023, 11:15 AM
Last Post: snippsat
  Lost Control over VLC jrockow 8 1,271 Jul-18-2023, 06:04 PM
Last Post: jrockow
  Lost Modules standenman 2 847 Jun-22-2023, 12:18 PM
Last Post: standenman
  print(data) is suddenly invalid syntax db042190 6 1,447 Jun-14-2023, 02:55 PM
Last Post: deanhystad
  Lost Module standenman 10 3,114 Oct-30-2021, 05:11 PM
Last Post: deanhystad
  XML Editing formatting lost ateestructural 2 2,006 Apr-08-2021, 04:41 AM
Last Post: ndc85430
  how to print all data from all data array? korenron 3 2,598 Dec-30-2020, 01:54 PM
Last Post: korenron
  List structure lost when multiplying Protonn 2 2,385 Apr-23-2020, 04:16 AM
Last Post: buran
  How do I install the dom.minidom module? Johno 3 7,385 Apr-17-2020, 09:46 PM
Last Post: Larz60+
  How to print counter without bracket in python and sort data. phob0s 1 2,886 Jul-25-2019, 05:33 PM
Last Post: ichabod801

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020