{"id":21997660,"url":"https://github.com/osfunapps/os-xml-handler-py","last_synced_at":"2025-03-23T04:45:09.072Z","repository":{"id":57449802,"uuid":"307070613","full_name":"osfunapps/os-xml-handler-py","owner":"osfunapps","description":"this module contains fundamental xml manipulation tools to implement in a Python project.","archived":false,"fork":false,"pushed_at":"2021-02-07T14:14:32.000Z","size":9,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-28T11:32:09.899Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osfunapps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-25T10:07:31.000Z","updated_at":"2023-08-11T07:13:56.000Z","dependencies_parsed_at":"2022-09-14T10:35:29.557Z","dependency_job_id":null,"html_url":"https://github.com/osfunapps/os-xml-handler-py","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osfunapps%2Fos-xml-handler-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osfunapps%2Fos-xml-handler-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osfunapps%2Fos-xml-handler-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osfunapps%2Fos-xml-handler-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osfunapps","download_url":"https://codeload.github.com/osfunapps/os-xml-handler-py/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245056902,"owners_count":20553854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-29T22:17:36.202Z","updated_at":"2025-03-23T04:45:09.039Z","avatar_url":"https://github.com/osfunapps.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Introduction\n------------\n\nthis module contains fundamental xml manipulation tools to implement in a Python project.\n\n## Installation\nInstall via pip:\n\n    pip install os-xml-handler\n\n\n## Usage       \n```python \nimport xml_file_handler as xh\n```    \n\n## Functions and signatures:\n\n```python\n# will read and return an xml file.\n# added a custom parser to prevent the commends from being removed\ndef read_xml_file(xml_path, namespace_dict=None, remove_comments=False):\n    parser = etree.XMLParser(remove_comments=remove_comments)\n    tree = etree.parse(xml_path, parser=parser)\n    etree.set_default_parser(parser)\n    if namespace_dict:\n        for prefix, uri in namespace_dict.items():\n            etree.register_namespace(prefix, uri)\n    return tree\n\n\n# will save the changes made in an xml file\ndef save_xml_file(xml_file, xml_path, add_utf_8_encoding=False):\n    if add_utf_8_encoding:\n        xml_file.write(xml_path, encoding=\"UTF-8\")\n    else:\n        xml_file.write(xml_path)\n\n# will return a list of nodes specified by an attribute key and an attribute value from a parent node\ndef get_child_nodes(node_parent, node_tag, node_att_name=None, node_att_val=None, namespace_map=None, filter_comment_nodes=True):\n    selector = node_tag\n    return find_all_nodes(node_parent, node_tag, node_att_name, node_att_val, recursive=False, namespace_map=namespace_map, filter_comment_nodes=filter_comment_nodes)\n\n\n# will search in all of the direct children of the root\ndef get_root_direct_child_nodes(xml_file, node_tag, node_att_name=None, node_att_val=None, namespace_map=None, filter_comment_nodes=True):\n    return get_child_nodes(get_root_node(xml_file), node_tag, node_att_name, node_att_val, namespace_map, filter_comment_nodes)\n\n\n# will return a list of nodes which doesn't have a specific attribute\ndef get_nodes_from_xml_without_att(xml_file, node_tag, node_att_name=None, namespace_map=None, filter_comment_nodes=True):\n    root = xml_file.getroot()\n    relevant_nodes = []\n    node_att_name = _sanitize_att_name(node_att_name, namespace_map)\n    nodes = root.findall(node_tag)\n    for node in nodes:\n        if get_node_att(node, node_att_name) is None:\n            relevant_nodes.append(node)\n    if filter_comment_nodes:\n        relevant_nodes = filter_comments(relevant_nodes)\n    return relevant_nodes\n\n\n# will remove any comment nodes from a node list\ndef filter_comments(node_list):\n    res = []\n    for node in node_list:\n        if not is_comment_node(node):\n            res.append(node)\n    return res\n\n\n# will check if a node is a comment node\ndef is_comment_node(node):\n    return type(node) is etree._Comment\n\n\ndef nodes_to_dict(nodes, att_key):\n    \"\"\"\n    Will turn a list of xml nodes to a dictionary carrying the nodes.\n    The keys of the dictionary will be the attribute value of each node and the values of of the dictionary will be the inner text\n    of each node.\n\n    For example, if we have these xml nodes:\n        \u003cstring name=\"app_name\"\u003eFirst Remote\u003c/string\u003e\n        \u003cstring name=\"app_short_name\" translatable=\"false\"\u003eremote\u003c/string\u003e\n\n    xml_nodes_to_dict(nodes, 'name') will return:\n    dict = {'app_name': 'First Remote', 'app_short_name': 'remote'}\n\n    param nodes: the xml nodes to search upon\n    param att_key: the attribute to search for it's value in each node\n    return: a dictionary representation of the nodes\n    \"\"\"\n\n    nodes_dict = {}\n    for node in nodes:\n        nodes_dict[get_node_att(node, att_key)] = get_text_from_node(node)\n    return nodes_dict\n\n\n# will return all of the direct children of a given node\ndef get_all_direct_child_nodes(node, filter_comment_nodes=True):\n    nodes = list(node)\n    if filter_comment_nodes:\n        return filter_comments(nodes)\n    return nodes\n\n\n# will return the text (inner html) of a given node\ndef get_text_from_node(node):\n    text = node.text\n    if text == '\\n        ':\n        return None\n    return node.text\n\n\n# will return the text from a child node, using the parent node.\n# NOTICE: this function will not crash but return None if the node isn't exists\ndef get_text_from_child_node(parent_node, child_node_tag, child_node_att_name=None, child_node_att_val=None):\n    child_nodes = get_child_nodes(parent_node, child_node_tag, child_node_att_name, child_node_att_val)\n    if child_nodes:\n        return get_text_from_node(child_nodes[0])\n    else:\n        return None\n\n\n# will set the text (inner html) in a given node\ndef set_node_text(node, text):\n    node.text = text\n\n\n# will return the value of a given att from a desired node\ndef get_node_att(node, att_name, namespace_map=None):\n    att_name = _sanitize_att_name(att_name, namespace_map)\n    return node.get(att_name)\n\n# will create an xml file\ndef create_xml_file(root_node_tag, output_file):\n    xml = etree.Element(root_node_tag)\n    tree = etree.ElementTree(xml)\n\n    # create dir if not exists\n    from os_file_handler import file_handler\n    parent_dir = file_handler.get_parent_path(output_file)\n    if not file_handler.is_dir_exists(parent_dir):\n        file_handler.create_dir(parent_dir)\n\n    save_xml_file(tree, output_file)\n    # tree = read_xml_file(output_file)   # maybe to read the xml again, to prevent the comments from being removed?\n    return tree\n\n\n# will add a node to a relative location\ndef create_and_add_new_node(parent_node, node_tag, att_val_dict=None, node_text=None, namespace_map=None):\n    if att_val_dict is None:\n        att_val_dict = {}\n    if att_val_dict is not None and namespace_map is not None:\n        node_att_val_dict = {}\n        for key, val in att_val_dict.items():\n            node_att_val_dict[_sanitize_att_name(key, namespace_map)] = val\n        att_val_dict = node_att_val_dict\n\n    node = etree.SubElement(parent_node, node_tag, att_val_dict)\n    if node_text is not None:\n        set_node_text(node, node_text)\n    return node\n\n\n# will add an existing node to a parent node\ndef add_node(parent_node, child_node):\n    direct_children = get_all_direct_child_nodes(parent_node)\n    location = 0\n    if direct_children:\n        location = len(direct_children)\n    parent_node.insert(location, child_node)\n\n\n# will add a bunch of already existing nodes to a parent node\ndef add_nodes(parent_node, child_nodes):\n    for child_node in child_nodes:\n        add_node(parent_node, child_node)\n\n\n# will merge xml1 with xml2 and return a new xml comprising both of them.\n# NOTICE: this function will compare the direct root child nodes and merge/append them.\ndef merge_xml1_with_xml2(xml1, xml2):\n    xml1_root = get_root_node(xml1)\n    xml1_direct_children = get_all_direct_child_nodes(xml1_root)\n    for xml2_child in get_all_direct_child_nodes(get_root_node(xml2)):\n        parent_node = None\n        for xml1_child in xml1_direct_children:\n            # if the direct child already exists, add the appended tag content to the existing one\n            if xml2_child.tag == xml1_child.tag:\n                parent_node = xml1_child\n                break\n\n        if parent_node is not None:\n            xml2_direct_children = get_all_direct_child_nodes(xml2_child)\n            if len(xml2_direct_children) \u003e 0:\n                add_nodes(parent_node, xml2_direct_children)\n            else:\n                # if reached here, it means that only the text is resembled\n                set_node_text(parent_node, f'{get_text_from_node(parent_node)}\\n{get_text_from_node(xml2_child)}')\n        else:\n            add_node(xml1_root, xml2_child)\n\n    return xml1\n\n\n# Will turn a simple dictionary to an xml file, by hierarchical order\ndef simple_dict_to_xml(xml_dict, root_name, output_path, namespace_map=None):\n    # will unpack the parent recursively\n    def recursive_unpack_parent(parent_dict, parent=None):\n        for key, val in parent_dict.items():\n            parent = create_and_add_new_node(parent, xml, key, namespace_map=namespace_map)\n            if type(val) is dict:\n                recursive_unpack_parent(val, parent)\n\n    xml = create_xml_file(root_name, output_path)\n    recursive_unpack_parent(xml_dict)\n    save_xml_file(xml, output_path)\n\n\ndef get_root_node(xml_file):\n    return xml_file.getroot()\n\n\n# will add attribute to a given node.\n# If adding a namespace map, add, for example {'android', 'http://schemas.android.com/apk/res/android'}. The keys should be normal. Like: {\"android:value\": xxxx}\ndef set_node_atts(node, atts_dict, namespace_map=None):\n    for key, val in atts_dict.items():\n        key = _sanitize_att_name(key, namespace_map)\n        node.set(key, val)\n\n\n# will search for a node\ndef find_all_nodes(parent_node, node_tag, node_att_name=None, node_att_val=None, recursive=True, namespace_map=None, filter_comment_nodes=True):\n    selector = node_tag\n    if node_att_name is not None:\n        node_att_name = _sanitize_att_name(node_att_name, namespace_map)\n        if node_att_val is not None:\n            selector = node_tag + \"/[@\" + node_att_name + \"='\" + node_att_val + \"']\"\n        else:\n            selector = node_tag + \"/[@\" + node_att_name + \"]\"\n    if recursive:\n        nodes = parent_node.findall(f'.//{selector}')\n    else:\n        nodes = parent_node.findall(selector, namespace_map)\n    if filter_comment_nodes:\n        return filter_comments(nodes)\n    return nodes\n\n\n# will parse the namespace, as required, in the att_name\ndef _sanitize_att_name(att_name, namespace_map):\n    if namespace_map is not None:\n        for prefix, uri in namespace_map.items():\n            if prefix in att_name:\n                att_name = '{' + str(att_name).replace(':', '}').replace(prefix, uri)\n                break\n    return att_name\n```\nAnd more...\n\n\n## Links\n[GitHub - osapps](https://github.com/osfunapps)\n\n## Licence\nISC","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosfunapps%2Fos-xml-handler-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosfunapps%2Fos-xml-handler-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosfunapps%2Fos-xml-handler-py/lists"}