## @file
# This is an XML API that uses a syntax similar to XPath, but it is written in
# standard python so that no extra python packages are required to use it.
#
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
#

'''
XmlRoutines
'''

##
# Import Modules
#
import xml.dom.minidom
import re
import codecs
from Logger.ToolError import PARSER_ERROR
import Logger.Log as Logger

## Create a element of XML
#
# @param Name
# @param String
# @param NodeList
# @param AttributeList
#
def CreateXmlElement(Name, String, NodeList, AttributeList):
    Doc = xml.dom.minidom.Document()
    Element = Doc.createElement(Name)
    if String != '' and String is not None:
        Element.appendChild(Doc.createTextNode(String))

    for Item in NodeList:
        if isinstance(Item, type([])):
            Key = Item[0]
            Value = Item[1]
            if Key != '' and Key is not None and Value != '' and Value is not None:
                Node = Doc.createElement(Key)
                Node.appendChild(Doc.createTextNode(Value))
                Element.appendChild(Node)
        else:
            Element.appendChild(Item)
    for Item in AttributeList:
        Key = Item[0]
        Value = Item[1]
        if Key != '' and Key is not None and Value != '' and Value is not None:
            Element.setAttribute(Key, Value)

    return Element

## Get a list of XML nodes using XPath style syntax.
#
# Return a list of XML DOM nodes from the root Dom specified by XPath String.
# If the input Dom or String is not valid, then an empty list is returned.
#
# @param  Dom                The root XML DOM node.
# @param  String             A XPath style path.
#
def XmlList(Dom, String):
    if String is None or String == "" or Dom is None or Dom == "":
        return []
    if Dom.nodeType == Dom.DOCUMENT_NODE:
        Dom = Dom.documentElement
    if String[0] == "/":
        String = String[1:]
    TagList = String.split('/')
    Nodes = [Dom]
    Index = 0
    End = len(TagList) - 1
    while Index <= End:
        ChildNodes = []
        for Node in Nodes:
            if Node.nodeType == Node.ELEMENT_NODE and Node.tagName == \
            TagList[Index]:
                if Index < End:
                    ChildNodes.extend(Node.childNodes)
                else:
                    ChildNodes.append(Node)
        Nodes = ChildNodes
        ChildNodes = []
        Index += 1

    return Nodes


## Get a single XML node using XPath style syntax.
#
# Return a single XML DOM node from the root Dom specified by XPath String.
# If the input Dom or String is not valid, then an empty string is returned.
#
# @param  Dom                The root XML DOM node.
# @param  String             A XPath style path.
#
def XmlNode(Dom, String):
    if String is None or String == ""  or Dom is None or Dom == "":
        return None
    if Dom.nodeType == Dom.DOCUMENT_NODE:
        Dom = Dom.documentElement
    if String[0] == "/":
        String = String[1:]
    TagList = String.split('/')
    Index = 0
    End = len(TagList) - 1
    ChildNodes = [Dom]
    while Index <= End:
        for Node in ChildNodes:
            if Node.nodeType == Node.ELEMENT_NODE and \
               Node.tagName == TagList[Index]:
                if Index < End:
                    ChildNodes = Node.childNodes
                else:
                    return Node
                break
        Index += 1
    return None


## Get a single XML element using XPath style syntax.
#
# Return a single XML element from the root Dom specified by XPath String.
# If the input Dom or String is not valid, then an empty string is returned.
#
# @param  Dom                The root XML DOM object.
# @param  Strin              A XPath style path.
#
def XmlElement(Dom, String):
    try:
        return XmlNode(Dom, String).firstChild.data.strip()
    except BaseException:
        return ""

## Get a single XML element using XPath style syntax.
#
# Similar with XmlElement, but do not strip all the leading and tailing space
# and newline, instead just remove the newline and spaces introduced by
# toprettyxml()
#
# @param  Dom                The root XML DOM object.
# @param  Strin              A XPath style path.
#
def XmlElement2(Dom, String):
    try:
        HelpStr = XmlNode(Dom, String).firstChild.data
        gRemovePrettyRe = re.compile(r"""(?:(\n *)  )(.*)\1""", re.DOTALL)
        HelpStr = re.sub(gRemovePrettyRe, r"\2", HelpStr)
        return HelpStr
    except BaseException:
        return ""


## Get a single XML element of the current node.
#
# Return a single XML element specified by the current root Dom.
# If the input Dom is not valid, then an empty string is returned.
#
# @param  Dom                The root XML DOM object.
#
def XmlElementData(Dom):
    try:
        return Dom.firstChild.data.strip()
    except BaseException:
        return ""


## Get a list of XML elements using XPath style syntax.
#
# Return a list of XML elements from the root Dom specified by XPath String.
# If the input Dom or String is not valid, then an empty list is returned.
#
# @param  Dom                The root XML DOM object.
# @param  String             A XPath style path.
#
def XmlElementList(Dom, String):
    return list(map(XmlElementData, XmlList(Dom, String)))


## Get the XML attribute of the current node.
#
# Return a single XML attribute named Attribute from the current root Dom.
# If the input Dom or Attribute is not valid, then an empty string is returned.
#
# @param  Dom                The root XML DOM object.
# @param  Attribute          The name of Attribute.
#
def XmlAttribute(Dom, Attribute):
    try:
        return Dom.getAttribute(Attribute)
    except BaseException:
        return ''


## Get the XML node name of the current node.
#
# Return a single XML node name from the current root Dom.
# If the input Dom is not valid, then an empty string is returned.
#
# @param  Dom                The root XML DOM object.
#
def XmlNodeName(Dom):
    try:
        return Dom.nodeName.strip()
    except BaseException:
        return ''

## Parse an XML file.
#
# Parse the input XML file named FileName and return a XML DOM it stands for.
# If the input File is not a valid XML file, then an empty string is returned.
#
# @param  FileName           The XML file name.
#
def XmlParseFile(FileName):
    try:
        XmlFile = codecs.open(FileName, 'rb')
        Dom = xml.dom.minidom.parse(XmlFile)
        XmlFile.close()
        return Dom
    except BaseException as XExcept:
        XmlFile.close()
        Logger.Error('\nUPT', PARSER_ERROR, XExcept, File=FileName, RaiseError=True)
