¿La mejor manera de analizar la cadena de URL para obtener valores para las claves?

81

Necesito analizar una cadena de URL como esta:

&ad_eurl=http://www.youtube.com/video/4bL4FI1Gz6s&hl=it_IT&iv_logging_level=3&ad_flags=0&endscreen_module=http://s.ytimg.com/yt/swfbin/endscreen-vfl6o3XZn.swf&cid=241&cust_gender=1&avg_rating=4.82280613104

Necesito dividir NSString en partes signle como cid=241y &avg_rating=4.82280613104. He estado haciendo esto con substringWithRange:pero los valores regresan en un orden aleatorio, así que eso lo estropea. ¿Existe alguna clase que permita un análisis fácil donde básicamente pueda convertirlo a NSDictionary para poder leer el valor de una clave (por ejemplo, ValueForKey: ciddebería regresar 241)? ¿O hay otra forma más fácil de analizarlo que usarlo NSMakeRangepara obtener una subcadena?

JonasG
fuente

Respuestas:

121

editar (junio de 2018): esta respuesta es mejor . Apple agregado NSURLComponentsen iOS 7 .

Crearía un diccionario, obtendría una matriz de pares clave / valor con

NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] init];
NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"];

Luego complete el diccionario:

for (NSString *keyValuePair in urlComponents)
{
    NSArray *pairComponents = [keyValuePair componentsSeparatedByString:@"="];
    NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
    NSString *value = [[pairComponents lastObject] stringByRemovingPercentEncoding];

    [queryStringDictionary setObject:value forKey:key];
}

A continuación, puede consultar con

[queryStringDictionary objectForKey:@"ad_eurl"];

Esto no está probado y probablemente debería hacer más pruebas de error.

Thomas Joulin
fuente
13
No se trata de decodificar URL de los componentes. Tampoco está haciendo la verificación de límites en la matriz pairComponents antes de hacer objectAtIndex: 1 (¿y si no hay ningún objeto en el índice 1?)
Yetanotherjosh
8
el uso de "componentsSeparatedByString" no es correcto para dividir una clave de un valor en una cadena de consulta. "=" se incluyen con bastante frecuencia en los valores (por ejemplo, tokens codificados en base64).
Donleyp
1
Un poco de optimización: NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"]; NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] initWithCapacity: urlComponents.count];
adnako
3
Si algún valor contiene el carácter "=" se rompe la lógica de poblado. Podría reemplazar parte del código porNSRange range = [keyValuePair rangeOfString:@"="]; NSString *key = [keyValuePair substringToIndex:range.location]; NSString *value = [keyValuePair substringFromIndex:range.location+1];
Renato Silva Das Neves
1
Esto no obtiene el primer artículo.
Keith Adler
166

También respondí esto en https://stackoverflow.com/a/26406478/215748 .

Puede usar queryItemsen URLComponents.

Cuando obtiene el valor de esta propiedad, la clase NSURLComponents analiza la cadena de consulta y devuelve una matriz de objetos NSURLQueryItem, cada uno de los cuales representa un único par clave-valor, en el orden en que aparecen en la cadena de consulta original.

let url = "http://example.com?param1=value1&param2=param2"
let queryItems = URLComponents(string: url)?.queryItems
let param1 = queryItems?.filter({$0.name == "param1"}).first
print(param1?.value)

Alternativamente, puede agregar una extensión en la URL para facilitar las cosas.

extension URL {
    var queryParameters: QueryParameters { return QueryParameters(url: self) }
}

class QueryParameters {
    let queryItems: [URLQueryItem]
    init(url: URL?) {
        queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? []
        print(queryItems)
    }
    subscript(name: String) -> String? {
        return queryItems.first(where: { $0.name == name })?.value
    }
}

A continuación, puede acceder al parámetro por su nombre.

let url = URL(string: "http://example.com?param1=value1&param2=param2")!
print(url.queryParameters["param1"])
Onato
fuente
47

Llego un poco tarde, pero las respuestas proporcionadas hasta ahora no funcionaron como necesitaba. Puede utilizar este fragmento de código:

NSMutableDictionary *queryStrings = [[NSMutableDictionary alloc] init];
for (NSString *qs in [url.query componentsSeparatedByString:@"&"]) {
    // Get the parameter name
    NSString *key = [[qs componentsSeparatedByString:@"="] objectAtIndex:0];
    // Get the parameter value
    NSString *value = [[qs componentsSeparatedByString:@"="] objectAtIndex:1];
    value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

    queryStrings[key] = value;
}

¿Dónde urlestá la URL que desea analizar? Tiene todas las cadenas de consulta, escapadas, en el queryStringsdiccionario mutable.

EDITAR : Versión Swift:

var queryStrings = [String: String]()
if let query = url.query {
    for qs in query.componentsSeparatedByString("&") {
        // Get the parameter name
        let key = qs.componentsSeparatedByString("=")[0]
        // Get the parameter value
        var value = qs.componentsSeparatedByString("=")[1]
        value = value.stringByReplacingOccurrencesOfString("+", withString: " ")
        value = value.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!

        queryStrings[key] = value
    }
}
Nikolovski
fuente
2
Esta es una buena respuesta, pero no la mejor. Debe tener cuidado cuando los parámetros son algo así como cadenas codificadas en base64. Como dijo @donleyp, el carácter '=' puede ser un valor normal. Por ejemplo, la cadena codificada en base64 necesita relleno con el carácter '='.
khcpietro
@Aigori me corrige si me equivoco, pero me parece que esta respuesta es compatible con base64. =utilizado para el relleno no aparecería como =hasta que se decodificara al final. Sin embargo, no admite parámetros de consulta sin un valor.
Boris
@Boris Por ejemplo, decodifica esta URL http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==con el código de respuesta. Volverá ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQno ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==. Por supuesto, algunos decodificadores base64 decodifican correctamente, pero, por ejemplo, NSData initWithBase64EncodedString:options:devuelven una cadena vacía sin relleno =. Por lo tanto, debe tener cuidado cuando maneje la cadena base64 con el código de respuesta.
khcpietro
@Aigori, sí, tienes razón, pero en este caso la url no lo sería http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==, sería http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ%3D%3Dcomo el valor debería estar codificado como url
Boris
10

Para iOS8 y superior usando NSURLComponents:

+(NSDictionary<NSString *, NSString *> *)queryParametersFromURL:(NSURL *)url {
    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
    NSMutableDictionary<NSString *, NSString *> *queryParams = [NSMutableDictionary<NSString *, NSString *> new];
    for (NSURLQueryItem *queryItem in [urlComponents queryItems]) {
        if (queryItem.value == nil) {
            continue;
        }
        [queryParams setObject:queryItem.value forKey:queryItem.name];
    }
    return queryParams;
}

Para iOS 8 a continuación:

+(NSDictionary<NSString *, NSString *> *)queryParametersFromURL:(NSURL *)url    
    NSMutableDictionary<NSString *, NSString *> * parameters = [NSMutableDictionary<NSString *, NSString *> new];
    [self enumerateKeyValuePairsFromQueryString:url.query completionblock:^(NSString *key, NSString *value) {
        parameters[key] = value;
    }];
    return parameters.copy;
}

- (void)enumerateKeyValuePairsFromQueryString:(NSString *)queryString completionBlock:(void (^) (NSString *key, NSString *value))block {
    if (queryString.length == 0) {
        return;
    }
    NSArray *keyValuePairs = [queryString componentsSeparatedByString:@"&"];
    for (NSString *pair in keyValuePairs) {
        NSRange range = [pair rangeOfString:@"="];
        NSString *key = nil;
        NSString *value = nil;

        if (range.location == NSNotFound) {
            key = pair;
            value = @"";
        }
        else {
            key = [pair substringToIndex:range.location];
            value = [pair substringFromIndex:(range.location + range.length)];
        }

        key = [self decodedStringFromString:key];
        key = key ?: @"";

        value = [self decodedStringFromString:value];
        value = value ?: @"";

        block(key, value);
    }
}

+ (NSString *)decodedStringFromString:(NSString *)string {
    NSString *input = shouldDecodePlusSymbols ? [string stringByReplacingOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, string.length)] : string;
    return [input stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
Yatheesha BL
fuente
6

Si desea hacer lo mismo en Swift, puede usar una extensión.

extension NSURL {
    func queryDictionary() -> [String:String] {
        let components = self.query?.componentsSeparatedByString("&")
        var dictionary = [String:String]()

        for pairs in components ?? [] {
            let pair = pairs.componentsSeparatedByString("=")
            if pair.count == 2 {
                dictionary[pair[0]] = pair[1]
            }
        }

        return dictionary
    }
}
apouche
fuente
4

Si está utilizando NSURLComponentsla siguiente extensión concisa también funcionará:

extension NSURLComponents {

    func getQueryStringParameter(name: String) -> String? {
        return (self.queryItems? as [NSURLQueryItem])
            .filter({ (item) in item.name == name }).first?
            .value()
    }

}
Gilles De Mey
fuente
1
Y en Swift 2, func getQueryStringParameter(name: String) -> String? { return self.queryItems?.filter({ (item) in item.name == name }).first?.value }
Jedidja
4

Desde iOS 8 puede usar propiedades directamente namey así valuesucesivamente NSURLQueryItem.

Ejemplo, cómo analizar la URL y obtener valores específicos para una clave en pares analizados.

NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:@"someURL" resolvingAgainstBaseURL:false];
NSArray *queryItems = urlComponents.queryItems;
NSMutableArray *someIDs = [NSMutableArray new];
for (NSURLQueryItem *item in queryItems) {
    if ([item.name isEqualToString:@"someKey"]) {
        [someIDs addObject:item.value];
    }
}
NSLog(@"%@", someIDs);
Jakub Truhlář
fuente
4

Rápido 5

extension URL {
    func queryParams() -> [String:String] {
        let queryItems = URLComponents(url: self, resolvingAgainstBaseURL: false)?.queryItems
        let queryTuples: [(String, String)] = queryItems?.compactMap{
            guard let value = $0.value else { return nil }
            return ($0.name, value)
        } ?? []
        return Dictionary(uniqueKeysWithValues: queryTuples)
    }
}
Wujo
fuente
3

Este código funciona en tres casos.

1. http://www.youtube.com/watch?v=VWsl7C-y7EI&feature=youtu.be 2. http://youtu.be/lOvcFqQyaDY
3. http://www.youtube.com/watch?v= VWsl7C-y7EI

NSArray *arr = [youtubeurl componentsSeparatedByString:@"v="];
 NSString *youtubeID;
if([arr count]>0)
{
    if([arr count]==1){
        youtubeID= [[youtubeurl componentsSeparatedByString:@"/"] lastObject];

    }
    else{
        NSArray *urlComponents = [[arr objectAtIndex:1] componentsSeparatedByString:@"&"];
        youtubeID=[urlComponents objectAtIndex:0];
    }
}
Rinju Jain
fuente
1
Estaba buscando específicamente una solución para extraer el ID de YouTube y me había olvidado de tener en cuenta el hecho de que existen múltiples variaciones de URL válidas de YouTube. +1 ¡Gracias!
Seoras
3
-(NSArray *)getDataOfQueryString:(NSString *)url{
    NSArray *strURLParse = [url componentsSeparatedByString:@"?"];
    NSMutableArray *arrQueryStringData = [[NSMutableArray alloc] init];
    if ([strURLParse count] < 2) {
        return arrQueryStringData;
    }
    NSArray *arrQueryString = [[strURLParse objectAtIndex:1] componentsSeparatedByString:@"&"];

    for (int i=0; i < [arrQueryString count]; i++) {
        NSMutableDictionary *dicQueryStringElement = [[NSMutableDictionary alloc]init];
        NSArray *arrElement = [[arrQueryString objectAtIndex:i] componentsSeparatedByString:@"="];
        if ([arrElement count] == 2) {
            [dicQueryStringElement setObject:[arrElement objectAtIndex:1] forKey:[arrElement objectAtIndex:0]];
        }
        [arrQueryStringData addObject:dicQueryStringElement];
    }

    return arrQueryStringData; 
}

Esta función solo tiene que pasar URL y obtendrá todo el elemento de la cadena de consulta.

Desarrollador loco
fuente
3

Una solución tardía a esto en forma de una extensión Swift 3 en URL

extension URL {

  func value(for paramater: String) -> String? {

    let queryItems = URLComponents(string: self.absoluteString)?.queryItems
    let queryItem = queryItems?.filter({$0.name == paramater}).first
    let value = queryItem?.value

    return value
  }

}

esencia

Damo
fuente
2

Como función completa:

+ (NSString *)getQueryComponentWithName:(NSString *)name  fromURL:(NSURL *)url{

NSString *component = nil;
if (url) {
    NSString *query = url.query;

    NSMutableDictionary *queryStringDictionary = [NSMutableDictionary dictionary];
    NSArray *urlComponents = [query componentsSeparatedByString:@"&"];

    for (NSString *keyValuePair in urlComponents){

        NSArray *pairComponents = [keyValuePair componentsSeparatedByString:@"="];
        NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
        NSString *value = [[pairComponents lastObject] stringByRemovingPercentEncoding];

        [queryStringDictionary setObject:value forKey:key];
    }

    component = [queryStringDictionary objectForKey:name];
}

return component;
}
[self getQueryComponentWithName:@"example" fromURL:[NSURL URLWithString:@"https://google.es/?example=test"]];
93sauu
fuente
Tiene un pequeño error tipográfico en el ejemplo, los corchetes están mal colocados
Vaiden
2

Para obtener los parámetros de consulta como un dictado:

extension URL {
    var parameters: [String: String] {
        var parameters = [String: String]()
        if let urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: false),
            let queryItems = urlComponents.queryItems {
            for queryItem in queryItems where queryItem.value != nil {
                parameters[queryItem.name] = queryItem.value
            }
        }
        return parameters
    }
}

o devolver Opcional si esta más conveniente en tu caso.

Oleksandr Buravlyov
fuente
0

La propiedad de consulta en NSURL proporcionará la cadena de consulta. Luego puede analizar la cadena de consulta usando componentsSeparatedByString

    NSArray *parameters = [[url query] componentsSeparatedByString:@"&"];

    NSMutableDictionary *keyValuePairs = [NSMutableDictionary dictionary];

    for (NSString *eachParam in parameters)
    {
        NSArray *QryParts = [eachParam componentsSeparatedByString:@"="];
        if ( [QryParts count] == 2 )
        {
            keyValuePairs[QryParts[0]] = QryParts[1];
        }
        else
        {
            keyValuePairs[QryParts[0]] = QryParts[0];
        }
    }

NSString * name = [keyValuePairs valueForKey:@"name"];
NSString * username = [keyValuePairs valueForKey:@"username"];
Joe M
fuente
0
- (NSString *)getLoginTokenFromUrl:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSArray *queryStrings = [url.query componentsSeparatedByString:@"&"];

    NSMutableDictionary *queryParams = [[NSMutableDictionary alloc] init];
    for (NSString *qs in queryStrings) {
        // Get the parameter name
        NSArray *components = [qs componentsSeparatedByString:@"="];
        NSString *key = [components objectAtIndex:0];

        // Get the parameter value
        NSString *value;
        if (components.count > 1) {
            value = [components objectAtIndex:1];
        }
        else {
            value = @"";
        }
        value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
        value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

        queryParams[key] = value;
    }

    return [queryParams objectForKey:@"login_token"];
}
Yogesh Maheshwari
fuente
0

Un enfoque Swift 2:

extension NSURL {

  var queryDictionary: [String: String] {
    var queryDictionary = [String: String]()
    guard let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false), queryItems = components.queryItems else { return queryDictionary }
    queryItems.forEach { queryDictionary[$0.name] = $0.value }
    return queryDictionary
  }

}

Descarga Gist

Scott Gardner
fuente