Skip to main content
 首页 » 编程设计

python之解析 CS :GO language file with encoding in Python

2025年04月02日33me-sa

此主题与 Parsing a CS:GO script file in Python 有关主题,但还有另一个问题。 我正在处理来自 CS:GO 的内容,现在我正在尝试制作一个 python 工具,将所有数据从/scripts/文件夹导入 Python 词典。

解析数据后的下一步是解析/resources 中的语言资源文件,并在字典和语言之间建立关系。

Eng本地化有一个原文件: https://github.com/spec45as/PySteamBot/blob/master/csgo_english.txt

文件格式与之前的任务类似,但我遇到了另一个问题。所有语言文件都采用 UTF-16-LE 编码,我无法理解在 Python 中处理编码文件和字符串的方式(我主要使用 Java) 我试图根据 open(fileName, encoding='utf-16-le').read() 制定一些解决方案,但我不知道如何在pyparsing.

pyparsing.ParseException: Expected quoted string, starting with " ending with " (at char 0), (line:1, col:1)

另一个问题是带有\"-like 表达式的行,例如:

"musickit_midnightriders_01_desc"       "\"HAPPY HOLIDAYS, ****ERS!\"\n    -Midnight Riders" 

如果我想保留这些行,如何解析这些符号?

请您参考如下方法:

原始 CS:GO 示例中没有此输入文件的一些新问题:

  1. 在一些值字符串中嵌入 \" 转义引号
  2. 一些带引号的值字符串跨越多行
  3. 一些值以尾随环境条件结尾(例如[$WIN32][$OSX])
  4. 在文件中嵌入注释,用'//'标记

前两个通过修改 value_qs 的定义来解决。由于值现在比键功能更全面,我决定为它们使用单​​独的 QuotedString 定义:

key_qs = QuotedString('"').setName("key_qs") 
value_qs = QuotedString('"', escChar='\\', multiline=True).setName("value_qs") 

第三个需要更多的重构。这些限定条件的使用类似于 C 中的 #IFDEF 宏 - 它们仅在环境匹配条件时才启用/禁用定义。其中一些条件甚至是 bool 表达式:

  • [!$PS3]
  • [$WIN32||$X360||$OSX]
  • [!$X360&&!$PS3]

这可能会导致定义文件中出现重复键,例如在这些行中:

"Menu_Dlg_Leaderboards_Lost_Connection"     "You must be connected to Xbox LIVE to view Leaderboards. Please check your connection and try again." [$X360] 
"Menu_Dlg_Leaderboards_Lost_Connection"     "You must be connected to PlayStation®Network and Steam to view Leaderboards. Please check your connection and try again." [$PS3] 
"Menu_Dlg_Leaderboards_Lost_Connection"     "You must be connected to Steam to view Leaderboards. Please check your connection and try again." 

其中包含键“Menu_Dlg_Leaderboards_Lost_Connection”的 3 个定义,具体取决于设置的环境值。

为了在解析文件时不丢失这些值,我选择在解析时通过附加条件(如果存在)来修改键。此代码实现更改:

LBRACK,RBRACK = map(Suppress, "[]") 
qualExpr = Word(alphanums+'$!&|') 
qualExprCondition = LBRACK + qualExpr + RBRACK 
 
key_value = Group(key_qs + value + Optional(qualExprCondition("qual"))) 
def addQualifierToKey(tokens): 
    tt = tokens[0] 
    if 'qual' in tt: 
        tt[0] += '/' + tt.pop(-1) 
key_value.setParseAction(addQualifierToKey) 

因此在上面的示例中,您将获得 3 个键:

  • Menu_Dlg_Leaderboards_Lost_Connection/$X360
  • Menu_Dlg_Leaderboards_Lost_Connection/$PS3
  • Menu_Dlg_Leaderboards_Lost_Connection

最后,评论的处理,可能是最简单的。 Pyparsing 内置支持跳过注释,就像空格一样。您只需要为评论定义表达式,并让顶级解析器忽略它。为了支持此功能,pyparsing 中预定义了几种常见的注释形式。在这种情况下,解决方案只是将最终解析器定义更改为:

parser.ignore(dblSlashComment) 

最后,在 QuotedString 的实现中有一个小错误,其中标准空白字符串文字如 \t\n 没有被处理,并且是只是被视为不必要的转义“t”或“n”。所以现在,当这一行被解析时:

"SFUI_SteamOverlay_Text"  "This feature requires Steam Community In-Game to be enabled.\n\nYou might need to restart the game after you enable this feature in Steam:\nSteam -> File -> Settings -> In-Game: Enable Steam Community In-Game\n" [$WIN32] 

对于刚刚得到的值字符串:

This feature requires Steam Community In-Game to be enabled.nnYou  
might need to restart the game after you enable this feature in  
Steam:nSteam -> File -> Settings -> In-Game: Enable Steam Community  
In-Gamen 

代替:

This feature requires Steam Community In-Game to be enabled. 
 
You might need to restart the game after you enable this feature in Steam: 
Steam -> File -> Settings -> In-Game: Enable Steam Community In-Game 

我将不得不在 pyparsing 的下一个版本中修复此行为。

这是最终的解析器代码:

from pyparsing import (Suppress, QuotedString, Forward, Group, Dict,  
    ZeroOrMore, Word, alphanums, Optional, dblSlashComment) 
 
LBRACE,RBRACE = map(Suppress, "{}") 
 
key_qs = QuotedString('"').setName("key_qs") 
value_qs = QuotedString('"', escChar='\\', multiline=True).setName("value_qs") 
 
# use this code to convert integer values to ints at parse time 
def convert_integers(tokens): 
    if tokens[0].isdigit(): 
        tokens[0] = int(tokens[0]) 
value_qs.setParseAction(convert_integers) 
 
LBRACK,RBRACK = map(Suppress, "[]") 
qualExpr = Word(alphanums+'$!&|') 
qualExprCondition = LBRACK + qualExpr + RBRACK 
 
value = Forward() 
key_value = Group(key_qs + value + Optional(qualExprCondition("qual"))) 
def addQualifierToKey(tokens): 
    tt = tokens[0] 
    if 'qual' in tt: 
        tt[0] += '/' + tt.pop(-1) 
key_value.setParseAction(addQualifierToKey) 
 
struct = (LBRACE + Dict(ZeroOrMore(key_value)) + RBRACE).setName("struct") 
value <<= (value_qs | struct) 
parser = Dict(key_value) 
parser.ignore(dblSlashComment) 
 
sample = open('cs_go_sample2.txt').read() 
config = parser.parseString(sample) 
 
 
print (config.keys()) 
for k in config.lang.keys(): 
    print ('- ' + k) 
 
#~ config.lang.pprint() 
print (config.lang.Tokens.StickerKit_comm01_burn_them_all) 
print (config.lang.Tokens['SFUI_SteamOverlay_Text/$WIN32'])