# -*- coding: utf-8 -*-

# 2015 Calculate Ltd. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.


from lxml import etree as ET
from copy import deepcopy


def display_xml(xml):
    print(xml_to_str(xml))

def xml_to_str(xml):
    return str(ET.tostring(xml, pretty_print=True), encoding="UTF-8")

def str_to_xml_doc(text):
    return ET.XML(bytes(bytearray(text, encoding="UTF-8")))

#backwards compat
class xpath():
    @staticmethod
    def Evaluate(xpath, xml):
        f = ET.XPath(xpath)
        return f(xml)

#can't add methods to Cython lib.
#have to do this the ugly way
def firstChild(element):
    if(element.text):
        return element
    if(len(element) == 0):
        return None
    return element[0]

def insertBefore(elem, new_child, ref_child):
    
    child_parent = new_child.getparent()
    if child_parent is not None:
        child_parent.remove(new_child)
    if(ref_child is None):
        elem.append(new_child)
    ref_child.addprevious(new_child)
    return new_child

class xmlShare():
    """Общий класс для объектов XML, наследуем
    """

    def _createElement(self, doc, tag, text="", attributes=None):
        """Создание нового XML элемента"""
        if not isinstance(attributes, dict):
            attributes = {}
        element = ET.Element(tag, attributes)
        if text:
            element.text = text
        return element


class xmlNode(xmlShare):
    """Класс для создания нод без аттрибутов
    """

    def __init__(self):
        self.node = None

    def createNode(self, doc, tag, text=""):
        """Создает XML элемент без аттрибутов"""
        self.node = self._createElement(doc, tag, text)
        return self.node

    def getNode(self):
        return self.node

    def toxml(self, pretty_print=True):
        if(self.node is not None):
            return ET.tostring(self.node, encoding="UTF-8", pretty_print=pretty_print)

class xmlCaption():
    """Класс XML заголовок

    """

    def __init__(self):
        # Заголовок области XML нода
        self.caption = False

    def createCaption(self, doc, name, quotes, action=None):
        """Создание заголовка области"""
        tmpNode = xmlNode()
        self.caption = tmpNode.createNode(doc, "caption")
        nameNode = tmpNode.createNode(doc, "name", name)
        self.caption.append(nameNode)
        if action:
            actNode = tmpNode.createNode(doc, "action", action)
            self.caption.append(actNode)
        for q in quotes:
            quoteNode = tmpNode.createNode(doc, "quote", q)
            self.caption.append(quoteNode)
        return self.caption

    def getCaption(self):
        """Выдает XML ноду заголовка области"""
        return self.caption


class xmlField(xmlShare):
    """Класс для работы с XML полем

    """

    def __init__(self):
        # XML нода поле
        self.field = None

    def createField(self, doc, typeField, quotes, name="",
                    values=(), action=None):
        """Cоздание XML ноды поле"""
        self.field = self._createElement(doc, "field", "", {"type": typeField})
        if name:
            nameNode = self._createElement(doc, "name", name)
            self.field.append(nameNode)
        for v in values:
            valueNode = self._createElement(doc, "value", v)
            self.field.append(valueNode)
        if action:
            actNode = self._createElement(doc, "action", action)
            self.field.append(actNode)
        for q in quotes:
            quoteNode = self._createElement(doc, "quote", q)
            self.field.append(quoteNode)
        return self.field

    def toxml(self, pretty_print=True):
        if(self.field is not None):
            return ET.tostring(self.field, encoding="UTF-8", pretty_print=pretty_print)


class xmlFields():
    """Класс, в котором находится список ХМL нод field

    """

    def __init__(self):
        self.fields = []

    def appendField(self, field):
        """Добавить XML ноду field"""
        self.fields.append(field)
        return self.fields

    def getFields(self):
        """Выдать список XML нод"""
        return self.fields


class xmlArea():
    """Класс для работы с XML областью

    """

    def __init__(self):
        # Область
        self.area = None

    def createArea(self, doc, xmlCaption, xmlFields):
        """Создание XML области"""
        tmpNode = xmlNode()
        self.area = tmpNode.createNode(doc, "area")
        if xmlCaption and xmlCaption.getCaption() is not None:
            self.area.append(xmlCaption.getCaption())
        if xmlFields:
            fields = xmlFields.getFields()
            for field in fields:
                self.area.append(field)
        return self.area


class xmlDoc():
    """Класс для работы с XML документом

    """

    def __init__(self):
        # документ
        self.doc = None
        # главная нода
        self.root = None
        # тело документа
        self.body = None
        # Заголовок области - временный (в реальности один объект заголовок)
        self.tmpCaption = None
        # Поля - временные (в реальности один объект поля)
        self.tmpFields = None
        # Разделитель областей - по умолчанию перевод строки "\n"
        self.sepAreas = None
        # Разделитель разделенных списков - по умолчанию перевод строки "\n"
        # self.sepSplitFields  = False

    def toxml(self, pretty_print=True):
        if(self.doc is not None):
            return ET.tostring(self.doc, encoding="UTF-8", pretty_print=pretty_print)

    def createDoc(self, typeDoc, version):
        """Создание нового документа новый документ"""
        docTxt = ('<?xml version="1.0" encoding="UTF-8"?><cxmlconf><head>'
                  '<ver>{version}</ver>'
                  '<format>{type_doc}</format>'
                  '</head><body></body></cxmlconf>'.format(version=version,
                                                           type_doc=typeDoc))
        # self.doc = minidom.parseString(docTxt)
        # self.root = self.doc.documentElement
        # self.body = xpath.Evaluate('child::body', self.root)[0]
        
        self.doc = str_to_xml_doc(docTxt)
        self.root = self.doc
        self.body = xpath.Evaluate('child::body', self.root)[0]
        # установка разделителя областей
        self.sepAreas = self.createField("br", [], "", [], False, False)
        # установка разделителя областей разделенных списков
        # self.sepSplitFields  = self.createField("br",[],"",[],False,False)
        return self.doc

    def addField(self, field):
        """Добавляет поле во временный список

        Из этого списка в дальнейшем формируется XML область
        """
        if not self.tmpFields:
            self.tmpFields = xmlFields()
        self.tmpFields.appendField(field)

    def createCaption(self, name, quotes, action=False):
        """Cоздает заголовок области

        Помещает заголовок в временный артибут
        Используется при создании области
        """
        self.tmpCaption = xmlCaption()
        return self.tmpCaption.createCaption(self.doc, name, quotes, action)

    def createField(self, typeField, quotes=(), name="",
                    values=(), action=False, addTmpField=True):
        """Cоздает поле

        Если установлена переменнная addTmpField
        добавляет поле во временный список
        """
        fieldObj = xmlField()
        field = fieldObj.createField(self.doc, typeField, quotes, name,
                                     values, action)
        if addTmpField:
            self.addField(field)
        return field

    def clearTmpFields(self):
        """Очищает временный список"""
        self.tmpFields = False

    def createArea(self):
        """Cоздает область

        Область создается на основании временного атрибута и временного списка
        """
        areaObj = xmlArea()
        area = areaObj.createArea(self.doc, self.tmpCaption, self.tmpFields)
        self.clearTmpCaptionAndFields()
        return area

    def clearTmpCaptionAndFields(self):
        """Очищает временный аттрибут и временный список"""
        self.tmpCaption = False
        self.tmpFields = False

    def getNodeRoot(self):
        """Выдает корневую ноду"""
        return self.root

    def getNodeBody(self):
        """Выдает ноду body"""
        return self.body

    def setActionField(self, xmlField, actionTxt):
        """Устанавливает свойство action для XML поля"""
        xmlActions = xpath.Evaluate('child::action', xmlField)
        if xmlActions and firstChild(xmlActions[0]) is not None:
            firstChild(xmlActions[0]).text = actionTxt
        else:
            nodeObj = xmlNode()
            newNode = nodeObj.createNode(self.doc, "action", actionTxt)
            xmlField.append(newNode)

    def setActionArea(self, xmlArea, actionTxt):
        """Устанавливает свойство action для XML области"""
        xmlActions = xpath.Evaluate('child::caption/action', xmlArea)
        xmlCaptions = xpath.Evaluate('child::caption', xmlArea)
        if xmlActions and firstChild(xmlActions[0]) is not None:
            firstChild(xmlActions[0]).text = actionTxt
        else:
            if xmlCaptions:
                nodeObj = xmlNode()
                newNode = nodeObj.createNode(self.doc, "action", actionTxt)
                xmlCaptions[0].append(newNode)

    def joinField(self, xmlArea, xmlNewField):
        """Объединяет XML ноду область и XML ноду поле"""
        newNameField = self.getNameField(xmlNewField)
        if not newNameField or not newNameField.strip():
            return False
        fieldsOldComp = xpath.Evaluate("child::field[child::name='%s']" \
                                       % newNameField, xmlArea)
        # Если поле не найдено добавляем его
        typeNewField = self.getTypeField(xmlNewField)
        if not fieldsOldComp and typeNewField != "seplist":
            if self.getActionField(xmlNewField) != "drop":
                self.setActionField(xmlNewField, "append")
                xmlArea.append(xmlNewField)
            return True
        newFieldsAction = self.getActionField(xmlNewField)
        newValues = self.getFieldValues(xmlNewField)
        flagCompare = True

        for nodeFieldOld in fieldsOldComp:
            if newFieldsAction == "drop":
                if nodeFieldOld.getnext() is not None and \
                                self.getTypeField(
                                    nodeFieldOld.getnext()) == "br":
                    xmlArea.remove(nodeFieldOld.getnext())
                elif nodeFieldOld.getprevious() is not None and \
                                self.getTypeField(
                                    nodeFieldOld.getprevious()) == "br":
                    xmlArea.remove(nodeFieldOld.getprevious())
                xmlArea.remove(nodeFieldOld)
                continue
            oldValues = self.getFieldValues(nodeFieldOld)
            # Сравнение значений переменной шаблона и файла
            if set(newValues) != set(oldValues):
                flagCompare = False
        if self.getActionField(xmlNewField) == "drop":
            return True
        appSplLst = []
        insSplLst = []
        if typeNewField == "seplist":
            if fieldsOldComp:
                xmlOldField = fieldsOldComp[-1]
            else:
                xmlOldField = None
            seplistNewXML = self.getSepListToField(xmlNewField)
            if seplistNewXML:
                for nodeSeplist in seplistNewXML:
                    if self.getActionField(nodeSeplist) != "drop":
                        if newFieldsAction == "join":
                            flagCompareSeplist = False
                            newValues = self.getFieldValues(nodeSeplist)
                            for nodeFieldOld in fieldsOldComp:
                                oldValues = self.getFieldValues(nodeFieldOld)
                                for newValue in newValues:
                                    if newValue in oldValues:
                                        flagCompareSeplist = True
                                        break
                            if not flagCompareSeplist:
                                nextNode = xmlOldField.getnext()
                                newInsNode = deepcopy(nodeSeplist)
                                self.setActionField(newInsNode, "append")

                                if nextNode is not None:
                                    appSplLst.append((newInsNode,
                                                      nextNode,
                                                      "insert"))
                                else:
                                    appSplLst.append((newInsNode,
                                                      False,
                                                      "append"))
                        else:
                            newInsNode = deepcopy(nodeSeplist)
                            if self.getActionField(newInsNode) == "join":
                                self.setActionField(newInsNode, "append")
                            if xmlOldField is not None:
                                insSplLst.append((newInsNode,
                                                  xmlOldField,
                                                  "insert"))
                            else:
                                insSplLst.append((newInsNode,
                                                  False,
                                                  "append"))

                                # xmlArea.insertBefore(\
                                # nodeSeplist.cloneNode(True),
                                # xmlOldField)

                        parentNode = nodeSeplist.getparent()
                        parentNode.remove(nodeSeplist)

                insNodesRepl = []
                for newNode, nxtNode, app in insSplLst:
                    flagCompareSeplist = False
                    newValues = self.getFieldValues(newNode)
                    for nodeRepl, nxtNode_, app_ in insNodesRepl:
                        nxtNode, app = nxtNode_, app_
                        oldValues = self.getFieldValues(nodeRepl)
                        for newValue in newValues:
                            if newValue in oldValues:
                                flagCompareSeplist = True
                                break
                    if not flagCompareSeplist:
                        if xmlOldField is not None:
                            insNodesRepl.append((newNode, nxtNode, app))

                for newNode, nxtNode, app in insNodesRepl:
                    if app == "insert":
                        insertBefore(xmlArea, newNode, nxtNode)
                    elif app == "append":
                        xmlArea.append(newNode)
                if xmlOldField is not None:
                    parentNode = xmlOldField.getparent() 
                    if parentNode is not None and newFieldsAction != "join":
                        parentNode.remove(xmlOldField)

        for newNode, nxtNode, app in appSplLst:
            if app == "insert":
                insertBefore(xmlArea, newNode, nxtNode)
            elif app == "append":
                xmlArea.append(newNode)

        if not flagCompare and typeNewField != "seplist":
            # Устанавливаем action=replace
            self.setActionField(xmlNewField, "replace")
            # Если параметры поля не сходятся заменяем поле
            xmlArea.replace(fieldsOldComp[-1], deepcopy(xmlNewField))

        if newFieldsAction == "join":
            fieldsOldRemove = []
        else:
            fieldsOldRemove = fieldsOldComp[:-1]

        for nodeFieldOld in fieldsOldRemove:
            actionOldNode = self.getActionField(nodeFieldOld)
            if actionOldNode == "insert" or actionOldNode == "append":
                pass
            else:
                if nodeFieldOld.getnext() is not None and \
                                self.getTypeField(
                                    nodeFieldOld.getnext()) == "br":
                    xmlArea.remove(nodeFieldOld.getnext())
                xmlArea.remove(nodeFieldOld)
        return True

    def getSepListToField(self, xmlField):
        """Выдает элементы распределенного массива

        Область предок поля, в этой области ищутся
        элементы распределенного массива
        """
        nameField = self.getNameField(xmlField)
        if not nameField:
            return []
        parentNode = xmlField.getparent()
        if parentNode is not None:
            fieldsVal = xpath.Evaluate(
                "child::field[attribute::type='seplist'][child::name='%s'] " \
                % nameField, parentNode)
            return fieldsVal
        else:
            return []

    def removeComment(self, xmlArea):
        """Удаляет комментарии в XML области"""
        fieldNodes = xpath.Evaluate('descendant::field', xmlArea)
        for fieldNode in fieldNodes:
            if "type" in fieldNode.keys():
                if fieldNode.get("type") == "comment" or \
                                fieldNode.get("type") == "br":
                    parentNode = fieldNode.getparent()
                    parentNode.remove(fieldNode)
                else:
                    if self.getActionField(fieldNode) == "drop":
                        pass
                    elif self.getActionField(fieldNode) == "join":
                        pass
                    else:
                        self.setActionField(fieldNode, "append")

    def joinBody(self, baseBody, newBody):
        """Объединяет две области Body"""
        newFields = xpath.Evaluate('child::field', newBody)
        xmlNewAreas = xpath.Evaluate('child::area', newBody)
        for i, xmlNewArea in enumerate(xmlNewAreas):
            self.joinArea(baseBody, xmlNewArea)
        joinNewFields = xpath.Evaluate("child::field[child::action='join']",
                                       newBody)
        self.addNewFielsOldArea(newFields, joinNewFields, baseBody)

    def getRemoveNodeSepList(self, removeNodesDict, baseNode, xmNewlField):
        """Находит элементы разделенного списка

        Параметры:
        removeNodesDict - Cловарь удаляемых полей разделенного списка
                          формируется программой
        baseNode        - Нода в которой идет поиск
        xmNewlField     - Нода field которая проверяется на принадлежность
                          к разделенному списку
        """
        flagNewNodeSeplist = False
        if self.getTypeField(xmNewlField) == "seplist":
            flagNewNodeSeplist = True
        nameNewField = self.getNameField(xmNewlField)
        if nameNewField:
            if nameNewField in removeNodesDict:
                return removeNodesDict[nameNewField]
            else:
                oldFields = xpath.Evaluate('child::field', baseNode)
                removeNodes = []
                lenOldFields = len(oldFields)
                for i in range(lenOldFields):
                    oldNode = oldFields[i]
                    flagSep = self.getTypeField(oldNode) == "seplist"
                    if flagNewNodeSeplist:
                        flagSep = True
                    if flagSep and \
                                    nameNewField == self.getNameField(oldNode):
                        removeNodes.append(oldNode)
                        if i + 1 < lenOldFields:
                            nextNode = oldFields[i + 1]
                            if self.getTypeField(nextNode) == "br":
                                removeNodes.append(nextNode)
                removeNodesDict[nameNewField] = removeNodes
                return removeNodes

    def addNewFielsOldArea(self, newFields, joinNewFields, xmlOldArea):
        """Добавляет новые XML поля в область шаблона"""
        removeNodesDict = {}
        notRemoveNodesDict = {}
        for notRemNode in joinNewFields:
            nameField = self.getNameField(notRemNode)
            if nameField not in notRemoveNodesDict:
                notRemoveNodesDict[nameField] = []
                notRemoveNodesDict[nameField].append(notRemNode)
            else:
                notRemoveNodesDict[nameField].append(notRemNode)
        notSepListField = []
        sepListField = []
        for nField in newFields:
            if self.getRemoveNodeSepList(removeNodesDict, xmlOldArea,
                                         nField):
                sepListField.append(nField)
            else:
                if self.getNameField(nField):
                    notSepListField.append(nField)
        for name in notRemoveNodesDict:
            if name in removeNodesDict:
                removeNodesDict[name] = []
        for removeNodes in removeNodesDict.values():
            if removeNodes:
                if self.getTypeField(removeNodes[-1]) == "seplist":
                    removeNodes = removeNodes[:-1]
                else:
                    removeNodes = removeNodes[:-2]

            for removeNode in removeNodes:
                xmlOldArea.remove(removeNode)
        for node in sepListField:
            node.set("type", "seplist")
            if not (self.getActionField(node) == "join" or
                            self.getActionField(node) == "drop"):
                self.setActionField(node, "insert")
            self.joinField(xmlOldArea, node)
        for node in notSepListField:
            if self.getTypeField(node) == "seplist":
                self.setActionField(node, "append")
                xmlOldArea.append(node)
            else:
                self.joinField(xmlOldArea, node)

    def insertBeforeSepAreas(self, xmlArea):
        """Добавляет разделитель областей перед каждой областью"""
        if self.sepAreas is None:
            return False
        areaNodes = xpath.Evaluate('descendant::area', xmlArea)
        for areaNode in areaNodes:
            prevNode = areaNode.getprevious()
            if prevNode is not None:
                parentNode = areaNode.getparent()
                insertBefore(parentNode, deepcopy(self.sepAreas),
                                        areaNode)
        return True

    def getAreaFields(self, nameArea, xmlArea, allVars=False):
        """По имени области выводит названия и значения всех переменных

        поиск ведется только в 1-х потомках области xmlArea
        на выход словарь переменных {имя:значение}
        """
        namesAreaCompareAll = xpath.Evaluate(
            "child::area/child::caption[child::name='%s']" % nameArea,
            xmlArea)
        if not namesAreaCompareAll:
            return False
        dictVar = {}
        for namesAreaCompare in namesAreaCompareAll:
            fields = xpath.Evaluate("child::field/child::name",
                                    namesAreaCompare.getparent())
            for fieldName in fields:
                nodeField = fieldName.getparent()
                fieldValue = xpath.Evaluate("child::value", nodeField)
                name = firstChild(fieldName).text
                value = ""
                if fieldValue and firstChild(fieldValue[0]) is not None:
                    value = firstChild(fieldValue[0]).text
                dictVar[name] = value
            if not allVars:
                break
        return dictVar

    def getAreaFieldValues(self, nameArea, nameField, xmlArea):
        """По имени области и имени переменной выводит значениe переменной

        поиск ведется только в 1-х потомках области xmlArea
        """
        namesAreaComare = xpath.Evaluate(
            "child::area/child::caption[child::name='%s']" % nameArea,
            xmlArea)
        fieldsVal = False
        for areaComp in namesAreaComare:
            fieldsVal = xpath.Evaluate(
                "child::field[child::name='%s'] "
                % nameField, areaComp.getparent())
            if fieldsVal:
                break
        if not fieldsVal:
            return False
        fieldValue = xpath.Evaluate("child::value",
                                    fieldsVal[0])
        if not fieldValue:
            return False
        if firstChild(fieldValue[0]) is not None:
            return firstChild(fieldValue[0]).text
        else:
            return ""

    def getAllAreas(self):
        """Выдает все области"""
        return xpath.Evaluate('descendant::area', self.body)

    def getArea(self, nameArea, xmlArea):
        """По имени области находит области (первый потомок xmlArea)"""
        namesAreaComare = xpath.Evaluate(
            "child::area/child::caption[child::name='%s']" % nameArea,
            xmlArea)
        return [x.getparent() for x in namesAreaComare]


    def joinArea(self, baseNode, xmlNewArea):
        """Объединяет область c областью Body (xmlNewArea c baseNode)"""

        def appendArea(baseNode, xmlNewArea):
            fieldsRemove = xpath.Evaluate(
                "descendant::field[child::action='drop']", xmlNewArea)
            for rmNode in fieldsRemove:
                parentNode = rmNode.getparent()
                parentNode.remove(rmNode)
            captionAreasRemove = xpath.Evaluate(
                "descendant::area/child::caption[child::action='drop']",
                xmlNewArea)
            for rmNodeCapt in captionAreasRemove:
                rmNode = rmNodeCapt.getparent()
                parentNode = rmNode.getparent()
                parentNode.remove(rmNode)
            self.setActionArea(xmlNewArea, "append")
            # Добавляем разделитель областей во вложенные области
            areaNodes = xpath.Evaluate('descendant::area', xmlNewArea)
            for areaNode in areaNodes:
                self.setActionArea(areaNode, "append")
                parentNode = areaNode.getparent()
                insertBefore(parentNode, deepcopy(self.sepAreas),
                                        areaNode)
            baseNode.append(xmlNewArea)
            # Добавляем разделитель областей
            insertBefore(baseNode, deepcopy(self.sepAreas), xmlNewArea)

        nodesNames = xpath.Evaluate('child::area/caption/name', baseNode)
        nodesNewArea = xpath.Evaluate('child::caption/name', xmlNewArea)
        if not nodesNames:
            # Добавляем область
            if nodesNewArea:
                newAreaAction = self.getActionArea(xmlNewArea)
                if not (newAreaAction == "drop" or newAreaAction == "replace"):
                    appendArea(baseNode, xmlNewArea)
            return True
        if not nodesNames or not nodesNewArea:
            return False
        nameArea = ""
        if firstChild(nodesNewArea[0]) is not None:
            nameArea = firstChild(nodesNewArea[0]).text.strip()
        flagFindArea = False
        newAreaAction = None
        baseNodes = []
        for oName in nodesNames:
            newAreaAction = self.getActionArea(xmlNewArea)
            oArea = oName.getparent().getparent()
            oNameTxt = ""
            
            if firstChild(oName) is not None:

                oNameTxt = firstChild(oName).text
            if nameArea == oNameTxt:
                flagFindArea = True
                # При использовании удаления
                if newAreaAction == "drop":
                    prevNode = oName.getparent().getparent().getprevious()
                    removePrevNodes = []
                    while prevNode is not None and self.getTypeField(prevNode) == "br":
                        removePrevNodes.append(prevNode)
                        prevNode = prevNode.getprevious()
                    for removeNode in removePrevNodes:
                        baseNode.remove(removeNode)
                    baseNode.remove(oName.getparent().getparent())
                    continue
                elif newAreaAction == "replace":
                    oldAreaNode = oName.getparent().getparent()
                    newAreaCaption = xpath.Evaluate('child::caption',
                                                    xmlNewArea)[0]
                    oldAreaCaption = xpath.Evaluate('child::caption',
                                                    oldAreaNode)[0]
                    if newAreaCaption is not None and oldAreaCaption is not None:
                        #its was "replace(old, new)" in legacy code, even though
                        #the func takes (new, old). Mistake, or on purpose?
                        #xmlNewArea.replaceChild(oldAreaCaption, newAreaCaption)
                        
                        xmlNewArea.replace(newAreaCaption, oldAreaCaption)
                        self.setActionArea(xmlNewArea, "replace")
                    baseNode.replace(oldAreaNode, xmlNewArea)
                    continue
                baseNodes.append(oName.getparent().getparent())
                newFields = xpath.Evaluate('child::field', xmlNewArea)

                joinNewFields = xpath.Evaluate(
                    "child::field[child::action='join']",
                    xmlNewArea)
                self.addNewFielsOldArea(newFields, joinNewFields, oArea)

        if not flagFindArea:
            # Добавляем область
            if not (newAreaAction == "drop" or newAreaAction == "replace"):
                appendArea(baseNode, xmlNewArea)
        else:
            tmpXmlNewAreas = xpath.Evaluate('child::area', xmlNewArea)
            for na in tmpXmlNewAreas:
                for bn in baseNodes:
                    self.joinArea(bn, na)
        return True

    def joinDoc(self, xmlNewDoc):
        """Объединяет два документа"""
        newRootNode = xmlNewDoc.getroottree().getroot()
        newBodyNode = xpath.Evaluate('child::body', newRootNode)[0]
        # newImportBodyNode = self.doc.importNode(newBodyNode, True)
        newImportBodyNode = deepcopy(newBodyNode)
        # Перед объединение области с документом
        # удаляем комментарии
        
        self.removeComment(newImportBodyNode)
        self.joinBody(self.body, newImportBodyNode)
        # расставляем BR
        self.insertBRtoBody(self.body)

    def getQuoteField(self, xmlField):
        """Выдает текст из поля"""
        xmlQuotes = xpath.Evaluate('child::quote', xmlField)
        br = ""
        if (xmlField.get("type") == "br"):
            br = "\n"
        if xmlQuotes:
            field = xmlQuotes[0]
            if firstChild(field) is not None:
                return firstChild(field).text + br
        return "" + br

    def getFieldsArea(self, xmlArea):
        """Выдает потомков XML области"""
        xmlFields = []
        childNodes = list(xmlArea)
        for node in childNodes:
            # if node.nodeType == node.ELEMENT_NODE:
            if node.tag == "area" or node.tag == "field":
                xmlFields.append(node)
        return xmlFields

    def getTypeField(self, xmlField):
        """Выдает тип поля"""
        return xmlField.get("type")

    def getNameField(self, xmlField):
        """Выдает имя поля"""
        xmlNameFields = xpath.Evaluate('child::name', xmlField)
        if xmlNameFields and firstChild(xmlNameFields[0]) is not None:
            return firstChild(xmlNameFields[0]).text
        else:
            return False

    def getNameArea(self, xmlArea):
        """Выдает имя области"""
        xmlNameAreas = xpath.Evaluate('child::caption/name', xmlArea)
        if xmlNameAreas and firstChild(xmlNameAreas[0]) is not None:
            return firstChild(xmlNameAreas[0]).text
        else:
            return False

    def xmlToText(self, xmlAreas, text):
        """Преобразует список XML областей в текст"""

        def getQuotesArea(xmlArea):
            quotes = []
            xmlQuotes = xpath.Evaluate('child::caption/quote', xmlArea)
            for node in xmlQuotes:
                if firstChild(node) is not None:
                    quotes.append(firstChild(node).text)
            if len(quotes) == 0:
                quotes.append("")
                quotes.append("")
            elif len(quotes) == 1:
                quotes.append("")
            return quotes

        for i in xmlAreas:
            if i.tag == "area":
                quotesI = getQuotesArea(i)
                startAreaI = quotesI[0]
                endAreaI = quotesI[1]
                text.append(startAreaI)
                xmlFieldsI = self.getFieldsArea(i)
                for f in xmlFieldsI:
                    if f.tag == "area":
                        quotesF = getQuotesArea(f)
                        startAreaF = quotesF[0]
                        endAreaF = quotesF[1]
                        text.append(startAreaF)
                        xmlFieldsF = self.getFieldsArea(f)
                        self.xmlToText(xmlFieldsF, text)
                        text.append(endAreaF)
                    else:
                        fieldF = self.getQuoteField(f)
                        text.append(fieldF)
                text.append(endAreaI)
            else:
                fieldI = self.getQuoteField(i)
                text.append(fieldI)

    def getActionField(self, xmlField):
        """Выдает свойство action XML поля"""
        xmlActions = xpath.Evaluate('child::action', xmlField)
        if xmlActions and firstChild(xmlActions[0]) is not None:
            return firstChild(xmlActions[0]).text
        else:
            return False

    def getFieldValues(self, xmlField):
        """Выдает значения XML поля в виде массива"""
        vals = []
        xmlValues = xpath.Evaluate('child::value', xmlField)
        if xmlValues:
            for node in xmlValues:
                if firstChild(node) is not None:
                    vals.append(firstChild(node).text)
        return vals

    def getActionArea(self, xmlArea):
        """Выдает свойство action XML области"""
        xmlActions = xpath.Evaluate('child::caption/action', xmlArea)
        if xmlActions and firstChild(xmlActions[0]) is not None:
            return firstChild(xmlActions[0]).text
        else:
            return False

    def delActionNodeArea(self, xmlArea):
        """Удаляет свойство action XML области"""
        xmlActions = xpath.Evaluate('child::caption/action', xmlArea)
        if xmlActions and firstChild(xmlActions[0]) is not None:
            parentNode = xmlActions[0].getparent()
            parentNode.remove(xmlActions[0])
            return True
        else:
            return False

    def delActionNodeField(self, xmlField):
        """Удаляет свойство action XML поля"""
        xmlActions = xpath.Evaluate('child::action', xmlField)
        if xmlActions and firstChild(xmlActions[0]) is not None:
            parentNode = xmlActions[0].getparent()
            parentNode.remove(xmlActions[0])
            return True
        else:
            return False

    # Создает распределенные списки
    def postParserListSeplist(self, xmlArea):
        """Создает распределенные списки из элементов области"""
        # Потомки
        childNodes = self.getFieldsArea(xmlArea)
        # содержит списки нод полей с одинаковыми именами в одной области
        fieldsSeplist = {}
        for node in childNodes:
            if node.tag == "area":
                self.postParserListSeplist(node)
            else:
                fieldName = False
                xmlFieldNameNodes = xpath.Evaluate('child::name', node)
                if xmlFieldNameNodes and firstChild(xmlFieldNameNodes[0]) is not None:
                    fieldName = firstChild(xmlFieldNameNodes[0]).text
                if fieldName:
                    if fieldName in fieldsSeplist:
                        fieldsSeplist[fieldName].append(node)
                    else:
                        fieldsSeplist[fieldName] = []
                        fieldsSeplist[fieldName].append(node)
        for listNodes in fieldsSeplist.values():
            if len(listNodes) > 1:
                for node in listNodes:
                    node.set("type", "seplist")

    def insertBRtoBody(self, xmlArea):
        """Добавляет необходимые переводы строк
        """
        # Потомки
        childNodes = self.getFieldsArea(xmlArea)
        # нода BR
        fieldXMLBr = self.createField("br", [], "", [], False, False)

        # Предыдущая нода
        lastNode = None
        lenChildNodes = len(childNodes)
        for i in range(lenChildNodes):
            node = childNodes[i]
            lastTmpNode = node
            # Нода area
            if node.tag == "area":
                if self.getActionArea(node) == "append" or \
                                self.getActionArea(node) == "join":
                    self.delActionNodeArea(node)
                    if lastNode is not None and lastNode.get("type") == "br" or \
                        lastNode is not None and lastNode.get("type") == "comment":
                        indNext = i + 1
                        if indNext == lenChildNodes:
                            xmlArea.append(deepcopy(fieldXMLBr))
                        else:
                            nextNode = childNodes[indNext]
                            lastTmpNode = insertBefore(xmlArea,
                                deepcopy(fieldXMLBr),
                                nextNode)
                    else:
                        insertBefore(xmlArea, deepcopy(fieldXMLBr),
                                             node)
                self.insertBRtoBody(node)
            # Нода field
            else:
                if self.getActionField(node) == "append" or \
                                self.getActionField(node) == "join":
                    self.delActionNodeField(node)
                    if lastNode is not None and lastNode.get("type") == "br" or \
                                    lastNode is not None and lastNode.get("type") == "comment":
                        indNext = i + 1
                        if indNext == lenChildNodes:
                            xmlArea.append(deepcopy(fieldXMLBr))
                        else:
                            nextNode = childNodes[indNext]
                            lastTmpNode = insertBefore(xmlArea,
                                deepcopy(fieldXMLBr),
                                nextNode)
                    else:
                        insertBefore(xmlArea, deepcopy(fieldXMLBr), node)
            lastNode = lastTmpNode

    def postParserList(self):
        """Находит подходящие XML области и делаем из них поля-массивы"""
        xmlAreas = xpath.Evaluate('descendant::area', self.body)
        for xmlArea in xmlAreas:
            flagListXml = True
            fieldValues = []
            xmlFields = xpath.Evaluate('child::field', xmlArea)
            if not xmlFields:
                flagListXml = False
            lenXmlFields = len(xmlFields)
            lenBrArea = 0
            for xmlField in xmlFields:
                xmlNames = xpath.Evaluate('child::name', xmlField)
                xmlVals = xpath.Evaluate('child::value', xmlField)
                # if "type" in xmlField.keys() and \
                #                 xmlField.get("type") == "br":
                if xmlField.get("type") == "br":
                    lenBrArea += 1
                    continue
                if not xmlNames and not xmlVals:
                    flagListXml = False
                    break
                if xmlNames and firstChild(xmlNames[0]) is not None and \
                        firstChild(xmlNames[0]).text:
                    flagListXml = False
                    break
                if not (xmlVals and firstChild(xmlVals[0]) is not None and
                            firstChild(xmlVals[0]).text):
                    flagListXml = False
                    break
                else:
                    fieldValues.append(firstChild(xmlVals[0]).text)

            if lenXmlFields == lenBrArea:
                flagListXml = False
            if flagListXml:
                nameNode = xpath.Evaluate('child::caption/name', xmlArea)[0]
                fieldName = ""
                if firstChild(nameNode) is not None:
                    fieldName = firstChild(nameNode).text
                listArea = []
                self.xmlToText([xmlArea], listArea)
                fieldQuote = "".join(listArea)
                fieldXMLBr = False
                if fieldQuote and fieldQuote[-1] == "\n":
                    fieldQuote = fieldQuote[:-1]
                    fieldXMLBr = self.createField("br", [], "", [], False,
                                                  False)
                fieldXML = self.createField("list",
                                            [fieldQuote],
                                            fieldName, fieldValues,
                                            False, False)
                areaAction = self.getActionArea(xmlArea)
                if areaAction:
                    self.setActionField(fieldXML, areaAction)
                parentNode = xmlArea.getparent()
                insertBefore(parentNode, fieldXML, xmlArea)
                if fieldXMLBr:
                    insertBefore(parentNode, fieldXMLBr, xmlArea)
                parentNode.remove(xmlArea)
