2011-07-26 30 views
30

Tiêu đề chấp nhận ngôn ngữ theo yêu cầu thường là một chuỗi phức tạp dài -Tiêu đề chấp nhận ngôn ngữ phân tích cú pháp trong Java

Ví dụ:

Accept-Language : en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2 

Có cách nào đơn giản để phân tích cú pháp trong java không? Hoặc một API để giúp tôi làm điều đó?

+2

Nó không phải là thực sự là phức tạp: bạn chia phần sau dấu hai chấm bằng dấu phẩy, sau đó tìm kiếm một dấu chấm phẩy trong mỗi nhóm, sau đó phân tích cú pháp mã ngôn ngữ và các yếu tố q. –

+0

Và các mã ngôn ngữ có xu hướng tương ứng với 'java.util.Locale' sau khi bạn thay thế ''-'' bằng '' _''s. –

+4

Bạn có thực sự cần phải phân tích cú pháp nó, hoặc bạn có thể sử dụng [Http] ServletRequest.getLocale [s] và để cho container xử lý sự phức tạp? –

Trả lời

37

Tôi khuyên bạn nên sử dụng ServletRequest.getLocales để cho phép phân tích cú pháp vùng chứa Accept-Language thay vì cố tự quản lý độ phức tạp.

+3

Trừ khi bạn đang lập kế hoạch hỗ trợ trực tiếp mọi miền địa phương có thể, ServerRequest.getLocales có lẽ là một lựa chọn tốt hơn. –

+0

@JeremyList Đã cập nhật để đề xuất getLocales, cảm ơn. –

+0

Vấn đề là 'ServletRequest.getLocales' trả về miền địa phương máy chủ nếu người dùng không cung cấp địa chỉ hợp lệ. Để ngăn chặn các yêu cầu spam ngôn ngữ, bạn phải phân tích cú pháp chính nó trong đó 'LanguageRange.parse (String)' thuận tiện. – djmj

14

Dưới đây là một cách khác để phân tích header Accept-Language mà không đòi hỏi một container servlet:

String header = "en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2"; 
for (String str : header.split(",")){ 
    String[] arr = str.trim().replace("-", "_").split(";"); 

    //Parse the locale 
    Locale locale = null; 
    String[] l = arr[0].split("_"); 
    switch(l.length){ 
     case 2: locale = new Locale(l[0], l[1]); break; 
     case 3: locale = new Locale(l[0], l[1], l[2]); break; 
     default: locale = new Locale(l[0]); break; 
    } 

    //Parse the q-value 
    Double q = 1.0D; 
    for (String s : arr){ 
     s = s.trim(); 
     if (s.startsWith("q=")){ 
      q = Double.parseDouble(s.substring(2).trim()); 
      break; 
     } 
    } 

    //Print the Locale and associated q-value 
    System.out.println(q + " - " + arr[0] + "\t " + locale.getDisplayLanguage()); 
} 

Bạn có thể tìm thấy một lời giải thích của header Accept-Language và liên q-giá trị ở đây:

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

Rất cảm ơn Karl Knechtel và Mike Samuel. Thier bình luận cho câu hỏi ban đầu đã giúp tôi đi đúng hướng.

23

Đối với hồ sơ, bây giờ nó có thể với Java 8:

Locale.LanguageRange.parse() 
+1

Và nếu bạn muốn danh sách các ngôn ngữ, bạn có thể sử dụng ' Locale.LanguageRange.parse (requestLangs) .stream(). Được sắp xếp (Comparator.comparing (Locale.LanguageRange :: getWeight) .reversed()) Bản đồ (range -> new Locale (range.getRange())). (Collectors.toList()); ' – Alex

+1

@Alex: Theo javadoc, bạn không cần phải sắp xếp lại' Danh sách': "Không giống như một danh sách có trọng số, các phạm vi ngôn ngữ trong một danh sách được ưu tiên được sắp xếp theo thứ tự giảm dần về mức độ ưu tiên của nó. Phạm vi ngôn ngữ đầu tiên có mức độ ưu tiên cao nhất và đáp ứng được ưu tiên của người dùng nhiều nhất. ". Vì vậy, mã của bạn có thể chỉ đơn giản là: 'Locale.LanguageRange.parse (requestedLangs) .stream(). Map (range -> new Locale (range.getRange())) thu thập (Collectors.toList());' – FBB

+0

Điều này không không trả lại ngôn ngữ được phân tích cú pháp chính xác, .eg "en-GB" sẽ được phân tích cú pháp thành ngôn ngữ có tên "en-gb" không có quốc gia. –

3

ServletRequest.getLocale() chắc chắn là lựa chọn tốt nhất nếu nó có sẵn và không bị ghi đè như một số khuôn khổ làm.

Đối với tất cả các trường hợp khác, Java 8 cung cấp Locale.LanguageRange.parse() như đã đề cập trước đây bởi Quiang Li. Tuy nhiên, điều này chỉ trả về một Chuỗi Ngôn ngữ, không phải là một Ngôn ngữ. Để phân tích các chuỗi ngôn ngữ mà bạn có thể sử dụng Locale.forLanguageTag() (có sẵn từ phiên bản Java 7):

final List<Locale> acceptedLocales = new ArrayList<>(); 
    final String userLocale = request.getHeader("Accept-Language"); 
    if (userLocale != null) { 
     final List<LanguageRange> ranges = Locale.LanguageRange.parse(userLocale); 

     if (ranges != null) { 
      ranges.forEach(languageRange -> { 
       final String localeString = languageRange.getRange(); 
       final Locale locale = Locale.forLanguageTag(localeString); 
       acceptedLocales.add(locale); 
      }); 
     } 
    } 
    return acceptedLocales; 
+0

Điều này vẫn cho phép một cá thể Locale nonsene như '" test "' được sử dụng bởi các yêu cầu spam vì 'LanguageRange.parse' chỉ kiểm tra các quy tắc ngôn ngữ IMA và không đồng bộ. Bạn cần phải kiểm tra miền địa phương với các ngôn ngữ hợp lệ như 'Locale.getAvailableLocales()' để chắc chắn nó là hợp lệ. – djmj

0
Locale.forLanguageTag("en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2") 
+0

Tôi chỉ làm việc cho một Chuỗi, ví dụ: Locale.forLanguageTag ("vi") – Marx

+0

@Marx bạn đã kiểm tra nó bằng cách nào? Tôi kiểm tra lại nó hoạt động cho tôi. – mokshino

+0

có ví dụ hoạt động goo.gl/kCP8Cj – mokshino

2

Chúng tôi đang sử dụng khởi động mùa xuân và Java 8. này hoạt động

Trong ApplicationConfig.java viết này

@Bean 

public LocaleResolver localeResolver() { 
    return new SmartLocaleResolver(); 
} 

và tôi có danh sách này trong lớp hằng số có ngôn ngữ mà chúng tôi hỗ trợ

List<Locale> locales = Arrays.asList(new Locale("en"), 
             new Locale("es"), 
             new Locale("fr"), 
             new Locale("es", "MX"), 
             new Locale("zh"), 
             new Locale("ja")); 

và viết logic trong lớp dưới đây.

public class SmartLocaleResolver extends AcceptHeaderLocaleResolver { 
      @Override 
     public Locale resolveLocale(HttpServletRequest request) { 
      if (StringUtils.isBlank(request.getHeader("Accept-Language"))) { 
      return Locale.getDefault(); 
      } 
      List<Locale.LanguageRange> ranges = Locale.LanguageRange.parse("da,es-MX;q=0.8"); 
      Locale locale = Locale.lookup(ranges, locales); 
      return locale ; 
     } 
} 
1

Các giải pháp trên thiếu một số loại xác thực. Sử dụng ServletRequest.getLocale() trả về miền địa phương máy chủ nếu người dùng không cung cấp địa chỉ hợp lệ.

trang web của chúng tôi gần đây đã nhận được yêu cầu thư rác với nhiều Accept-Language heades như:

  1. secret.google.com
  2. o-o-8-o-o.com search shell is much better than google!
  3. Google officially recommends o-o-8-o-o.com search shell!
  4. Vitaly rules google ☆*:。゜゚・*ヽ(^ᴗ^)ノ*・゜゚。:*☆ ¯\_(ツ)_/¯(ಠ益ಠ)(ಥ‿ಥ)(ʘ‿ʘ)ლ(ಠ_ಠლ)(͡° ͜ʖ ͡°)ヽ(゚Д゚)ノʕ•̫͡•ʔᶘ ᵒᴥᵒᶅ(=^ ^=)oO

thực hiện này có thể agains kiểm tra bắt buộc t danh sách được hỗ trợ hợp lệ Locale. Nếu không có kiểm tra này, yêu cầu đơn giản với "test" hoặc (2, 3, 4) vẫn bỏ qua xác thực chỉ cú pháp của LanguageRange.parse(String).

Tùy chọn này cho phép giá trị trống và giá trị rỗng cho phép trình thu thập thông tin của công cụ tìm kiếm.

Servlet Lọc

final String headerAcceptLanguage = request.getHeader("Accept-Language"); 

// check valid 
if (!HttpHeaderUtils.isHeaderAcceptLanguageValid(headerAcceptLanguage, true, Locale.getAvailableLocales())) 
    return; 

Utility

/** 
* Checks if the given accept-language request header can be parsed.<br> 
* <br> 
* Optional the parsed LanguageRange's can be checked against the provided 
* <code>locales</code> so that at least one locale must match. 
* 
* @see LanguageRange#parse(String) 
* 
* @param acceptLanguage 
* @param isBlankValid Set to <code>true</code> if blank values are also 
*   valid 
* @param locales Optional collection of valid Locale to validate any 
*   against. 
* 
* @return <code>true</code> if it can be parsed 
*/ 
public static boolean isHeaderAcceptLanguageValid(final String acceptLanguage, final boolean isBlankValid, 
    final Locale[] locales) 
{ 
    // allow null or empty 
    if (StringUtils.isBlank(acceptLanguage)) 
     return isBlankValid; 

    try 
    { 
     // check syntax 
     final List<LanguageRange> languageRanges = Locale.LanguageRange.parse(acceptLanguage); 

     // wrong syntax 
     if (languageRanges.isEmpty()) 
      return false; 

     // no valid locale's to check against 
     if (ArrayUtils.isEmpty(locales)) 
      return true; 

     // check if any valid locale exists 
     for (final LanguageRange languageRange : languageRanges) 
     { 
      final Locale locale = Locale.forLanguageTag(languageRange.getRange()); 

      // validate available locale 
      if (ArrayUtils.contains(locales, locale)) 
       return true; 
     } 

     return false; 
    } 
    catch (final Exception e) 
    { 
     return false; 
    } 
} 
Các vấn đề liên quan