1
votes

Analyse d'une chaîne avec des guillemets imbriqués

J'ai besoin d'analyser une chaîne qui ressemble à ceci:

['field1', '', 'field2', 'field3', 'select ... where (column1 = '2017') and ((('literal1', 'literal2', 'literal3', 'literal4', 'literal5', 'literal6', 'literal7') OVERLAPS column2 Or ('literal8') OVERLAPS column3 And" (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = 'literal9')  LIMIT 0 ', 'field5', 'field6', 'field7', 'field8', 'field9', '', 'field10']

Et j'aimerais obtenir une liste comme celle-ci:

"prefix 'field1', '', 'field2', 'field3', 'select ... where (column1 = '2017') and ((('literal1', 'literal2', 'literal3', 'literal4', 'literal5', 'literal6', 'literal7') OVERLAPS column2 Or ('literal8') OVERLAPS column3 And (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = 'literal9')  LIMIT 0 ', 'field5', 'field6', 'field7', 'field8', 'field9', '', 'field10'"

Je l'ai essayé avec des expressions régulières, mais cela ne fonctionne pas dans la sous-chaîne de l'instruction pseudo-SQL.

Comment puis-je obtenir cette liste?


8 commentaires

L'exemple du bas ne se termine pas correctement lorsque vous le mettez dans un script, donc je ne sais pas exactement comment vous voulez le découper. Il y a aussi un voyou "après" colonne3 Et ", ce qui signifie qu'il y a 3 guillemets doubles. Comment devraient-ils s'aligner?


J'ai réparé la chaîne. Je voudrais obtenir une liste où chaque champ de la chaîne était un champ dans la liste, y compris la sous-chaîne commence par select et se termine par LIMIT 0.


La partie SQL est-elle correcte? il contient un guillemet non autorisé dans OVERLAPS column3 Et ". De plus, le nombre de champs est-il constant?


Je ne pense pas que cela soit possible si la chaîne d'entrée contient des instructions sql arbitratry , car celles-ci peuvent contenir n'importe quel nombre de guillemets et de virgules incorporés.


Si le nombre de champs est constant, vous pouvez extraire les champs du côté gauche de la requête et les champs du côté droit de la requête, puis tout ce qui reste est la requête SQL.


@solarc Oui - par "arbitraire", je voulais dire n'importe quel nombre d'instructions sql n'importe où dans la chaîne d'entrée. Ce qui, je suppose, est une autre façon de demander si la chaîne d'entrée a une structure fixe. Un autre problème est: qu'y a-t-il exactement dans les autres domaines? Ce n'est probablement pas littéralement "champ1", "champ2", etc.


@solarc oui, le nombre de champs est constant et n'aura qu'une seule instruction pseudosql.


@ekhumoro Dans les autres champs, il y a du texte libre mais ils ne contiennent pas de guillemets à l'intérieur


5 Réponses :


0
votes

Quelqu'un a signalé que votre chaîne est malformée, j'ai utilisé ceci:

['field1',
 '',
 'field2',
 'field3',
 'select',
 '2017)',
 '(((literal1',
 'literal2',
 'literal3',
 'literal4',
 'literal5',
 'literal6',
 'literal7)',
 '(literal8)',
 'literal9)',
 '',
 'field5',
 'field6',
 'field7',
 'field8',
 'field9',
 '',
 'field10']

Ce qui renvoie:

mystr = "prefix 'field1', '', 'field2', 'field3', 'select ... where (column1 = '2017') and ((('literal1', 'literal2', 'literal3', 'literal4', 'literal5', 'literal6', 'literal7') OVERLAPS column2 Or ('literal8') OVERLAPS column3 And" (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = 'literal9')  LIMIT 0 ', 'field5', 'field6', 'field7', 'field8', 'field9', '', 'field10'"

found = [a.replace("'", '').replace(',', '') for a in mystr.split(' ') if "'" in a]


1 commentaires

Votre sortie ne ressemble pas à ce que l'OP a demandé. L'instruction select ne doit pas du tout être interrompue.



2
votes

Voici une façon fragile de le faire si vous savez à quoi la chaîne SQL est censée ressembler.

Nous faisons correspondre la chaîne SQL et divisons le reste en chaînes de début et de fin.

Ensuite, nous faisons correspondre le modèle de champ plus simple et construisons une liste à partir du début pour ce modèle, rajoutons dans la correspondance SQL , puis les champs de la chaîne de fin.

['field1',
 '',
 'field2',
 'field3',
 'select ... where (column1 = \'2017\') and (((\'literal1\', \'literal2\', \'literal3\', \'literal4\', \'literal5\', \'literal6\', \'literal7\') OVERLAPS column2 Or (\'literal8\') 
OVERLAPS column3 And" (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = \'literal9\')  LIMIT 0',
 'field5',
 'field6',
 'field7',
 'field8',
 'field9',
 '',
 'field10']

Ensuite, la liste de résultats ressemble à ce qui suit:

sqlmatch = 'select .* LIMIT 0'
fieldmatch = "'(|\w+)'"
match = re.search(sqlmatch, mystring)
startstring = mystring[:match.start()]
sql = mystring[match.start():match.end()]
endstring = mystring[match.end():]
result = []
for found in re.findall(fieldmatch, startstring):
    result.append(found)

result.append(sql)
for found in re.findall(fieldmatch, endstring):
    result.append(found)


1 commentaires

Cela ne préserve pas les chaînes vides selon les exemples (bien que cela ne signifie pas nécessairement que l'OP en a besoin)



0
votes

Si le nombre de champs est constant, vous pouvez faire quelque chose comme ceci:

def splitter(string):
    strip_chars = "\"' "
    string = string[len('prefix '):] # remove the prefix
    left_parts = string.split(',', 4) # only split up to 4 times
    for i in left_parts[:-1]:
        yield i.strip(strip_chars) # return what we've found so far
    right_parts = left_parts[-1].rsplit(',', 7) # only split up to 7 times starting from the right
    for i in right_parts:
        yield i.strip(strip_chars) # return the rest

mystr = """prefix 'field1', '', 'field2', 'field3', 'select ... where (column1 = '2017') and ((('literal1', 'literal2', 'literal3', 'literal4', 'literal5', 'literal6', 'literal7') OVERLAPS column2 Or ('literal8') OVERLAPS column3 And" (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = 'literal9')  LIMIT 0 ', 'field5', 'field6', 'field7', 'field8', 'field9', '', 'field10'"""
result = list(splitter(mystr))
print(repr(result))


# result:
[
    'field1',
    '',
    'field2',
    'field3',
    'select ... where (column1 = \'2017\') and (((\'literal1\', \'literal2\', \'literal3\', \'literal4\', \'literal5\', \'literal6\', \'literal7\') OVERLAPS column2 Or (\'literal8\') OVERLAPS column3 And" (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = \'literal9\')  LIMIT 0',
    'field5',
    'field6',
    'field7',
    'field8',
    'field9',
    '',
    'field10'
]


1 commentaires

Si le reste des champs peut contenir des virgules, alors il faudrait un analyseur au lieu du simple fractionnement.



0
votes

Les séparateurs de virgule qui se trouvent réellement entre les champs seront à un niveau de guillemets pair. Ainsi, en changeant ces virgules en caractères \ n, vous pouvez appliquer un simple .split ("\ n") sur la chaîne pour obtenir les valeurs de champ. Il vous suffit ensuite de nettoyer les valeurs des champs pour supprimer les espaces de début / fin et les guillemets.

0 field1
1 
2 field2
3 field3
4 select ... where (column1 = '2017') and ((('literal1', 'literal2', 'literal3', 'literal4', 'literal5', 'literal6', 'literal7') OVERLAPS column2 Or ('literal8') OVERLAPS column3 And (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = 'literal9')  LIMIT 0 
5 field5
6 field6
7 field7
8 field8
9 field9
10 
11 field10

Ceci imprimera:

from itertools import accumulate

string      = "prefix 'field1', '', 'field2', 'field3', 'select ... where (column1 = '2017') and ((('literal1', 'literal2', 'literal3', 'literal4', 'literal5', 'literal6', 'literal7') OVERLAPS column2 Or ('literal8') OVERLAPS column3 And (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = 'literal9')  LIMIT 0 ', 'field5', 'field6', 'field7', 'field8', 'field9', '', 'field10'"
prefix,data = string.split(" ",1)                   # remove prefix
quoteLevels = accumulate( c == "'" for c in data )  # compute quote levels for each character
fieldData   = "".join([ "\n" if c=="," and q%2 == 0 else c for c,q in zip(data,quoteLevels) ]) # comma to /n at even quote levels
fields      = [ f.strip().strip("'") for f in fieldData.split("'\n '") ] # split and clean content

for i,field in enumerate(fields): print(i,field)


3 commentaires

Cela ne fonctionnera pas dans les cas où les champs contiennent des guillemets ou des virgules. par exemple. si 'literal1' était à la place 'bob, dole' ou 'bob \' s '.


Ce ne serait un problème que si un texte entre guillemets incorporé contenait une virgule ou si un champ contenait une citation déséquilibrée. Un champ contenant simplement une virgule ne poserait pas de problème. Dans tous les cas, spéculer au-delà de l'exemple fourni est un peu inutile étant donné que le format des données est criblé d'incohérences potentielles qui ne lui permettront pas de prendre en charge un spectre complet de contenu textuel.


Je ne suis pas en désaccord, je pense juste qu'il vaut la peine de noter les limites des solutions: si quelqu'un d'autre vient avec ces capacités, cela les aidera à se frayer un chemin. Et il échouera si l'un des champs littéraux contient une virgule . Il repose sur ces chaînes littérales pour ne pas contenir de virgule, car l'analyse est désactivée à cette profondeur.



1
votes

Étant donné que le nombre de champs est fixe et que les champs non-sql n'ont pas de guillemets incorporés, il existe une solution simple sur trois lignes:

['field1', '', 'field2', 'field3', "select ... where (column1 = '2017') and ((('literal1literal2literal3literal4literal5literal6literal7') OVERLAPS column2 Or ('literal8') OVERLAPS column3 And (column4 > 0.0 Or column6 > 0.0)) And column7 IN_COMMUNITY [int1] And column5 = 'literal9')  LIMIT 0 ", 'field5', 'field6', 'field7', 'field8', 'field9', '', 'field10']

Résultat:

XXX


0 commentaires