2015-06-03 19 views
5

Mam tu do czynienia z problemem spowodowanym przez brudny projekt. Otrzymuję listę ciągów i chcę parsować atrybuty z niej. Niestety nie mogę zmienić źródła, w którym zostały utworzone te String.Wyciąg atrybuty ciągu

Przykład:

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" 

Teraz chcę wyodrębnić atrybutów type, languageCode, url, ref, info i deactivated.

Problemem jest tutaj pole info, którego tekst nie jest ograniczony przez cudzysłowy. W tym polu mogą również wystąpić przecinki, więc nie mogę używać przecinka na końcu łańcucha, aby dowiedzieć się, gdzie kończy się koniec.

Dodatkowo, te ciągi nie zawsze zawierają wszystkie atrybuty. type, info i deactivated są zawsze obecne, reszta jest opcjonalna.

Jakieś sugestie, w jaki sposób mogę rozwiązać ten problem?

+0

Czy kolejność tych elementów stałe? – Pshemo

+1

Co powiesz na wyszukanie '=', a następnie wybierz jedno słowo przed nim jako nazwę pola. Wszystko po '=' do następnego pola jest wartością. Zakłada się, że wartość nie może zawierać "=" - jeśli to możliwe, nie masz zbyt wiele do zrobienia. – xxbbcc

+2

Jeśli wszystkie atrybuty _other_ mają nieco przewidywalny format, możesz je usunąć i zabrać wszystko, co zostało dla 'info' ... –

Odpowiedz

2

zakładając, że kolejność elementów jest stałe można napisać rozwiązanie przy użyciu regex jak ten jeden

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")); 
} 

wyjściowa:

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 szukasz oneOfPossibleKeys=value gdzie value kończy się na:

  • , oneOfPossibleKeys=
  • lub ma koniec sznurka po (reprezentowany przez $).

Kod:

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")); 
} 

wyjściowa:

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

Miałem podobny pomysł jak twoja wersja 2. Ale dlaczego nie używasz 'keysStrRegex' dla właściwego klucza, a więc zamiast' \\ w + '? –

+0

@tobias_k To jest bardzo dobre pytanie. Odpowiedź zaktualizowana. – Pshemo

4

Jednym z możliwych rozwiązań jest wyszukiwanie znaków = na wejściu, a następnie pojedyncze słowo bezpośrednio przed nim jako nazwy pola - wydaje się, że wszystkie nazwy pól są pojedynczymi słowami (bez białych znaków). Jeśli tak jest, wówczas możesz wziąć wszystko po =, aż do następnej nazwy pola (rozliczanie rozdzielania ,) jako wartości.

Zakłada się, że wartość nie może zawierać =.

Edit:

Jako możliwy sposób, aby uchwyt osadzony =, można sprawdzić, czy słowo przed nim jest jedną swoich znanych nazwisk polu - jeśli nie, można ewentualnie traktują = jako osadzonego raczej charakter niż operator. To jednak zakłada, że ​​masz ustalony zestaw znanych pól (niektóre z nich nie zawsze mogą się pojawić). To założenie może zostać złagodzone, jeśli wiesz, że w nazwach pól rozróżniana jest wielkość liter.

+2

"Zakłada się, że wartość nie może zawierać" = "" nie potrzebujemy tak silnego założenia. Możemy także założyć, że '=', który jest separatorem w 'key = value' może być poprzedzony tylko określonym zestawem słów. Jeśli nie ma wcześniej zdefiniowanego słowa kluczowego, musi być częścią wartości. – Pshemo

+0

@Pshemo Heh, właśnie to pisałem - dziękuję za komentarz. :) – xxbbcc

1

Możesz użyć wyrażenia regularnego, przechwytując wszystkie "ustalone" grupy i używając tego, co zostało na info. Powinno to nawet zadziałać, jeśli część info zawiera znaki , lub =. Oto krótki przykład (używając Pythona, ale to nie powinno być problemem ...).

>>> 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') 

Jeśli którykolwiek z tych elementów są opcjonalne, można umieścić ? po tych grup, i sprawiają, że przecinek opcjonalne. Jeśli zamówienie może być inne, to jest bardziej skomplikowane. W tym przypadku, zamiast używać jednego RegEx do przechwytywania wszystkiego na raz, użyj kilku RegExes, aby przechwycić poszczególne atrybuty, a następnie usuń (zamień na '') elementy w łańcuchu przed dopasowaniem następnego atrybutu. Wreszcie, dopasuj info.


Na dalszych rozważań, biorąc pod uwagę, że te cechy mogą mieć dowolną kolejność, może być bardziej obiecujące, aby uchwycić tylko wszystko rozciąga się od jednego słowa kluczowego do następnego, niezależnie od jego rzeczywistej zawartości, bardzo podobny do rozwiązania Pshemo za:

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

Ale to też może się nie udać w niektórych bardzo niejasnych przypadkach, np. jeśli atrybut info zawiera coś w rodzaju ', ref=foo', w tym przecinek. Jednak wydaje się, że nie ma możliwości obejścia tych dwuznaczności. Jeśli masz ciąg znaków, taki jak info=in this string, ref=1, and in another, ref=2, ref=1, czy zawiera on jeden atrybut ref lub trzy, czy nie ma go wcale?