힘을 잃지 않고 읽을 수있는 정규식?


77

많은 프로그래머들은 빠른 정규 표현식을 내놓는 기쁨을 알고 있습니다. 요즘에는 종종 웹 서비스의 도움을 받거나보다 전통적인 대화식 프롬프트를 사용하거나 개발중인 정규 표현식이있는 작은 스크립트를 작성하고 테스트 사례를 수집하는 경우가 많습니다 . 어쨌든 프로세스는 반복적이고 상당히 빠릅니다. 암호처럼 보이는 문자열을 해킹하여 원하는 것을 일치시키고 캡처하고 원하지 않는 것을 거부합니다.

간단한 경우 결과는 Java 정규 표현식과 같이 다음과 같습니다.

Pattern re = Pattern.compile(
  "^\\s*(?:(?:([\\d]+)\\s*:\\s*)?(?:([\\d]+)\\s*:\\s*))?([\\d]+)(?:\\s*[.,]\\s*([0-9]+))?\\s*$"
);

많은 프로그래머들은 정규 표현식을 편집해야하거나 레거시 코드 기반에서 정규 표현식을 코딩해야하는 어려움을 알고 있습니다. 약간의 편집으로 나누면 정규 표현식보다 위의 정규 표현식을 이해하기가 여전히 쉬우 며 정규 표현식 베테랑은 자신이하는 일을 즉시보아야합니다 (누군가 운동을 원할 경우 게시물 끝의 답변) 스스로 알아내는 것).

그러나 정규 표현식이 실제로 쓰기 전용이 되려면 훨씬 복잡해질 필요가 없으며 부지런 한 문서 ( 모든 복잡한 정규 표현식에 대해 모든 사람 작성하는 문서)를 사용하더라도 정규 표현식을 수정하면 힘든 일. 정규 표현식을 신중하게 단위 테스트하지 않으면 매우 위험한 작업 일 수 있습니다 (그러나 모든 사람 긍정적이고 부정적인 모든 복잡한 정규 표현식에 대한 포괄적 인 단위 테스트를 가지고 있습니다 ...).

짧은 이야기로, 정규 표현식의 힘을 잃지 않고 쓰기 읽기 솔루션 / 대안이 있습니까? 위의 정규 표현식은 다른 접근법으로 어떻게 보일까요? 다국어 솔루션이 가장 좋을지라도 모든 언어는 괜찮지 만 정규 표현식이 다국어입니다.


그런 다음 이전 정규 표현식의 기능은 다음과 같습니다. 형식으로 숫자 문자열을 구문 분석하고 1:2:3.4공백이 허용되고 3필요한 위치 만 각 숫자를 캡처 합니다.


2
wim

24
무엇을 캡처해야하는지 알면 정규식 읽기 / 편집은 실제로 쉽지 않습니다. "주석"이라고하는 대부분의 언어에서 거의 사용되지 않는 기능에 대해 들어 보셨을 것입니다. 무엇을 설명하는 복잡한 정규식 위에 놓지 않으면 나중에 가격을 지불하게됩니다. 또한 코드 검토.
TC1

2
실제로 작은 조각으로 나누지 않고이를 정리하는 두 가지 옵션. 그들의 존재 여부는 언어마다 다릅니다. (1) 확장 줄 정규 표현식. 여기서 정규 표현식의 공백은 무시되고 (이스케이프되지 않는 한) 한 줄 주석 양식이 추가되어 들여 쓰기, 줄 간격 및 주석이있는 논리적 청크로 나눌 수 있습니다. (2) 각각의 괄호에 이름을 지정할 수있는 명명 된 캡처 그룹으로, 자체 문서화를 추가하고 일치하는 해시를 자동으로 채 웁니다 (숫자로 색인화 된 일치 배열 또는 $ N 변수보다 낫습니다.
Ben Lee

3
문제의 일부는 정규식 언어 그 자체이며 디자인에서 잘못된 역사적 선택은 수하물처럼 드래그됩니다. 제정신의 언어에서, 그룹화 괄호는 순전히 구문 분석 트리를 형성하는 구문 장치입니다. 그러나 유닉스로 돌아가는 정규 표현식 구현에는 의미론이 있습니다. 레지스터를 하위 표현식 일치에 바인딩합니다. 따라서 순수한 그룹화를 달성하려면 더 복잡하고 추악한 대괄호가 필요합니다!
Kaz

2
실제로 실제적인 대답은 아니지만 정규 표현식의 힘이 유한 한 오토 마톤의 힘과 같다는 것을 언급하는 것이 유용 할 수 있습니다. 즉, 정규 표현식은 유한 오토 마톤에 의해 검증되고 구문 분석 된 동일한 클래스의 문자열을 검증 / 분석 할 수 있습니다. 따라서 사람이 읽을 수있는 정규식 표현은 아마도 그래프를 빠르게 작성할 수 있어야하며, 대부분의 텍스트 기반 언어가 실제로는 좋지 않다고 생각합니다. 우리가 그런 것들을 위해 시각적 도구를 사용하는 이유입니다. 한 번 봐 가지고 hackingoff.com/compilers/regular-expression-to-nfa-dfa 영감을 얻을 수 있습니다.
damix911

답변:


80

많은 사람들이 더 작은 부분으로 구성한다고 언급했지만 아직 아무도 예를 제공하지 않았으므로 여기에 내 것이 있습니다.

string number = "(\\d+)";
string unit = "(?:" + number + "\\s*:\\s*)";
string optionalDecimal = "(?:\\s*[.,]\\s*" + number + ")?";

Pattern re = Pattern.compile(
  "^\\s*(?:" + unit + "?" + unit + ")?" + number + optionalDecimal + "\\s*$"
);

가장 잘 읽을 수는 없지만 원본보다 명확하다고 생각합니다.

또한 C #에는 @문자 그대로 (이스케이프 문자 없음) 취해야 함을 나타 내기 위해 문자열 앞에 붙일 수있는 연산자 number가 있습니다.@"([\d]+)";


그냥 지금 방법을 모두 발견 [\\d]+하고 [0-9]+바로해야한다 \\d+(물론, 일부는 찾을 수 있습니다 [0-9]+더 읽기). 질문을 편집하지 않겠지 만이 답변을 수정하고 싶을 수도 있습니다.
hyde

@hyde-잘 잡습니다. 기술적으로는 완전히 동일하지 않습니다 \d. 다른 숫자 체계 (중국어, 아랍어 등)에서도 숫자로 간주되는 모든 [0-9]항목과 일치하지만 표준 숫자 만 일치합니다. \\d하지만 에 표준화 하고 optionalDecimal패턴에 반영했습니다 .
Bobson

42

정규식을 문서화하는 핵심은이를 문서화하는 것입니다. 너무 자주 사람들은 선 노이즈로 보이는 것을 던져 버리고 그 곳에 둡니다.

perl/x에서 정규 표현식의 끝에 있는 연산자는 공백을 억제하여 정규 표현식을 문서화 할 수 있습니다.

위의 정규식은 다음과 같습니다.

$re = qr/
  ^\s*
  (?:
    (?:       
      ([\d]+)\s*:\s*
    )?
    (?:
      ([\d]+)\s*:\s*
    )
  )?
  ([\d]+)
  (?:
    \s*[.,]\s*([\d]+)
  )?
  \s*$
/x;

예, 세로 공백을 약간 소비하지만 가독성을 크게 희생하지 않으면 서 공백을 줄일 수 있습니다.

그런 다음 이전 정규 표현식의 기능은 다음과 같습니다. 공백이 허용되고 3 만 필요한 각 숫자를 캡처하여 1 : 2 : 3.4 형식의 숫자 문자열을 구문 분석합니다.

이 정규 표현식을 보면 작동 방식과 작동 방식을 확인할 수 있습니다. 이 경우이 정규식은 문자열과 일치합니다 1.

다른 언어로도 비슷한 방법을 사용할 수 있습니다. python re.VERBOSE 옵션이 작동합니다.

Perl6 (위의 예는 perl5를위한 것임)은 PCRE보다 훨씬 더 강력한 구조로 이끄는 규칙 개념으로이를 더욱 발전시킵니다 (정규 및 확장 규칙보다 다른 문법 (문맥이없고 문맥에 민감 함)을 제공합니다).

Java (이 예제가 나오는 곳)에서 문자열 연결을 사용하여 정규식을 구성 할 수 있습니다.

Pattern re = Pattern.compile(
  "^\\s*"+
  "(?:"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #1
    ")?"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #2
    ")"+
  ")?"+ // First groups match 0 or 1 times
  "([\\d]+)"+ // Capture group #3
  "(?:\\s*[.,]\\s*([0-9]+))?"+ // Capture group #4 (0 or 1 times)
  "\\s*$"
);

분명히, 이것은 "문자열에서 더 많은 것을 생성 하여 혼란을 초래할 수 있으며 (특히 대부분의 IDE에서 구문 강조 표시로) 더 쉽게 읽고 문서화 할 수 있습니다.

핵심은 정규 표현식이 자주 사용하는 힘과 "한 번 쓰기"특성을 인식하는 것입니다. 이 코드를 방어 적으로 피하기 위해 코드를 작성하면 정규식이 명확하고 이해하기 쉬워집니다. 명확성을 위해 Java 코드의 형식을 지정합니다. 언어에서 옵션을 제공 할 때 정규 표현식은 다르지 않습니다.


13
"문서화"와 "줄 바꿈 추가"에는 큰 차이가 있습니다.

4
@JonofAllTrades 코드를 읽을 수있게하는 것이 무엇보다 첫 걸음입니다. 줄 바꿈을 추가하면 동일한 줄에 RE의 해당 하위 집합에 대한 설명을 추가 할 수 있습니다 (한 줄의 정규 표현식 텍스트에서는 더 어려운 것).

2
@JonofAllTrades, 나는 매우 강력하게 동의하지 않습니다. "문서화"와 "줄 바꿈 추가"는 동일한 목적을 수행한다는 점에서 다르지 않으므로 코드를 이해하기 쉽게 만듭니다. 형식이 잘못된 코드의 경우 "줄 바꿈 추가"는 문서를 추가하는 것보다 훨씬 더 나은 목적을 제공합니다.
Ben Lee

2
줄 바꿈을 추가하는 것이 시작이지만 작업의 약 10 %입니다. 다른 답변은 더 자세한 내용을 제공하는데 도움이됩니다.

26

일부 언어 및 라이브러리에서 제공하는 "자세한"모드는 이러한 문제에 대한 해답 중 하나입니다. 이 모드에서는 정규 표현식 문자열의 공백이 제거되므로 (사용해야 함 \s) 주석이 가능합니다. 다음 은 기본적으로 이것을 지원하는 Python 의 간단한 예입니다 .

email_regex = re.compile(r"""
    ([\w\.\+]+) # username (captured)
    @
    \w+         # minimal viable domain part
    (?:\.w+)    # rest of the domain, after first dot
""", re.VERBOSE)

그렇지 않은 모든 언어에서 번역기를 "보통"모드에서 "정상"모드로 구현하는 것은 간단한 작업이어야합니다. 정규 표현식의 가독성에 관심이 있다면 이번 투자를 매우 쉽게 정당화 할 수 있습니다.


15

정규 표현식을 사용하는 모든 언어를 사용하면 간단한 블록으로 구성하여 쉽게 읽을 수 있으며 예제보다 복잡하거나 복잡한 경우 해당 옵션을 반드시 활용해야합니다. Java와 다른 많은 언어의 특별한 문제점은 정규 표현식을 "일류"시민으로 취급하지 않고 대신 문자열 리터럴을 통해 언어에 몰래 들어가야한다는 것입니다. 이것은 실제로 정규 표현식 구문에 속하지 않고 내용을 읽기 어렵게 만드는 많은 따옴표와 백 슬래시를 의미하며, 자신의 미니 언어 및 통역사를 효과적으로 정의하지 않으면 읽을 수 없습니다.

정규 표현식을 통합하는 프로토 타입의 더 나은 방법은 물론 공백 옵션과 정규 표현식 인용 연산자를 사용하는 Perl이었습니다. Perl 6은 정규 표현식을 부분에서 실제 재귀 문법으로 확장하는 개념을 확장하므로 실제로 사용하는 것이 훨씬 좋습니다. 이 언어는 적시성 보트를 놓쳤을 수도 있지만 정규 표현식 지원은 The Good Stuff (tm)였습니다.


1
답의 시작 부분에 언급 된 "간단한 블록"은 단순히 문자열 연결 또는 더 진보 된 것을 의미합니까?
hyde

7
하위 표현식을 더 짧은 문자열 리터럴로 정의하여 의미있는 이름을 가진 로컬 변수에 할당 한 다음 연결하는 것을 의미했습니다. 레이아웃 개선보다 이름이 가독성에 더 중요하다는 것을 알았습니다.
Kilian Foth

11

Expresso를 사용하고 싶습니다 : http://www.ultrapico.com/Expresso.htm

이 무료 응용 프로그램에는 시간이 지남에 따라 유용한 다음과 같은 기능이 있습니다.

  • 정규식을 복사하여 붙여 넣기하면 응용 프로그램이 구문 분석합니다.
  • 정규식이 작성되면 응용 프로그램에서 직접 테스트 할 수 있습니다 (응용 프로그램은 캡처, 교체 목록을 제공합니다 ...)
  • 테스트 한 후에는 C # 코드를 생성하여 구현합니다 (코드에 정규식에 대한 설명이 포함되어 있음).

예를 들어, 방금 제출 한 정규식을 사용하면 다음과 같습니다. 처음에 정규식을 사용한 샘플 화면

물론, 그것을 시도하는 것은 그것을 묘사하는 수천 단어의 가치가 있습니다. 이 응용 프로그램의 편집기와 관련이 있습니다.


4
이에 대해 좀 더 자세히 설명해 주시겠습니까? 질문과 대답에 어떻게 그리고 왜 대답합니까? "링크 전용 답변" 은 Stack Exchange에서 그리 환영받지 못합니다
gnat

5
@gnat 죄송합니다. 당신 말이 맞아요 편집 한 답변이 더 많은 통찰력을 제공하기를 바랍니다.
E. Jaep

9

어떤 경우에는 BNF와 같은 문법을 사용하는 것이 도움이 될 수 있습니다. 정규식보다 훨씬 읽기 쉽습니다. 그런 다음 GoldParser Builder와 같은 도구를 사용하여 문법을 파서로 변환 할 수 있습니다.

BNF, EBNF 등의 문법은 복잡한 정규식보다 읽기 쉽고 작성하기가 훨씬 쉽습니다. GOLD는 그러한 것들을위한 하나의 도구입니다.

아래의 c2 wiki 링크에는 가능한 대안이 나열되어 있으며 이에 대한 토론이 포함되어 있습니다. 기본적으로 문법 엔진 권장 사항을 마무리하는 "참조"링크입니다.

정규식에 대한 대안

"다른 구문을 가진 의미 적으로 동등한 기능"을 의미하기 위해 "대체"를 취하면, RegularExpressions에 대해 / 다음과 같은 대안이 있습니다.

  • 기본 정규식
  • "확장 된"정규식
  • 펄 호환 정규 표현식
  • ... 그리고 다른 많은 변종들 ...
  • SNOBOL 스타일 RE 구문 (SnobolLanguage, IconLanguage)
  • SRE 구문 (RE는 EssExpressions입니다)
  • 다른 FSM 구문
  • 유한 상태 교차 문법 (quite expressive)
  • OMetaLanguage 및 LuaLanguage와 같이 ParsingExpressionGrammars ( http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html )
  • RebolLanguage의 구문 분석 모드
  • 확률 기반 구문 분석 ...

이 링크의 기능과 장점에 대해 더 자세히 설명해 주시겠습니까? "링크 전용 답변" 은 Stack Exchange에서 그리 환영받지 못합니다
gnat

1
프로그래머에 오신 것을 환영합니다, Nick P. downvote / r은 무시하되 @gnat이 연결된 메타 페이지를 읽으십시오.
Christoffer Lette

@ Christoffer Lette 답장을 보내 주셔서 감사합니다. 향후 게시물에서 이것을 명심하려고 노력할 것입니다. @ gnat Paulo Scardine의 의견은 내 게시물의 의도를 반영합니다. BNF, EBNF 등의 문법은 복잡한 정규식보다 읽기 쉽고 작성하기가 훨씬 쉽습니다. GOLD는 그러한 것들을위한 하나의 도구입니다. c2 링크에는 가능한 대안 목록이 있으며, 이에 대한 논의가 포함되어 있습니다. 기본적으로 문법 엔진 추천을 끝내기위한 "참조"링크입니다.
Nick P

6

이것은 오래된 질문이며 구두 표현에 대한 언급이 없으므로 미래의 구직자를 위해 여기에 해당 정보를 추가 할 것이라고 생각했습니다. 구두 표현은 정규식의 상징적 의미를 배울 필요없이 정규식 인간이 이해할 수 있도록 특별히 설계되었습니다. 다음 예를 참조하십시오. 나는 이것이 당신이 요구하는 것을 가장 잘한다고 생각합니다.

// Create an example of how to test for correctly formed URLs
var tester = VerEx()
    .startOfLine()
    .then('http')
    .maybe('s')
    .then('://')
    .maybe('www.')
    .anythingBut(' ')
    .endOfLine();

// Create an example URL
var testMe = 'https://www.google.com';

// Use RegExp object's native test() function
if (tester.test(testMe)) {
    alert('We have a correct URL '); // This output will fire}
} else {
    alert('The URL is incorrect');
}

console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/

이 예제는 자바 스크립트를위한 것이며 , 많은 프로그래밍 언어에 대해이 라이브러리를 찾을 수 있습니다.


2
대단해!
Jeremy Thompson

3

가장 간단한 방법은 아직 정규식을 사용하지만 설명 이름을 가진 간단한 expresssions를 구성에서 식을 구축하는 것입니다 예를 들어 http://www.martinfowler.com/bliki/ComposedRegex.html (그래이 문자열 CONCAT에서입니다)

그러나 대안으로 파서 결합기 라이브러리 (예 : http://jparsec.codehaus.org/) 를 사용하여 완전한 재귀 괜찮은 파서를 얻을 수 있습니다. 다시 여기서의 진정한 힘은 구성 (이번 기능 구성)에서 나옵니다.


3

나는 logstash의 grok 표현을 언급 할 가치가 있다고 생각했습니다 . Grok은 짧은 구문 분석에서 긴 구문 분석 표현을 작성한다는 아이디어를 기반으로합니다. 이들 빌딩 블록을 편리하게 테스트 할 수 및 100로 팔기 전에 포장되어 제공 일반적으로 사용되는 패턴 . 이러한 패턴 외에 모든 정규식 구문을 사용할 수 있습니다.

grok으로 표현 된 위의 패턴은 다음과 같습니다 ( 디버거 앱 에서 테스트 했지만 실수했을 수 있습니다).

"(( *%{NUMBER:a} *:)? *%{NUMBER:b} *:)? *%{NUMBER:c} *(. *%{NUMBER:d} *)?"

선택적 부분과 공간은 평소보다 조금 더 추악하게 보이지만 여기와 다른 경우 모두 grok을 사용하면 인생을 훨씬 좋게 만들 수 있습니다.


2

F #에는 FsVerbalExpressions 모듈이 있습니다. 구두 표현으로 정규식을 작성할 수 있으며 URL과 같은 사전 빌드 된 정규식도 있습니다.

이 구문의 예 중 하나는 다음과 같습니다.

let groupName =  "GroupNumber"

VerbEx()
|> add "COD"
|> beginCaptureNamed groupName
|> any "0-9"
|> repeatPrevious 3
|> endCapture
|> then' "END"
|> capture "COD123END" groupName
|> printfn "%s"

// 123

F # 구문에 익숙하지 않은 경우 groupName은 "GroupNumber"문자열입니다.

그런 다음 "COD (? <GroupNumber> [0-9] {3}) END"로 구성하는 언어 표현식 (VerbEx)을 작성합니다. 그런 다음 문자열 "COD123END"에서 테스트하여 명명 된 캡처 그룹 "GroupNumber"를 얻습니다. 결과는 123입니다.

솔직히 정규 정규 표현식을 이해하기가 훨씬 쉽습니다.


-2

먼저 작동하는 코드가 잘못된 코드임을 이해하십시오. 올바른 코드는 발생한 오류를 정확하게보고해야합니다.

예를 들어, 한 사용자의 계정에서 다른 사용자의 계정으로 현금을 이체하는 기능을 작성하는 경우; "작동 또는 실패"부울을 반환하는 것은 아닙니다. 호출자에게 무엇이 잘못되었는지에 대한 아이디어를 제공하지 않으며 호출자가 사용자에게 올바르게 알리지 못하기 때문입니다. 대신 오류 코드 세트 (또는 예외 세트)가있을 수 있습니다. 대상 계정을 찾을 수 없거나, 소스 계정에 자금이 부족하고, 권한이 거부되었거나, 데이터베이스에 연결할 수 없거나, 너무 많은로드 (나중에 다시 시도) 등 .

이제 "1 : 2 : 3.4 형식의 숫자 문자열 구문 분석"예를 생각해보십시오. 모든 정규 표현식은 사용자에게 적절한 피드백을 제공 할 수없는 "통과 / 실패"를보고합니다 (이 피드백이 로그에 오류 메시지인지 또는 오류가 빨간색으로 표시되는 대화식 GUI인지 여부) 사용자 유형 등). 어떤 유형의 오류가 제대로 설명되지 않습니까? 첫 번째 숫자의 잘못된 문자, 첫 번째 숫자가 너무 큼, 첫 번째 숫자 다음에 콜론 누락 등

"단순히 작동하는 잘못된 코드"를 "적절한 설명 오류를 제공하는 좋은 코드"로 변환하려면 정규식을 여러 개의 작은 정규식 (일반적으로 너무 작은 정규식으로 시작하여 정규식없이 쉽게 수행 할 수있는 정규식)으로 분류해야합니다. ).

코드를 읽기 / 유지 가능하게 만드는 것은 코드를 좋게 만드는 우연한 결과입니다.


6
아마 좋은 가정이 아닙니다. 내 이유는 A)이기 때문에 질문을 다루지 않습니다 ( 읽기 쉬운 방법 ?), B) 정규 표현식 일치 합격 / 불합격이며, 왜 실패했는지 정확히 말할 수있는 지점으로 분류하면 많은 힘과 속도를 잃고 복잡성을 증가시킵니다. C) 일치하지 않을 가능성이 있다는 질문에는 아무런 징후가 없습니다 .Regex를 읽을 수있게 만드는 것에 대한 질문 일뿐입니다. 들어오는 데이터를 제어하거나 미리 확인하면 유효하다고 가정 할 수 있습니다.
Bobson

A) 작은 조각으로 나누면 더 잘 읽을 수 있습니다. C) 알려지지 않은 / 검증되지 않은 문자열이 그 시점에서 오류 개발자와 함께 구문 분석하고 데이터를 재분석 할 필요가없는 형식으로 변환 할 소프트웨어에 입력하는 경우 정규식이 필요하지 않습니다. B) 잘못된 코드에만 적용되는 말도 안됩니다 (A 및 C 지점 참조).
Brendan

당신의 C에서가는 :이게 뭐죠 경우 입니다 자신의 유효성 검사 논리? OP의 코드는 입력 한 내용을 확인하고, 입력 내용이 유효하지 않은 경우보고하고, 캡처를 통해 사용 가능한 형식으로 변환하는 것입니다. 우리가 가진 것은 표현 자체입니다. 정규 표현식 이외의 구문 분석을 제안하는 방법은 무엇입니까? 동일한 결과를 얻을 수있는 샘플 코드를 추가하면 다운 보트를 제거합니다.
밥슨

"C : 유효성 검사 (오류보고 포함)"인 경우 오류보고가 잘못되어 잘못된 코드입니다. 실패하면; 문자열이 NULL이거나 첫 번째 숫자에 숫자가 너무 많거나 첫 번째 구분 기호가 아니기 때문 :입니까? 사용자에게 문제가 무엇인지 알려주기에는 너무 어리석은 오류 메시지 ( "ERROR")가 하나만있는 컴파일러를 상상해보십시오. 이제 "멍청한 이메일 주소"와 같은 어리 석고 표시되는 수천 개의 웹 사이트를 상상해보십시오.
Brendan

또한 반 훈련 된 헬프 데스크 운영자가 완전히 훈련받지 않은 사용자로부터 버그 보고서를받는 것을 상상해보십시오. 소프트웨어가 작동을 멈췄습니다-소프트웨어 로그의 마지막 줄이 "오류 : 버전 문자열 '1 : 2-3.4에서 부 버전 번호를 추출하지 못했습니다. '(두 번째 숫자 다음에 예상되는 콜론) "
Brendan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.