美文网首页
使用python3处理树形结构数据,tree data 树形数据

使用python3处理树形结构数据,tree data 树形数据

作者: SystemLight | 来源:发表于2020-07-24 23:32 被阅读0次
from tornado.template import Template, Loader

import os
import re
from typing import List, Optional, Callable, TypeVar

T = TypeVar("T")
ParseCallable = Callable[["TreeOperate", List[T], int], T]


def require(path):
    """

    有时你可能只是需要从文件中读取到json数据,这是require函数将根据
    获取到的path,返回dict对象,相当方便,该函数同样类似于json.load

    :param path:
    :return: dict

    """
    import json
    fp = open(path, "r")
    data = fp.read()
    fp.close()
    try:
        return json.loads(data)
    except json.decoder.JSONDecodeError:
        return {}


class GenerateCodeEngine:
    """

    生成代码引擎类

    使用方法::

        gec = GenerateCodeEngine()
        gec.catch_write("index.html", "template.html", {
            "anthor": "systemlight"
        })

    """

    def __init__(self, template_root_path=""):
        """

        构造函数

        :param template_root_path: 模板根目录

        """
        self.glob_content = {}
        self.template_root_path = template_root_path

        self.__start_match_tag = r"//start_user_code"
        self.__end_match_tag = r"//end_user_code"
        self.catch_match = r"{}(.*?){}".format(self.__start_match_tag, self.__end_match_tag)

    @property
    def start_match_tag(self):
        return self.__start_match_tag

    @start_match_tag.setter
    def start_match_tag(self, value):
        self.__start_match_tag = value
        self.catch_match = r"{}(.*?){}".format(self.__start_match_tag, self.__end_match_tag)

    @property
    def end_match_tag(self):
        return self.__end_match_tag

    @end_match_tag.setter
    def end_match_tag(self, value):
        self.__end_match_tag = value
        self.catch_match = r"{}(.*?){}".format(self.__start_match_tag, self.__end_match_tag)

    def register_glob_content(self, name, value):
        """

        注册全局方法或者变量,每个模板渲染时都将附带该内容

        :param name: 名称
        :param value: 内容
        :return: None

        """
        self.glob_content[name] = value

    def render(self, template_path, kwargs=None):
        """

        根据模板渲染并生成字符串返回

        :param template_path: 模板文件路径
        :param kwargs: 包含写入模板中的变量数据和函数等
        :return: 渲染后的内容

        """
        template_path = os.path.join(self.template_root_path, template_path)
        if kwargs is None:
            kwargs = {}
        with open(template_path, "r", encoding="utf-8") as fp:
            temp = Template(fp.read(), autoescape=None, loader=Loader(self.template_root_path))
        glob_content = {**self.glob_content, **kwargs}
        return temp.generate(**glob_content)

    def write(self, path, template_path, kwargs=None):
        """

        将渲染内容希尔到文件当中

        :param path: 目标文件路径
        :param template_path: 模板文件路径
        :param kwargs: 包含写入模板中的变量数据和函数等
        :return: None

        """
        with open(path, "w", encoding="utf-8") as fp:
            fp.write(self.render(template_path, kwargs).decode())

    def catch_write(self, path, template_path, kwargs=None):
        """

        捕获用户代码写入方法,执行写入之前会先匹配用户代码

        :param path: 目标文件路径
        :param template_path: 模板文件路径
        :param kwargs: 其它额外参数,参考catch_user_code方法,包含写入模板中的变量数据和函数等
        :return: None

        """
        if kwargs is None:
            kwargs = {}
        user_code = self.catch_user_code(
            path=path,
            match=kwargs.get("match", None),
            code_count=kwargs.get("code_count", 1),
        )
        kwargs["user_code"] = user_code
        self.write(path, template_path, kwargs)

    def catch_user_code(self, path, match=None, code_count=1):
        """

        捕获目标路径文件中的用户代码

        :param path: 目标文件路径
        :param match: 匹配用户代码规则
        :param code_count: 用户代码数量
        :return: 匹配结果列表

        """
        if match is None:
            match = self.catch_match
        if not os.path.exists(path):
            return [""] * code_count
        with open(path, "r", encoding="utf-8") as fp:
            content = fp.read()
        result = re.findall(match, content, re.S)
        result = list(map(lambda v: v.strip("\n "), result))
        size = len(result)
        if size < code_count:
            return result + [""] * (code_count - size)
        return result


class TreeOperate:
    """
    
    TreeOperate允许你操作一颗树型结构数据,支持数据导入和导出,数据必须含有key唯一标识,
    子元素必须存储在children键值下
    示例内容::
        _data = {
            "key": "1",
            "title": "root",
            "children": [
                {"key": "2", "title": "2", "children": [
                    {"key": "4", "title": "4"},
                    {"key": "5", "title": "5"}
                ]},
                {"key": "3", "title": "3", "children": [
                    {"key": "6", "title": "6"},
                    {"key": "7", "title": "7"}
                ]}
            ]
        }
        tree_root = TreeOperate.from_dict(_data)
        tree_root.find("2").append(TreeOperate.from_dict({"key": "8", "title": "8"}))
        print(tree_root.find("8"))
        tree_root.find("8").remove()
        print(tree_root.find("8"))
        
    """

    def __init__(self, key=None):
        self.key = key
        self.pid = None
        self.data = {}
        self.parent = None  # type: Optional[TreeOperate]
        self.__children = []  # type: List[TreeOperate]

    def __str__(self):
        return str({
            "key": self.key,
            "pid": self.pid,
            "data": self.data,
            "children": self.__children
        })

    @staticmethod
    def from_dict(data):
        """
        
        从dict对象中返回TreeOperate对象
        :param data: dict
        :return: TreeOperate
        
        """
        tree = TreeOperate(data.get("key", None))
        for d in data:
            if d not in ["key", "children"]:
                tree.data[d] = data[d]
        for i in data.get("children", []):
            tree.append(TreeOperate.from_dict(i))
        return tree

    @staticmethod
    def from_file(path):
        """
        
        从json文件中读取数据
        
        :param path: json文件路径 
        :return: TreeOperate
        
        """
        return TreeOperate.from_dict(require(path))

    @property
    def children(self):
        return self.__children

    def append(self, sub_tree: "TreeOperate"):
        """
        
        为当前节点添加子节点,节点类型必须是TreeOperate类型
        :param sub_tree: 子类型节点
        :return: None
        
        """
        if not isinstance(sub_tree, TreeOperate):
            raise TypeError("sub_tree must be of type TreeOperate")
        sub_tree.pid = self.key
        sub_tree.parent = self
        self.__children.append(sub_tree)

    def find(self, key: str):
        """
        
        根据key值查找节点
        :param key: key值
        :return: TreeOperate
        
        """
        if self.key == key:
            return self
        else:
            for i in self.__children:
                result = i.find(key)
                if result:
                    return result
        return None

    def remove(self, key=None):
        """
        
        删除节点,如果传递key值,将删除当前节点下匹配的子孙节点,
        如果不传递key值将当前节点从父节点中删除
        :param key: [可选] key值
        :return:
        
        """
        if key is None:
            if self.parent is not None:
                self.parent.__children.remove(self)
        else:
            remove_child = self.find(key)
            if remove_child:
                remove_child.parent.__children.remove(remove_child)

    def parse(self, callback: ParseCallable, deep=0):
        """
        
        遍历定制解析规则,返回解析内容
        
        :param callback: Callable[["TreeOperate", List[T], int], T] 解析回调函数返回解析结果
        :param deep: 当前解析深度,默认不需要填写,用于回调函数接收判断所在层级
        :return: 解析结果
        
        """
        child_parse_list = []
        for i in self.__children:
            child_parse_list.append(i.parse(callback, deep + 1))
        return callback(self, child_parse_list, deep)

    def to_dict(self, flat=False):
        """
        
        输出dict类型数据,用于json化
        :param flat: 是否将data参数内容直接映射到对象
        :return: dict
        
        """
        result = dict(key=self.key, pid=self.pid)
        if flat:
            for j in self.data:
                result[j] = self.data[j]
        else:
            result["data"] = self.data
        children = []
        for i in self.__children:
            children.append(i.to_dict(flat))
        result["children"] = children
        return result


def domain_parse_jsx(_tree_obj: TreeOperate):
    """
    
    保存额外解析数据,解析函数会被所有树叶节点对象调用,如果需要保存一些遍历叶子节点时的数据内容,
    可以定义一个函数,并增加一个变量用于接受。
    
    :param _tree_obj: 
    :return: 
    
    """
    data_pool = set()

    def parse_jsx(self: "TreeOperate", child: List[str], deep=0) -> str:
        p_str = ""
        props = self.data["props"]
        data_pool.add(self.data["title"])
        for p in props:
            if isinstance(props[p], str):
                p_str += " {}={{\"{}\"}}".format(p, props[p])
            elif isinstance(props[p], bool):
                p_str += " {}={{{}}}".format(p, str(props[p]).lower())
            elif isinstance(props[p], int):
                p_str += " {}={{{}}}".format(p, props[p])
            else:
                p_str += " {}={{\"{}\"}}".format(p, props[p])
        return "\n{0}<{1}{3}>{2}\n{0}</{1}>".format("\t" * deep, self.data["title"], "".join(child), p_str)

    result = tree_obj.parse(parse_jsx)
    return result, data_pool


if __name__ == '__main__':
    tree_obj = TreeOperate.from_file("data.json")
    dom, import_pool = domain_parse_jsx(tree_obj)
    print(dom)

相关文章

网友评论

      本文标题:使用python3处理树形结构数据,tree data 树形数据

      本文链接:https://www.haomeiwen.com/subject/puyelktx.html