Pyparsing: синтаксический анализ вложенных текстовых данных в формате JSON.
У меня есть куча вложенных данных в формате, который напоминает JSON:
company="My Company"
phone="555-5555"
people=
{
person=
{
name="Bob"
location="Seattle"
settings=
{
size=1
color="red"
}
}
person=
{
name="Joe"
location="Seattle"
settings=
{
size=2
color="blue"
}
}
}
places=
{
...
}
Существует много разных параметров с разными уровнями глубины - это всего лишь небольшое подмножество.
Также может стоить отметить, что при создании нового подмассива всегда есть знак равенства, за которым следует разрыв строки, за которым следует открывающая скобка (как видно выше).
Есть ли какой-нибудь простой метод зацикливания или рекурсии для преобразования этих данных в дружественный к системе формат данных, такой как массивы или JSON? Я хочу избежать жесткого кодирования названий свойств. Я ищу что-то, что будет работать на Python, Java или PHP. Псевдокод тоже подойдет.
Я ценю любую помощь.
РЕДАКТИРОВАТЬ: я обнаружил библиотеку Pyparsing для Python, и, похоже, это может быть очень полезно. Я не могу найти примеры того, как использовать Pyparsing для анализа вложенных структур неизвестной глубины. Может ли кто-нибудь пролить свет на Pyparsing с точки зрения данных, которые я описал выше?
РЕДАКТИРОВАТЬ 2: Хорошо, вот рабочее решение в Pyparsing:
def parse_file(fileName):
#get the input text file
file = open(fileName, "r")
inputText = file.read()
#define the elements of our data pattern
name = Word(alphas, alphanums+"_")
EQ,LBRACE,RBRACE = map(Suppress, "={}")
value = Forward() #this tells pyparsing that values can be recursive
entry = Group(name + EQ + value) #this is the basic name-value pair
#define data types that might be in the values
real = Regex(r"[+-]?\d+\.\d*").setParseAction(lambda x: float(x[0]))
integer = Regex(r"[+-]?\d+").setParseAction(lambda x: int(x[0]))
quotedString.setParseAction(removeQuotes)
#declare the overall structure of a nested data element
struct = Dict(LBRACE + ZeroOrMore(entry) + RBRACE) #we will turn the output into a Dictionary
#declare the types that might be contained in our data value - string, real, int, or the struct we declared
value << (quotedString | struct | real | integer)
#parse our input text and return it as a Dictionary
result = Dict(OneOrMore(entry)).parseString(inputText)
return result.dump()
Это работает, но когда я пытаюсь записать результаты в файл с помощью json.dump (result), содержимое файла будет заключено в двойные кавычки. Также есть\n
персонажи между многими парами данных. Я попытался подавить их в коде выше с помощьюLineEnd().suppress()
, но я не должен использовать это правильно.
Хорошо, я пришел к окончательному решению, которое на самом деле превращает эти данные в JSON-дружественный Dict, как я изначально хотел. Сначала он использует Pyparsing для преобразования данных в серию вложенных списков, а затем перебирает список и преобразует его в JSON. Это позволяет мне преодолеть проблему, когда PyparsingtoDict()
Метод не смог обработать, когда один и тот же объект имеет два свойства с одинаковым именем. Чтобы определить, является ли список простым списком или парой свойство / значение,prependPropertyToken
метод добавляет строку__property__
перед именами свойств, когда Pyparsing обнаруживает их.
def parse_file(self,fileName):
#get the input text file
file = open(fileName, "r")
inputText = file.read()
#define data types that might be in the values
real = Regex(r"[+-]?\d+\.\d*").setParseAction(lambda x: float(x[0]))
integer = Regex(r"[+-]?\d+").setParseAction(lambda x: int(x[0]))
yes = CaselessKeyword("yes").setParseAction(replaceWith(True))
no = CaselessKeyword("no").setParseAction(replaceWith(False))
quotedString.setParseAction(removeQuotes)
unquotedString = Word(alphanums+"_-?\"")
comment = Suppress("#") + Suppress(restOfLine)
EQ,LBRACE,RBRACE = map(Suppress, "={}")
data = (real | integer | yes | no | quotedString | unquotedString)
#define structures
value = Forward()
object = Forward()
dataList = Group(OneOrMore(data))
simpleArray = (LBRACE + dataList + RBRACE)
propertyName = Word(alphanums+"_-.").setParseAction(self.prependPropertyToken)
property = dictOf(propertyName + EQ, value)
properties = Dict(property)
object << (LBRACE + properties + RBRACE)
value << (data | object | simpleArray)
dataset = properties.ignore(comment)
#parse it
result = dataset.parseString(inputText)
#turn it into a JSON-like object
dict = self.convert_to_dict(result.asList())
return json.dumps(dict)
def convert_to_dict(self, inputList):
dict = {}
for item in inputList:
#determine the key and value to be inserted into the dict
dictval = None
key = None
if isinstance(item, list):
try:
key = item[0].replace("__property__","")
if isinstance(item[1], list):
try:
if item[1][0].startswith("__property__"):
dictval = self.convert_to_dict(item)
else:
dictval = item[1]
except AttributeError:
dictval = item[1]
else:
dictval = item[1]
except IndexError:
dictval = None
#determine whether to insert the value into the key or to merge the value with existing values at this key
if key:
if key in dict:
if isinstance(dict[key], list):
dict[key].append(dictval)
else:
old = dict[key]
new = [old]
new.append(dictval)
dict[key] = new
else:
dict[key] = dictval
return dict
def prependPropertyToken(self,t):
return "__property__" + t[0]