Python PyYAML和不寻常的标签

Python PyYAML和不寻常的标签,python,yaml,pyyaml,Python,Yaml,Pyyaml,我正在从事一个使用Unity3D游戏引擎的项目。对于某些管道需求,最好能够使用Python从外部工具更新一些文件。Unity的meta和anim文件是YAML格式的,所以我认为使用PyYAML就足够了 问题是Unity的格式使用自定义属性,我不知道如何使用它们,因为所有示例都显示了Python和Ruby使用的更常见的标记 以下是文件顶部的几行内容: %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!74 &7400000 AnimationC

我正在从事一个使用Unity3D游戏引擎的项目。对于某些管道需求,最好能够使用Python从外部工具更新一些文件。Unity的meta和anim文件是YAML格式的,所以我认为使用PyYAML就足够了

问题是Unity的格式使用自定义属性,我不知道如何使用它们,因为所有示例都显示了Python和Ruby使用的更常见的标记

以下是文件顶部的几行内容:

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  ...
当我尝试读取文件时,出现以下错误:

could not determine a constructor for the tag 'tag:unity3d.com,2011:74'
现在,在看了所有其他问题后,这个标签方案似乎与这些问题和答案不相似。例如,该文件使用“!u!”,我无法理解它的含义或类似的行为(我未受过教育的猜测是它看起来像一个别名或名称空间)

我可以做一个黑客的方式,剥离标签,但这不是理想的方式来尝试这样做。我正在寻求一个解决方案的帮助,该解决方案将正确处理标记,并允许我以保留正确格式的方式解析和编码数据

谢谢,
-R

我也有这个问题,互联网也没有什么帮助。在对这个问题苦思冥想了3天后,我终于能够解决它……或者至少找到了一个有效的解决方案。如果有人想添加更多信息,请添加。但这是我得到的

1) 关于Unity的YAML文件格式的文档(他们称之为“文本场景文件”,因为它包含人类可读的文本)——

它是符合YAML 1.1的格式。因此,您应该能够使用PyYAML或任何其他pythonyaml库来加载YAML对象

好的,很好。但它不起作用。每个YAML库都有此文件的问题

2) 文件格式不正确。事实证明,Unity文件存在一些语法问题,导致YAML解析器出错。具体而言:

2a)在顶部,它使用%TAG指令为字符串“unity3d.com,2011”创建别名。它看起来像:

%TAG !u! tag:unity3d.com,2011:
这意味着你可以在任何地方看到“!u!”,替换为“tag:unity3d.com,2011”

2b)然后在每个对象流之前的所有位置使用“!u!”。但问题是,为了符合YAML 1.1,它实际上应该为每个流声明一个标记别名(每当新对象以“---”开头时)。在顶部声明一次且不再声明只对第一个流有效,而下一个流对“!u!”一无所知,因此会出错

而且,这个标签是无用的。它基本上会将“tag:unity3d.com,2011”附加到流中的每个条目。我们不在乎。我们已经知道这是一个统一的YAML文件。为什么要把数据弄得乱七八糟

3) 对象类型由Unity的类ID给出。以下是关于该类的文档:

基本上,每个流都被定义为一个新的对象类…对应于该链接中的ID。所以“游戏对象”是“1”,以此类推。线条如下:

--- !u!1 &100000
def removeUnityTagAlias(filepath):
    """
    Name:               removeUnityTagAlias()

    Description:        Loads a file object from a Unity textual scene file, which is in a pseudo YAML style, and strips the
                        parts that are not YAML 1.1 compliant. Then returns a string as a stream, which can be passed to PyYAML.
                        Essentially removes the "!u!" tag directive, class type and the "&" file ID directive. PyYAML seems to handle
                        rest just fine after that.

    Returns:                String (YAML stream as string)  


    """
    result = str()
    sourceFile = open(filepath, 'r')

    for lineNumber,line in enumerate( sourceFile.readlines() ): 
        if line.startswith('--- !u!'):          
            result += '--- ' + line.split(' ')[2] + '\n'   # remove the tag, but keep file ID
        else:
            # Just copy the contents...
            result += line

    sourceFile.close()  

    return result
所以“--”定义了一个新的流。“!u!”是“tag:unity3d.com,2011”的别名,“&100000”是此对象的文件ID(在此文件中,如果有东西引用此对象,则使用此ID…。请记住,YAML是基于节点的表示,因此ID用于表示节点连接)

下一行是YAML对象的根,它恰好是Unity类的名称……例如“GameObject”。所以事实证明,我们实际上不需要将类ID转换为人类可读的节点类型。就在那里。如果需要使用它,只需使用根节点即可。如果您需要为Unity构建一个YAML对象,只需根据文档链接保留一个字典,将“GameObject”转换为“1”,等等

另一个问题是大多数YAML解析器(我测试的是PyYAML)只支持3种类型的YAML对象:

  • 标量
  • 序列
  • 映射
  • 可以定义/扩展自定义节点。但这相当于手工编写自己的YAML解析器,因为您必须明确定义每个YAML构造函数的创建和输出方式。为什么我要使用PyYAML这样的库,然后编写自己的解析器来读取这些自定义节点?使用库的全部目的是利用以前的工作并从第一天起获得所有这些功能。我花了两天时间试图为unity中的每个类ID创建一个新的构造函数。它从来都不起作用,我陷入了困境,试图正确地构建构造函数

    好消息/解决方案:

    事实证明,到目前为止我遇到的所有Unity节点都是YAML中的基本“映射”节点。因此,您可以抛弃自定义节点映射,让PyYAML自动检测节点类型。从那以后,一切都很顺利

    在PyYAML中,可以传递文件对象或字符串。因此,我的解决方案是编写一个简单的5行预解析器,去掉混淆PyYAML的位(Unity语法错误的位),并将这个新字符串提供给PyYAML

    1) 完全删除第2行,或忽略它:

    %TAG !u! tag:unity3d.com,2011:
    
    我们不在乎。我们知道这是一个统一文件。标签对我们没有任何作用

    2) 对于每个流声明,删除标记别名(“!u!”)并删除类ID。保留文件ID。让PyYAML自动检测作为映射节点的节点

    --- !u!1 &100000
    
    变成

    --- &100000
    
    3) 其余部分按原样输出

    预解析器的代码如下所示:

    --- !u!1 &100000
    
    def removeUnityTagAlias(filepath):
        """
        Name:               removeUnityTagAlias()
    
        Description:        Loads a file object from a Unity textual scene file, which is in a pseudo YAML style, and strips the
                            parts that are not YAML 1.1 compliant. Then returns a string as a stream, which can be passed to PyYAML.
                            Essentially removes the "!u!" tag directive, class type and the "&" file ID directive. PyYAML seems to handle
                            rest just fine after that.
    
        Returns:                String (YAML stream as string)  
    
    
        """
        result = str()
        sourceFile = open(filepath, 'r')
    
        for lineNumber,line in enumerate( sourceFile.readlines() ): 
            if line.startswith('--- !u!'):          
                result += '--- ' + line.split(' ')[2] + '\n'   # remove the tag, but keep file ID
            else:
                # Just copy the contents...
                result += line
    
        sourceFile.close()  
    
        return result
    
    要从Unity文本场景文件创建PyYAML对象,请对该文件调用预解析器函数:

    import yaml
    
    # This fixes Unity's YAML %TAG alias issue.
    fileToLoad = '/Users/vlad.dumitrascu/<SOME_PROJECT>/Client/Assets/Gear/MeleeWeapons/SomeAsset_test.prefab'
    
    UnityStreamNoTags = removeUnityTagAlias(fileToLoad)
    
    ListOfNodes = list()
    
    for data in yaml.load_all(UnityStreamNoTags):
        ListOfNodes.append( data )
    
    # Example, print each object's name and type
    for node in ListOfNodes:
        if 'm_Name' in node[ node.keys()[0] ]:
            print( 'Name: ' + node[ node.keys()[0] ]['m_Name']  + ' NodeType: ' + node.keys()[0] )
        else:
            print( 'Name: ' + 'No Name Attribute'  + ' NodeType: ' + node.keys()[0] )
    
    那个文件在别的地方。您可以草率地重新打开它以查找任何依赖项

    我刚刚浏览了这个游戏项目,并保存了一个GUID:Filepath-Key:Value对字典,我可以对它进行匹配