You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
12 KiB
303 lines
12 KiB
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2008-2016 Mir Calculate. 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.
|
|
|
|
import sys
|
|
import time
|
|
import re
|
|
from ..cl_xml import xpath, xml_to_str, str_to_xml_doc
|
|
# import xml.dom.minidom
|
|
from lxml import etree
|
|
from .xml_xfce import xml_xfce
|
|
from ..cl_lang import setLocalTranslate
|
|
from collections.abc import Iterable
|
|
from copy import deepcopy
|
|
|
|
_ = lambda x: x
|
|
setLocalTranslate('cl_lib3', sys.modules[__name__])
|
|
|
|
|
|
class xml_gconf(xml_xfce):
|
|
"""Класс для объединения gconf-xml файлов"""
|
|
# root нода
|
|
rootNode = False
|
|
# body нода
|
|
bodyNode = False
|
|
# Документ
|
|
doc = False
|
|
# Текст шаблона
|
|
text = ""
|
|
# Текущее время в секундах
|
|
currentTime = ""
|
|
# Комментарий
|
|
_comment = ("<!--", "-->")
|
|
# поддерживаемые аттрибуты тега entry. Пример <entry type="int"/>
|
|
supportEntryTypes = ("int", "bool", "float", "string", "list", "pair")
|
|
# регулярное выражения для поиска \t в начале строки (преобразование xml)
|
|
reStartTabs = re.compile("^(\t+)(.*)$")
|
|
|
|
def prepare(self):
|
|
# Создаем пустой объект
|
|
self.docObj = type("_empty_class", (object,), {})()
|
|
# Названия аттрибутов для пустого объекта
|
|
emptyMethods = ["getNodeBody", "removeComment", "insertBRtoBody",
|
|
"insertBeforeSepAreas"]
|
|
# Добавляем необходимые аттрибуты пустому объекту
|
|
for method in emptyMethods:
|
|
setattr(self.docObj, method, self.emptyMethod)
|
|
# Пустой метод (не нужно имя файла для корневой ноды)
|
|
setattr(self, "setNameBodyNode", self.emptyMethod)
|
|
# Создаем XML документ
|
|
self.doc = self.textToXML()
|
|
|
|
def getCurrentTime(self):
|
|
"""Получение текущего времени в секундах"""
|
|
return str(int(time.time()))
|
|
|
|
def textToXML(self):
|
|
"""Создание из текста XML документа
|
|
Храним xml в своем формате
|
|
"""
|
|
if not self.text.strip():
|
|
self.text = '''<?xml version="1.0"?><gconf></gconf>'''
|
|
try:
|
|
self.doc = str_to_xml_doc(self.text)
|
|
except Exception:
|
|
self.setError(_("The template content is not XML"))
|
|
return False
|
|
self.rootNode = self.doc.getroottree().getroot()
|
|
self.bodyNode = self.rootNode #? a mistake, maybe?
|
|
return self.doc
|
|
|
|
def cmpListsNodesEntry(self, listXmlA, listXmlB):
|
|
"""Сравнение содержимого двух списков XML нод"""
|
|
def getTextsNodes(listXml):
|
|
for node in (deepcopy(x)
|
|
for x in listXml):# if x.nodeType == x.ELEMENT_NODE):
|
|
if "mtime" in node.keys():
|
|
# node.removeAttribute("mtime")
|
|
del node.attrib["mtime"]
|
|
text = node.toxml()
|
|
yield text.replace(" ", "").replace("\t", "").replace("\n", "")
|
|
if set(getTextsNodes(listXmlA)) == set(getTextsNodes(listXmlB)):
|
|
return True
|
|
return False
|
|
|
|
def _join(self, xmlNewNode, xmlOldNode, flagRootNode=True, levelNumber=0):
|
|
"""Объединение корневой ноды шаблона и корневой ноды файла"""
|
|
if levelNumber > 1:
|
|
return True
|
|
xmlNode = xmlNewNode
|
|
childNodes = list(xmlNode)
|
|
nextOldNode = xmlOldNode
|
|
# if xmlNode.nodeType == xmlNode.ELEMENT_NODE:
|
|
n = xmlNode
|
|
tagName = n.tag
|
|
nAction = ''
|
|
nType = ''
|
|
nValue = ''
|
|
nSchema = ''
|
|
attrName = ''
|
|
if flagRootNode:
|
|
if not tagName == "gconf":
|
|
self.setError(_("The text is not a valid gconf-XML format \
|
|
(not found '<gconf>...</gconf>')"))
|
|
return False
|
|
flagType = False
|
|
flagValue = False
|
|
flagSchema = False
|
|
else:
|
|
if not tagName == "entry":
|
|
self.setError(_("The text is not a valid gconf-XML format \
|
|
(found '<gconf><%(tag)s>..</%(tag)s></gconf>')") % {'tag': tagName})
|
|
return False
|
|
if "name" not in n.keys():
|
|
self.setError(
|
|
_("Attribute \"name\" not found in the tag entry"))
|
|
return False
|
|
flagType = "type" in n.keys()
|
|
flagValue = False
|
|
flagSchema = "schema" in n.keys()
|
|
if flagSchema:
|
|
nSchema = n.get("schema")
|
|
if not flagType and not flagSchema:
|
|
self.setError(
|
|
_("Attribute \"type\" not found in the tag entry"))
|
|
return False
|
|
nName = n.get("name")
|
|
attrName = "attribute::name='%s'" % nName
|
|
if flagType:
|
|
flagValue = "value" in n.keys()
|
|
nType = n.get("type")
|
|
# Проверка правильности аттрибута type
|
|
if nType not in self.supportEntryTypes:
|
|
self.setError(_("Incorrect \"type\" attribute "
|
|
": <entry type=\"%s\"/>") % nType)
|
|
return False
|
|
if flagValue:
|
|
nValue = n.get("value")
|
|
if "action" in n.keys():
|
|
nAction = n.get("action")
|
|
if nAction not in ("join", "replace", "drop"):
|
|
textError = _("In the text of the XML template, "
|
|
"reserved attribute 'action' comes with an "
|
|
"incorrect value.\n"
|
|
"Valid values of the 'action' attribute are: "
|
|
'(action="join", action="replace",'
|
|
'action="drop")')
|
|
self.setError(textError)
|
|
return False
|
|
if xmlNewNode.getparent():
|
|
findAttrStr = ""
|
|
if attrName:
|
|
findAttrStr = "[%s]" % attrName
|
|
findPath = "child::%s%s" % (tagName, findAttrStr)
|
|
# Рабочая нода
|
|
if flagRootNode:
|
|
workNode = xmlOldNode.getparent()
|
|
else:
|
|
workNode = xmlOldNode
|
|
oldNodes = xpath.Evaluate(findPath, workNode)
|
|
# По умолчанию - объединение
|
|
flagJoin = True
|
|
flagReplace = False
|
|
flagDrop = False
|
|
# Замещаем ноду
|
|
if nType == "string" or nAction == "replace":
|
|
flagJoin = False
|
|
flagReplace = True
|
|
# Замещаем ноду в случае массива
|
|
elif nType == "list" or nType == "pair":
|
|
flagJoin = False
|
|
flagReplace = True
|
|
# Удаляем ноду
|
|
elif nAction == "drop":
|
|
flagJoin = False
|
|
flagDrop = True
|
|
if flagRootNode:
|
|
textError = _('Incorrect action="drop" in '
|
|
'the root node')
|
|
self.setError(textError)
|
|
return False
|
|
if oldNodes:
|
|
if len(oldNodes) > 1:
|
|
textError = _("Ambiguity in this template: the same "
|
|
"nodes are on a same level")
|
|
self.setError(textError)
|
|
return False
|
|
nextOldNode = oldNodes[0]
|
|
# Объединение нод
|
|
if flagJoin:
|
|
if flagType and flagValue:
|
|
flagChange = False
|
|
if "value" in nextOldNode.keys():
|
|
oValue = nextOldNode.get("value")
|
|
if nValue != oValue:
|
|
flagChange = True
|
|
else:
|
|
flagChange = True
|
|
if flagChange:
|
|
nextOldNode.set("mtime", self.currentTime)
|
|
nextOldNode.set("value", nValue)
|
|
elif flagSchema:
|
|
flagChange = False
|
|
if "schema" in nextOldNode.keys():
|
|
oSchema = nextOldNode.get("schema")
|
|
if nSchema != oSchema:
|
|
flagChange = True
|
|
else:
|
|
flagChange = True
|
|
if flagChange:
|
|
nextOldNode.set("mtime", self.currentTime)
|
|
nextOldNode.set("schema", nSchema)
|
|
# Замещение ноды
|
|
elif flagReplace:
|
|
replaceXmlNode = deepcopy(xmlNode)
|
|
# Сравнение содержимого нод
|
|
if not self.cmpListsNodesEntry([replaceXmlNode],
|
|
[nextOldNode]):
|
|
replaceXmlNode.set("mtime",
|
|
self.currentTime)
|
|
if not self._removeDropNodesAndAttrAction(
|
|
replaceXmlNode):
|
|
return False
|
|
workNode.replace(nextOldNode, replaceXmlNode)
|
|
childNodes = False
|
|
# Удаление ноды
|
|
elif flagDrop:
|
|
workNode.remove(nextOldNode)
|
|
childNodes = False
|
|
else:
|
|
# Добавление ноды
|
|
childNodes = False
|
|
if not flagDrop:
|
|
appendXmlNode = deepcopy(xmlNode)
|
|
appendXmlNode.set("mtime", self.currentTime)
|
|
if not self._removeDropNodesAndAttrAction(
|
|
appendXmlNode):
|
|
return False
|
|
workNode.append(appendXmlNode)
|
|
if isinstance(childNodes, Iterable):
|
|
for node in childNodes:
|
|
levelNumber += 1
|
|
if not self._join(node, nextOldNode, False, levelNumber):
|
|
return False
|
|
levelNumber -= 1
|
|
return True
|
|
|
|
def join(self, xml_gconfObj):
|
|
"""Объединяем конфигурации"""
|
|
# Получаем текущее время
|
|
self.currentTime = self.getCurrentTime()
|
|
if isinstance(xml_gconfObj, xml_gconf):
|
|
try:
|
|
self.joinDoc(xml_gconfObj.doc)
|
|
except Exception:
|
|
self.setError(_("Failed to join the template"))
|
|
return False
|
|
return True
|
|
|
|
def getConfig(self):
|
|
"""Получение текстового файла из XML документа"""
|
|
|
|
def expandStartTabs(s):
|
|
if s.startswith("\t"):
|
|
res = self.reStartTabs.search(s)
|
|
if res:
|
|
return str(res.group(1)).replace("\t", " ") + res.group(2)
|
|
return s
|
|
else:
|
|
return s
|
|
|
|
data = xml_to_str(self.doc).split("\n")
|
|
data = [expandStartTabs(x) for x in data if x.strip()]
|
|
dataOut = []
|
|
z = 0
|
|
lenData = len(data)
|
|
lenM2 = lenData - 2
|
|
for i in range(lenData):
|
|
if z > 0:
|
|
z -= 1
|
|
continue
|
|
if i < lenM2 and data[i].endswith(">") and "<" not in data[i + 1]:
|
|
dataOut.append(
|
|
data[i] + data[i + 1].strip() + data[i + 2].strip())
|
|
z = 2
|
|
continue
|
|
dataOut.append(data[i])
|
|
#TODO test this
|
|
dataOut.insert(0, '<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
return "\n".join(dataOut)
|