2015-06-03 16 views
5

Devo occuparmi di un problema causato da un disegno sporco. Ottengo un elenco di stringhe e voglio analizzare gli attributi al di fuori di esso. Sfortunatamente, non posso cambiare la fonte, dove sono state create queste stringhe.Estrai attributi di una stringa

Esempio:

String s = "type=INFO, languageCode=EN-GB, url=http://www.stackoverflow.com, ref=1, info=Text, that may contain all kind of chars., deactivated=false" 

Ora voglio per estrarre gli attributi type, languageCode, url, ref, info e deactivated.

Il problema qui è il campo info, il cui testo non è limitato dal segno di virgolette. Anche le virgole possono comparire in questo campo, quindi non posso usare la virgola alla fine della stringa per scoprire dove finisce.

Inoltre, queste stringhe non contengono sempre tutti gli attributi. type, info e deactivated sono sempre presenti, il resto è facoltativo.

Qualche suggerimento su come risolvere questo problema?

+0

è ordine di questi elementi fissi? – Pshemo

+1

Come cercare '=' e quindi scegliere la singola parola davanti ad essa come nome del campo. Tutto dopo '=' fino a quando il nome del campo successivo è il valore. Questo presuppone che il valore non possa contenere '=' - se può, non hai molto da fare. – xxbbcc

+2

Se tutti gli attributi _other_ hanno un formato un po 'prevedibile, è possibile rimuoverli e prendere quello che rimane per 'info' ... –

risposta

2

Supponendo che l'ordine degli elementi è fisso si potrebbe scrivere soluzione utilizzando espressioni regolari come questo

String s = "type=INFO, languageCode=EN-GB, url=http://www.stackoverflow.com, ref=1, info=Text, that may contain all kind of chars., deactivated=false"; 

String regex = //type, info and deactivated are always present 
      "type=(?<type>.*?)" 
     + "(?:, languageCode=(?<languageCode>.*?))?"//optional group 
     + "(?:, url=(?<url>.*?))?"//optional group 
     + "(?:, ref=(?<rel>.*?))?"//optional group 
     + ", info=(?<info>.*?)" 
     + ", deactivated=(?<deactivated>.*?)"; 
Pattern p = Pattern.compile(regex); 
Matcher m = p.matcher(s); 
if(m.matches()){ 
    System.out.println("type -> "+m.group("type")); 
    System.out.println("languageCode -> "+m.group("languageCode")); 
    System.out.println("url -> "+m.group("url")); 
    System.out.println("rel -> "+m.group("rel")); 
    System.out.println("info -> "+m.group("info")); 
    System.out.println("deactivated -> "+m.group("deactivated")); 
} 

uscita:

type -> INFO 
languageCode -> EN-GB 
url -> http://www.stackoverflow.com 
rel -> 1 
info -> Text, that may contain all kind of chars. 
deactivated -> false 

EDIT: Version2 regex ricerca di oneOfPossibleKeys=value dove value termina con:

  • , oneOfPossibleKeys=
  • o ha fine della stringa dopo (rappresentato da $).

Codice:

String s = "type=INFO, languageCode=EN-GB, url=http://www.stackoverflow.com, ref=1, info=Text, that may contain all kind of chars., deactivated=false"; 

String[] possibleKeys = {"type","languageCode","url","ref","info","deactivated"}; 
String keysStrRegex = String.join("|", possibleKeys); 
//above will contain type|languageCode|url|ref|info|deactivated 

String regex = "(?<key>\\b(?:"+keysStrRegex+")\\b)=(?<value>.*?(?=, (?:"+keysStrRegex+")=|$))"; 
    // (?<key>\b(?:type|languageCode|url|ref|info|deactivated)\b) 
    // = 
    // (?<value>.*?(?=, (?:type|languageCode|url|ref|info|deactivated)=|$))System.out.println(regex); 

Pattern p = Pattern.compile(regex); 
Matcher m = p.matcher(s); 


while(m.find()){ 
    System.out.println(m.group("key")+" -> "+m.group("value")); 
} 

uscita:

type -> INFO 
languageCode -> EN-GB 
url -> http://www.stackoverflow.com 
ref -> 1 
info -> Text, that may contain all kind of chars. 
deactivated -> false 
+0

Ho avuto un'idea simile a quella della tua versione 2. Ma perché non stai usando il tuo 'keysStrRegex' per la chiave effettiva, cioè, invece di' \\ w + '? –

+0

@tobias_k Questa è un'ottima domanda. Risposta aggiornata – Pshemo

4

Una possibile soluzione è cercare i caratteri = nell'input e quindi prendere la parola singola immediatamente prima di essa come nome del campo - sembra che tutti i nomi dei campi siano parole singole (senza spazi vuoti). In questo caso, è possibile prendere tutto dopo lo = fino al nome del campo successivo (che conta separare ,) come valore.

Ciò presuppone che il valore non possa contenere =.

Edit:

come un possibile modo per gestire incorporato =, si può vedere se la parola di fronte ad essa è uno tuoi noti i nomi dei campi - in caso contrario, si può eventualmente trattare la = come incorporato personaggio piuttosto che un operatore. Questo, tuttavia, presuppone che tu abbia un set fisso di campi conosciuti (alcuni dei quali potrebbero non apparire sempre). Questa ipotesi può essere attenuata se si sa che i nomi dei campi sono sensibili al maiuscolo/minuscolo.

+2

"Ciò presuppone che il valore non possa contenere' = '" non abbiamo bisogno di presupposti così forti. Possiamo anche supporre che '=' quale separatore in 'chiave = valore' possa essere preceduto solo da un insieme specifico di parole. Se non ha una parola chiave predefinita prima di essa deve essere parte del valore. – Pshemo

+0

@Pshemo Heh, stavo solo scrivendo - grazie per il tuo commento. :) – xxbbcc

1

È possibile utilizzare un'espressione regolare, catturando tutti i gruppi "fissi" e utilizzando tutto ciò che rimane per info. Questo dovrebbe funzionare anche se la parte info contiene , o = caratteri. Ecco un breve esempio (usando Python, ma non dovrebbe essere un problema ...).

>>> p = r"(type=[A-Z]+), (languageCode=[-A-Z]+), (url=[^,]+), (ref=\d), (info=.+?), (deactivated=(?:true|false))" 
>>> s = "type=INFO, languageCode=EN-GB, url=http://www.stackoverflow.com, ref=1, info=Text, that may contain all kind of chars, even deactivated=true., deactivated=false" 
>>> re.search(p, s).groups() 
('type=INFO', 
'languageCode=EN-GB', 
'url=http://www.stackoverflow.com', 
'ref=1', 
'info=Text, that may contain all kind of chars, even deactivated=true.', 
'deactivated=false') 

Se uno qualsiasi di questi elementi sono opzionali, si può mettere un ? dopo quei gruppi, e rendere la virgola opzionale. Se l'ordine può essere diverso, allora è più complicato. In questo caso, invece di utilizzare una RegEx per acquisire tutto in una volta, utilizzare più RegEx per acquisire i singoli attributi e quindi rimuovere (sostituire con '') quelli nella stringa prima di trovare la corrispondenza con l'attributo successivo. Infine, corrisponde a info.


Su ulteriore considerazione, dal momento che questi attributi potrebbero avere qualsiasi ordine, può essere più promettente per catturare proprio tutto si estende da una parola chiave per il prossimo, indipendentemente dal suo effettivo contenuto, molto simile alla soluzione di Pshemo:

keys = "type|languageCode|url|ref|info|deactivated" 
p = r"({0})=(.+?)(?=\, (?:{0})=|$)".format(keys) 
matches = re.findall(p, s) 

Ma anche questo potrebbe non riuscire in alcuni casi molto oscuri, ad esempio se l'attributo info contiene qualcosa come ', ref=foo', inclusa la virgola. Tuttavia, sembra non esserci alcun modo per aggirare queste ambiguità. Se avevi una stringa come info=in this string, ref=1, and in another, ref=2, ref=1, contiene un attributo ref o tre o nessuno?