def test_renormalize_xml_obj_ids(stc):
    root = etree.fromstring(get_test_xml())
    xml_utils.renormalize_xml_obj_ids(root)
    vlan_ele_list = root.findall(".//VlanIf")
    assert len(vlan_ele_list) == 2

    # VlanIf IDs should be 13 and 14
    obj_id_list = []
    for vlan_ele in vlan_ele_list:
        obj_id_list.append(vlan_ele.get("id"))
    assert len(obj_id_list) == 2
    assert "13" in obj_id_list
    assert "14" in obj_id_list

    # Ipv4If should be 11
    ipv4if_ele = root.find(".//Ipv4If")
    assert ipv4if_ele is not None
    assert ipv4if_ele.get("id") == "11"

    stacked_on_rel = None
    user_tag_rel = None
    for child in ipv4if_ele:
        if child.tag != "Relation":
            continue
        if child.get("type") == "StackedOnEndpoint":
            stacked_on_rel = child
        elif child.get("type") == "UserTag":
            user_tag_rel = child
    assert stacked_on_rel is not None
    assert user_tag_rel is not None

    # Should be stacked on 14 (one of the VlanIfs)
    assert stacked_on_rel.get("target") == "14"

    # Should be tagged with 8 (TopLevelIf tag)
    assert user_tag_rel.get("target") == "8"

    # Check the tag
    tag_ele_list = root.findall(".//Tag")
    assert len(tag_ele_list) == 5
    top_level_if_tag = None
    for tag_ele in tag_ele_list:
        if tag_ele.get("Name") == "TopLevelIf":
            top_level_if_tag = tag_ele
            break
    assert top_level_if_tag is not None
    assert top_level_if_tag.get("id") == "8"
def run(
    StmTemplateConfig, SrcTagList, TargetTagList, TagPrefix, TemplateXml, TemplateXmlFileName, EnableLoadFromFileName
):
    plLogger = PLLogger.GetLogger("methodology")
    plLogger.LogDebug("run MergeTemplateCommand")

    hnd_reg = CHandleRegistry.Instance()
    temp_conf = hnd_reg.Find(StmTemplateConfig)

    if temp_conf is None:
        plLogger.LogError("ERROR: Failed to find a valid StmTemplateConfig")
        return False

    # Get the template XML (target XML) and renormalize the IDs
    template = temp_conf.Get("TemplateXml")
    target_root = etree.fromstring(template)

    # FIXME:
    # RENORMALIZE must ALSO remove invalid relations and potentially invalid
    # property values (for type handle).  Otherwise, renumbering the objects
    # may end up relating things that shouldn't be related.
    # Example:
    # <Relation type="DefaultSelection" target="15"/>
    #  Object with ID 15 doesn't exist if the XML was stripped incorrectly
    #  but it will probably exist when the objects are renumbered.

    xml_utils.renormalize_xml_obj_ids(target_root)

    # Get the new current object ID (1 + maximum object ID)
    curr_id = xml_utils.get_max_object_id(target_root, 0) + 1

    # Find the target elements
    target_ele_list = find_tagged_filtered_elements(target_root, TargetTagList)
    if len(target_ele_list) < 1:
        plLogger.LogError(
            "Found no target elements in the " + "StmTemplateConfig in to which the " + "new XML should be merged into."
        )
        return False

    # Get the source XML
    merge_xml = None
    if not EnableLoadFromFileName:
        merge_xml = TemplateXml
        if merge_xml == "":
            plLogger.LogError("No valid XML Template String defined.")
            return False
    elif TemplateXmlFileName != "":
        merge_xml = xml_utils.load_xml_from_file(TemplateXmlFileName)
        if merge_xml is None:
            plLogger.LogError("No valid XML Template File defined.")
            return False

    source_root = etree.fromstring(merge_xml)

    # Find the source elements
    src_ele_list = find_tagged_filtered_elements(source_root, SrcTagList)
    if len(src_ele_list) < 1:
        plLogger.LogError(
            "Found no source elements in the input XML "
            + "from which XML should be copied into the "
            + "StmTemplateConfig from."
        )
        return False

    # FIXME:
    # Source elements should not contain other source elements
    # Need to prevent this from happening

    # Find the Tags object in the target XML
    target_tags_ele = target_root.find(".//Tags")
    if target_tags_ele is None:
        plLogger.LogError("ERROR: Target XML has no Tags element!")
        return False

    # Merge Tag objects from the XML source if necessary
    copy_src_tag_list = []
    for src_ele in src_ele_list:
        tag_ele_list = src_ele.findall(".//Relation")
        for tag_ele in tag_ele_list:
            if tag_ele.get("type") == "UserTag":
                copy_src_tag_list.append(tag_ele.get("target"))
    copy_src_tag_list = set(copy_src_tag_list)
    plLogger.LogDebug("copy_src_tag_list: " + str(copy_src_tag_list))

    # Find the tags in the source XML and extract the ones of interest
    copy_src_tag_ele_list = []
    src_tag_ele_list = source_root.findall(".//Tag")
    for src_tag_ele in src_tag_ele_list:
        if src_tag_ele.get("id") in copy_src_tag_list:
            copy_src_tag_ele_list.append(src_tag_ele)
    plLogger.LogDebug("copy_src_tag_ele_list: " + str(copy_src_tag_ele_list))
    # Store the mappings between the old ID, new ID, old and new name
    # These will be used later to update relations and
    # StmPropertyModifier objects
    new_src_tag_id_map = {}
    new_src_tag_name_map = {}
    for src_tag_ele in copy_src_tag_ele_list:
        src_tag_ele_copy = xml_utils.get_etree_copy(src_tag_ele)

        # Update the tag's Name if the TagPrefix is defined
        old_name = src_tag_ele_copy.get("Name")
        if TagPrefix != "":
            new_name = TagPrefix + old_name
        else:
            new_name = old_name
        src_tag_ele_copy.set("Name", new_name)

        # Update the ID
        old_id = src_tag_ele_copy.get("id")
        src_tag_ele_copy.set("id", str(curr_id))

        # Store both for use later
        new_src_tag_id_map[old_id] = curr_id
        new_src_tag_name_map[old_name] = new_name

        target_tags_ele.append(src_tag_ele_copy)
        xml_utils.create_relation_element(target_tags_ele, "UserTag", str(curr_id))

        curr_id = curr_id + 1
    plLogger.LogDebug("new_src_tag_id_map: " + str(new_src_tag_id_map))
    plLogger.LogDebug("new_src_tag_name_map: " + str(new_src_tag_name_map))

    # Merge the sources into the target(s)
    for target_ele in target_ele_list:
        for src_ele in src_ele_list:
            plLogger.LogInfo("merging " + str(src_ele) + " into " + str(target_ele))
            # Renumber the object IDs
            src_ele_copy = xml_utils.get_etree_copy(src_ele)
            xml_utils.renormalize_xml_obj_ids(src_ele_copy, start_id=curr_id)

            # Update the tag IDs (should have been lost
            # unless src_ele is Tags...which is not allowed).
            update_relation_targets(src_ele_copy, new_src_tag_id_map)

            # Update the StmPropertyModifiers
            update_property_modifier_tag_names(src_ele_copy, new_src_tag_name_map)

            plLogger.LogInfo(" src_ele_copy: " + etree.tostring(src_ele_copy))
            target_ele.append(src_ele_copy)

            # Increment the current max ID
            curr_id = curr_id + 1

    # Clean up the invalid handles
    xml_utils.remove_invalid_stc_handles(target_root)
    xml_value = etree.tostring(target_root)
    plLogger.LogInfo("FINAL xml_value: " + etree.tostring(target_root))
    temp_conf.Set("TemplateXml", xml_value)

    return True