Pregunta Extraer atributos de una cadena


Tengo que tratar aquí un problema causado por un diseño sucio. Obtengo una lista de cadenas y quiero analizar los atributos de ella. Lamentablemente, no puedo cambiar la fuente, donde se crearon estas cadenas.

Ejemplo:

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"

Ahora quiero extraer los atributos type, languageCode, url, ref, info y deactivated.

El problema aquí es el campo. info, cuyo texto no está limitado por comillas. También pueden aparecer comas en este campo, así que no puedo usar la coma al final de la cadena para averiguar dónde termina.

Adicional, esas cadenas no siempre contienen todos los atributos. type, info y deactivated están siempre presentes, el resto es opcional.

¿Alguna sugerencia de cómo puedo resolver este problema?


5
2018-06-03 21:35


origen


Respuestas:


Suponiendo que el orden de los elementos sea fijo, podría escribir una solución utilizando expresiones regulares como esta.

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

Salida:

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

EDITAR: Version2 regex buscando oneOfPossibleKeys=value dónde value termina con:

  • , oneOfPossibleKeys= 
  • o tiene el final de la cadena después de ella (representado por $)

Código:

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

Salida:

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

2
2018-06-03 21:59



Una posible solución es buscar = los caracteres en la entrada y luego tome la única palabra inmediatamente anterior como el nombre del campo; parece que todos los nombres de sus campos son palabras simples (sin espacios en blanco). Si ese es el caso, puedes tomar todo después de la = hasta el próximo nombre de campo (que representa la separación ,) como el valor.

Esto supone que el valor no puede contener =.

Editar:

Como posible forma de manejar embebidos. =, puede ver si la palabra que hay delante es uno de sus nombres de campo conocidos; de lo contrario, puede tratar el = como un personaje incrustado en lugar de un operador. Sin embargo, esto supone que tiene un conjunto fijo de campos conocidos (algunos de los cuales no siempre aparecen). Esta suposición puede simplificarse si sabe que los nombres de los campos distinguen entre mayúsculas y minúsculas.


4
2018-06-03 21:42



Podría usar una expresión regular, capturar todos los grupos "fijos" y usar lo que quede para info. Esto debería funcionar incluso si el info parte contiene , o = caracteres. Aquí hay un ejemplo rápido (usando Python, pero eso no debería ser 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')

Si alguno de esos elementos es opcional, puede poner un ? después de esos grupos, y haz la coma opcional. Si el orden puede ser diferente, entonces es más complicado. En este caso, en lugar de usar un RegEx para capturar todo a la vez, use varios RegEx para capturar los atributos individuales y luego elimínelos (reemplace con '') aquellos en la cadena antes de coincidir con el siguiente atributo. Por último, partido info.


Considerándolo más detenidamente, dado que esos atributos pueden tener cualquier orden, puede ser más prometedor capturar todo lo que se extiende de una palabra clave a otra, independientemente de su contenido real, muy similar a la solución de Pshemo:

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

Pero esto también puede fallar en algunos casos muy oscuros, p. Si el info atributo contiene algo como ', ref=foo', incluida la coma. Sin embargo, parece que no hay manera de evitar esas ambigüedades. Si tuvieras una cuerda como info=in this string, ref=1, and in another, ref=2, ref=1, ¿contiene uno ref atributo, o tres, o ninguno en absoluto?


1
2018-06-03 21:49