반응형
4장. 표준 표현식 심화
4.1 조건 연산자와 삼항 연산자
Thymeleaf에서 조건부 로직을 처리하는 다양한 방법들을 살펴보겠습니다.
기본 삼항 연산자
<div class="conditional-examples">
<!-- 기본 삼항 연산자 (condition ? trueValue : falseValue) -->
<p th:text="${user.age >= 18} ? '성인' : '미성년자'">연령 구분</p>
<p th:text="${user.isActive()} ? '활성' : '비활성'">계정 상태</p>
<!-- 숫자 조건 -->
<span th:class="${product.stock > 0} ? 'in-stock' : 'out-of-stock'"
th:text="${product.stock > 0} ? '재고있음' : '품절'">재고 상태</span>
<!-- 문자열 조건 -->
<p th:text="${#strings.isEmpty(user.nickname)} ? user.name : user.nickname">표시명</p>
<!-- null 조건 -->
<img th:src="${user.profileImage != null} ? ${user.profileImage} : '/images/default.png'"
th:alt="${user.name}">
</div>
중첩된 삼항 연산자
<div class="nested-conditionals">
<!-- 다중 조건 분기 -->
<span th:class="${user.role == 'admin'} ? 'badge-admin' :
(${user.role == 'moderator'} ? 'badge-moderator' : 'badge-user')"
th:text="${user.role}">역할</span>
<!-- 점수 기반 등급 -->
<div class="grade-display">
<span th:text="${user.score >= 90} ? 'A' :
(${user.score >= 80} ? 'B' :
(${user.score >= 70} ? 'C' :
(${user.score >= 60} ? 'D' : 'F')))">등급</span>
<span th:style="${user.score >= 90} ? 'color: gold;' :
(${user.score >= 80} ? 'color: silver;' :
(${user.score >= 70} ? 'color: bronze;' : 'color: gray;'))">
★
</span>
</div>
<!-- 복잡한 상태 판단 -->
<div class="user-status">
<span th:text="${user.isActive()} ?
(${user.isVerified()} ? '정상 사용자' : '미인증 사용자') :
(${user.isSuspended()} ? '정지된 사용자' : '비활성 사용자')">
사용자 상태
</span>
</div>
<!-- 날짜 기반 조건 -->
<div class="membership-status" th:with="daysSinceJoin=${#temporals.daysBetween(user.joinDate, #temporals.createNow())}">
<span th:text="${daysSinceJoin < 7} ? '신규 회원' :
(${daysSinceJoin < 30} ? '새 회원' :
(${daysSinceJoin < 365} ? '일반 회원' : '베테랑 회원'))">
회원 등급
</span>
</div>
</div>
조건부 HTML 속성
<div class="conditional-attributes">
<!-- 조건부 클래스 -->
<div th:class="${user.isOnline()} ? 'user-card online' : 'user-card offline'">
<h3 th:text="${user.name}">사용자명</h3>
</div>
<!-- 조건부 스타일 -->
<div th:style="${user.isPremium()} ? 'border: 2px solid gold;' : 'border: 1px solid gray;'">
프리미엄 사용자 카드
</div>
<!-- 조건부 링크 -->
<a th:href="${user.hasProfile()} ? @{/user/{id}/profile(id=${user.id})} : @{/user/{id}/setup(id=${user.id})}"
th:text="${user.hasProfile()} ? '프로필 보기' : '프로필 설정'">링크</a>
<!-- 조건부 이벤트 핸들러 -->
<button th:onclick="${user.canEdit()} ? |editUser(${user.id})| : 'alert(\'권한이 없습니다\')'">
편집
</button>
<!-- 조건부 데이터 속성 -->
<div th:attr="data-status=${user.isActive()} ? 'active' : 'inactive',
data-role=${user.role},
data-premium=${user.isPremium()} ? 'yes' : 'no'">
데이터 속성 예제
</div>
</div>
4.2 Elvis 연산자와 No-Operation 토큰
Elvis 연산자(?:
)와 No-Operation 토큰(_
)은 더 간결한 조건 처리를 가능하게 합니다.
Elvis 연산자 기본 사용법
<div class="elvis-examples">
<!-- 기본 Elvis 연산자 (leftExpression ?: rightExpression) -->
<h3 th:text="${user.nickname ?: user.name}">표시명</h3>
<p th:text="${user.bio ?: '자기소개가 없습니다.'}">자기소개</p>
<!-- null이나 빈 문자열 처리 -->
<p th:text="${user.company ?: '회사 정보 없음'}">회사</p>
<p th:text="${user.website ?: '#'}">웹사이트</p>
<!-- 숫자 기본값 -->
<p>점수: <span th:text="${user.score ?: 0}">0</span>점</p>
<p>레벨: <span th:text="${user.level ?: 1}">1</span></p>
<!-- 컬렉션 기본값 -->
<p>취미 개수: <span th:text="${#lists.size(user.hobbies ?: {})}">0</span>개</p>
</div>
체인된 Elvis 연산자
<div class="chained-elvis">
<!-- 여러 값 중 첫 번째 null이 아닌 값 -->
<h3 th:text="${user.displayName ?: user.nickname ?: user.firstName ?: 'Unknown'}">
표시명
</h3>
<!-- 연락처 우선순위 -->
<p>연락처: <span th:text="${user.mobile ?: user.phone ?: user.email ?: '연락처 없음'}">연락처</span></p>
<!-- 이미지 fallback -->
<img th:src="${user.avatar ?: user.profilePicture ?: '/images/default-avatar.png'}"
th:alt="${user.name}">
<!-- 주소 정보 -->
<p th:text="${user.address?.fullAddress ?: user.address?.city ?: '주소 미입력'}">주소</p>
<!-- 소셜 링크 -->
<a th:href="${user.website ?: user.blog ?: user.socialMedia ?: '#'}"
th:text="${user.website ?: user.blog ?: user.socialMedia ?: 'No Link'}">링크</a>
</div>
Safe Navigation과 Elvis 연산자 조합
<div class="safe-navigation-elvis">
<!-- 중첩 객체의 안전한 접근 -->
<p th:text="${user.profile?.company?.name ?: '소속 없음'}">회사명</p>
<p th:text="${user.settings?.privacy?.email ?: 'public'}">이메일 공개 설정</p>
<!-- 컬렉션의 안전한 접근 -->
<p th:text="${user.orders?[0]?.orderNumber ?: '주문 없음'}">최근 주문번호</p>
<p th:text="${user.projects?[0]?.title ?: '프로젝트 없음'}">최근 프로젝트</p>
<!-- 메소드 체이닝과 Elvis -->
<p th:text="${user.getLatestPost()?.getTitle()?.toUpperCase() ?: 'NO POSTS'}">
최신 게시글 (대문자)
</p>
<!-- 날짜 포맷과 Elvis -->
<p th:text="${user.lastLogin != null} ? ${#temporals.format(user.lastLogin, 'yyyy-MM-dd')} : '로그인 기록 없음'">
마지막 로그인
</p>
<!-- Safe Navigation + Elvis + 포맷팅 -->
<p th:text="${#temporals.format(user.profile?.lastUpdate, 'yyyy-MM-dd') ?: '업데이트 없음'}">
프로필 마지막 업데이트
</p>
</div>
No-Operation 토큰 활용
<div class="no-operation-examples">
<!-- 조건부 텍스트 표시 (조건이 거짓이면 원래 텍스트 유지) -->
<p th:text="${user.isVip()} ? 'VIP 회원' : _">일반 텍스트</p>
<h2 th:text="${pageTitle} ?: _">기본 페이지 제목</h2>
<!-- 조건부 속성 설정 -->
<div th:class="${user.isPremium()} ? 'premium-badge' : _"
th:attr="data-premium=${user.isPremium()} ? 'true' : _">
조건부 속성
</div>
<!-- 조건부 링크 -->
<a th:href="${user.hasPermission('ADMIN')} ? @{/admin} : _"
th:text="관리자 페이지">관리자 페이지</a>
<!-- 조건부 이벤트 -->
<button th:onclick="${user.canDelete()} ? |deleteItem(${item.id})| : _">삭제</button>
<!-- 조건부 스타일 -->
<div th:style="${item.isHighlighted()} ? 'background-color: yellow;' : _">
하이라이트 항목
</div>
<!-- 복잡한 조건부 처리 -->
<div class="notification"
th:class="${notification.isUrgent()} ? 'notification urgent' : 'notification'"
th:attr="data-sound=${notification.hasSound()} ? 'enabled' : _,
data-popup=${notification.isPopup()} ? 'true' : _">
<span th:text="${notification.message}">알림 메시지</span>
<button th:onclick="${notification.isDismissible()} ? |dismiss(${notification.id})| : _">
닫기
</button>
</div>
</div>
4.3 문자열 연산과 비교 연산
문자열 연산
<div class="string-operations">
<!-- 기본 문자열 연결 -->
<p th:text="'Hello' + ' ' + 'World'">Hello World</p>
<p th:text="${user.firstName} + ' ' + ${user.lastName}">전체 이름</p>
<!-- 문자열 템플릿 (권장 방법) -->
<p th:text="|안녕하세요, ${user.name}님!|">인사말</p>
<p th:text="|${user.name} (${user.email})|">사용자 정보</p>
<!-- 복잡한 문자열 구성 -->
<p th:text="|주문 #${order.id}: ${order.status} - ${#numbers.formatCurrency(order.total)}|">
주문 정보
</p>
<!-- 조건부 문자열 구성 -->
<p th:text="|상태: ${user.isActive() ? '활성' : '비활성'} | 레벨: ${user.level ?: 1}|">
사용자 상태 정보
</p>
<!-- 문자열과 숫자 혼합 -->
<p th:text="|총 ${#lists.size(products)}개 상품 중 ${#lists.size(products.?[inStock])}개 재고 있음|">
재고 현황
</p>
<!-- URL과 문자열 조합 -->
<a th:href="|/user/${user.id}/posts?page=${currentPage}|"
th:text="|${user.name}의 게시글 보기|">게시글 링크</a>
<!-- CSS 클래스명 동적 생성 -->
<div th:class="|user-card ${user.role} ${user.isOnline() ? 'online' : 'offline'}|">
동적 클래스
</div>
<!-- 이미지 경로 생성 -->
<img th:src="|/images/avatars/${user.id}_${user.avatarVersion ?: 'default'}.jpg|"
th:alt="|${user.name} 아바타|">
</div>
문자열 비교 연산
<div class="string-comparison">
<!-- 기본 문자열 비교 -->
<p th:if="${user.role == 'admin'}">관리자입니다.</p>
<p th:if="${user.status != 'banned'}">정상 사용자입니다.</p>
<!-- 대소문자 무시 비교 -->
<p th:if="${#strings.equalsIgnoreCase(user.role, 'ADMIN')}">
관리자 권한 (대소문자 무시)
</p>
<!-- 문자열 포함 검사 -->
<div th:if="${#strings.contains(user.email, '@gmail.com')}">
Gmail 사용자입니다.
</div>
<!-- 문자열 시작/끝 검사 -->
<div th:if="${#strings.startsWith(user.phone, '010')}">
휴대폰 번호입니다.
</div>
<div th:if="${#strings.endsWith(user.website, '.com')}">
.com 도메인입니다.
</div>
<!-- 빈 문자열 검사 -->
<div th:if="${#strings.isEmpty(user.bio)}">
자기소개가 비어있습니다.
</div>
<div th:unless="${#strings.isEmpty(user.nickname)}">
닉네임: <span th:text="${user.nickname}">닉네임</span>
</div>
<!-- 문자열 길이 비교 -->
<div th:if="${#strings.length(user.password) < 8}">
비밀번호가 너무 짧습니다.
</div>
<!-- 정규식 매칭 -->
<div class="validation-results">
<p th:if="${#strings.matches(user.email, '^[A-Za-z0-9+_.-]+@(.+))}">
유효한 이메일 형식입니다.
</p>
<p th:if="${#strings.matches(user.phone, '^01[016789]-?[0-9]{3,4}-?[0-9]{4})}">
유효한 휴대폰 번호입니다.
</p>
<p th:if="${#strings.matches(user.zipCode, '^[0-9]{5})}">
유효한 우편번호입니다.
</p>
</div>
<!-- 복합 문자열 조건 -->
<div th:if="${#strings.contains(user.email, 'admin') and #strings.endsWith(user.email, '.com')}">
관리자 이메일로 추정됩니다.
</div>
</div>
고급 문자열 처리
<div class="advanced-string-operations">
<!-- 문자열 변환 -->
<p th:text="${#strings.toUpperCase(user.name)}">이름 (대문자)</p>
<p th:text="${#strings.toLowerCase(user.email)}">이메일 (소문자)</p>
<p th:text="${#strings.capitalizeWords(user.address)}">주소 (단어별 대문자)</p>
<!-- 문자열 자르기 -->
<p th:text="${#strings.substring(user.bio, 0, 100)} + ${#strings.length(user.bio) > 100 ? '...' : ''}">
자기소개 미리보기
</p>
<!-- 문자열 분할과 조인 -->
<div th:with="tags=${#strings.listSplit(user.tags, ',')}">
<span th:each="tag, stat : ${tags}">
<span th:text="${#strings.trim(tag)}">태그</span>
<span th:if="${!stat.last}">, </span>
</span>
</div>
<!-- 문자열 치환 -->
<p th:text="${#strings.replace(user.bio, '\n', '<br>')}">
줄바꿈 처리된 자기소개
</p>
<!-- 문자열 패딩 -->
<div class="user-id">
ID: <span th:text="${#strings.leftPad(#strings.toString(user.id), 6, '0')}">000001</span>
</div>
<!-- 문자열 트림 -->
<p th:text="${#strings.trim(user.company)}">회사명 (공백 제거)</p>
<!-- 문자열 null 안전 처리 -->
<p th:text="${#strings.defaultString(user.nickname, '닉네임 없음')}">닉네임</p>
<!-- 복잡한 문자열 가공 -->
<div class="formatted-address" th:if="${user.address}">
<span th:text="${#strings.abbreviate(#strings.capitalizeWords(user.address.street), 20)}">
주소
</span>
<br>
<span th:text="${#strings.toUpperCase(user.address.city)}">도시</span>
<span th:text="${user.address.zipCode}">우편번호</span>
</div>
</div>
4.4 산술 연산과 논리 연산
산술 연산 심화
<div class="advanced-arithmetic">
<!-- 기본 산술 연산 -->
<div class="price-calculation">
<p>상품 가격: <span th:text="${product.basePrice}">10000</span>원</p>
<p>할인율: <span th:text="${product.discountRate}">10</span>%</p>
<p>할인 금액: <span th:text="${product.basePrice * product.discountRate / 100}">1000</span>원</p>
<p>최종 가격: <span th:text="${product.basePrice * (100 - product.discountRate) / 100}">9000</span>원</p>
</div>
<!-- 복잡한 계산 -->
<div class="shopping-cart" th:with="total=${#aggregates.sum(cartItems.![quantity * price])}">
<div th:each="item : ${cartItems}">
<span th:text="${item.name}">상품명</span>:
<span th:text="${item.quantity}">2</span> ×
<span th:text="${#numbers.formatCurrency(item.price)}">₩10,000</span> =
<span th:text="${#numbers.formatCurrency(item.quantity * item.price)}">₩20,000</span>
</div>
<div class="cart-summary">
<p>소계: <span th:text="${#numbers.formatCurrency(total)}">₩50,000</span></p>
<p>배송비: <span th:text="${total >= 30000} ? '무료' : '₩3,000'">무료</span></p>
<p>총합계:
<span th:text="${#numbers.formatCurrency(total + (total >= 30000 ? 0 : 3000))}">
₩50,000
</span>
</p>
</div>
</div>
<!-- 수학 함수 활용 -->
<div class="math-functions">
<!-- 반올림 -->
<p>평균 점수: <span th:text="${#numbers.formatDecimal(totalScore / studentCount, 1, 2)}">85.67</span></p>
<!-- 최대/최소값 -->
<p>최고 점수: <span th:text="${#aggregates.max(scores)}">95</span></p>
<p>최저 점수: <span th:text="${#aggregates.min(scores)}">72</span></p>
<!-- 절댓값 -->
<p>차이: <span th:text="${#numbers.formatInteger(#math.abs(score1 - score2), 0, 'COMMA')}">15</span></p>
<!-- 거듭제곱과 제곱근 -->
<p>제곱: <span th:text="${value * value}">25</span></p>
<p>세제곱: <span th:text="${value * value * value}">125</span></p>
</div>
<!-- 날짜 계산 -->
<div class="date-calculations" th:with="now=${#dates.createNow()}">
<p>가입한지:
<span th:text="${#dates.daysBetween(user.joinDate, now)}">30</span>일 경과
</p>
<p>다음 결제일까지:
<span th:text="${#dates.daysBetween(now, user.nextPaymentDate)}">15</span>일 남음
</p>
<!-- 나이 계산 -->
<p>나이:
<span th:text="${#dates.year(now) - #dates.year(user.birthDate)}">25</span>세
</p>
<!-- 근무 연차 계산 -->
<p>근무 연차:
<span th:text="${(#dates.daysBetween(user.hireDate, now) / 365.0) + 1}">3.2</span>년
</p>
</div>
</div>
논리 연산 심화
<div class="advanced-logical-operations">
<!-- 복합 논리 조건 -->
<div class="access-control">
<!-- 관리자이거나 자신의 프로필인 경우 -->
<div th:if="${currentUser.isAdmin() or currentUser.id == user.id}">
<button>편집</button>
<button>삭제</button>
</div>
<!-- 활성 상태이고 인증된 사용자만 -->
<div th:if="${user.isActive() and user.isVerified() and not user.isSuspended()}">
<p>모든 기능을 사용할 수 있습니다.</p>
</div>
<!-- 프리미엄이거나 높은 점수를 가진 사용자 -->
<div th:if="${user.isPremium() or user.score >= 90}">
<div class="premium-features">
<p>프리미엄 기능 이용 가능</p>
</div>
</div>
</div>
<!-- 날짜 기반 논리 연산 -->
<div class="date-based-logic" th:with="now=${#temporals.createNow()}">
<!-- 신규 사용자 (가입 후 7일 이내) -->
<div th:if="${#temporals.daysBetween(user.joinDate, now) <= 7}">
<div class="welcome-message">
<p>신규 회원 환영합니다! 🎉</p>
</div>
</div>
<!-- 오랫동안 활동하지 않은 사용자 -->
<div th:if="${user.lastLoginDate != null and #temporals.daysBetween(user.lastLoginDate, now) > 30}">
<div class="inactive-warning">
<p>오랫동안 로그인하지 않았습니다. 계정을 확인해주세요.</p>
</div>
</div>
<!-- 구독 만료 임박 -->
<div th:if="${user.subscriptionEndDate != null and #temporals.daysBetween(now, user.subscriptionEndDate) <= 7 and #temporals.daysBetween(now, user.subscriptionEndDate) >= 0}">
<div class="subscription-warning">
<p>구독이 곧 만료됩니다. 갱신해주세요.</p>
</div>
</div>
</div>
<!-- 컬렉션 기반 논리 연산 -->
<div class="collection-based-logic">
<!-- 빈 컬렉션 검사 -->
<div th:if="${#lists.isEmpty(user.orders)}">
<p>아직 주문 내역이 없습니다.</p>
</div>
<!-- 컬렉션 크기 조건 -->
<div th:if="${#lists.size(user.friends) >= 10}">
<div class="social-badge">
<p>인기 사용자 배지 🌟</p>
</div>
</div>
<!-- 컬렉션 내용 검사 -->
<div th:if="${#lists.contains(user.roles, 'EDITOR')}">
<button>글쓰기</button>
</div>
<!-- 모든/일부 조건 -->
<div th:if="${not #lists.isEmpty(user.projects) and #lists.size(user.projects.?[completed]) == #lists.size(user.projects)}">
<div class="completion-badge">
<p>모든 프로젝트 완료! 🎯</p>
</div>
</div>
</div>
<!-- 문자열 기반 논리 연산 -->
<div class="string-based-logic">
<!-- 이메일 도메인 검사 -->
<div th:if="${#strings.contains(user.email, '@company.com') or #strings.contains(user.email, '@partner.com')}">
<div class="corporate-user">
<p>기업 사용자입니다.</p>
</div>
</div>
<!-- 전화번호 유효성과 지역 검사 -->
<div th:if="${not #strings.isEmpty(user.phone) and (#strings.startsWith(user.phone, '02') or #strings.startsWith(user.phone, '010'))}">
<p>서울 또는 휴대폰 번호입니다.</p>
</div>
<!-- 사용자명 길이와 형식 검사 -->
<div th:if="${#strings.length(user.username) >= 3 and #strings.length(user.username) <= 20 and #strings.matches(user.username, '^[a-zA-Z0-9_]+)}">
<p>유효한 사용자명입니다.</p>
</div>
</div>
</div>
4.5 정규 표현식 활용
기본 정규식 매칭
<div class="regex-examples">
<!-- 이메일 유효성 검사 -->
<div class="email-validation">
<p th:if="${#strings.matches(user.email, '^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\.[A-Za-z]{2,}))}">
✅ 유효한 이메일 주소
</p>
<p th:unless="${#strings.matches(user.email, '^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\.[A-Za-z]{2,}))}">
❌ 이메일 형식이 올바르지 않습니다
</p>
</div>
<!-- 전화번호 유효성 검사 -->
<div class="phone-validation">
<div th:switch="${true}">
<p th:case="${#strings.matches(user.phone, '^01[016789]-?[0-9]{3,4}-?[0-9]{4})}">
📱 유효한 휴대폰 번호
</p>
<p th:case="${#strings.matches(user.phone, '^0[2-9][0-9]?-?[0-9]{3,4}-?[0-9]{4})}">
📞 유효한 일반 전화번호
</p>
<p th:case="*">
❌ 전화번호 형식이 올바르지 않습니다
</p>
</div>
</div>
<!-- 비밀번호 강도 검사 -->
<div class="password-strength" th:with="password=${userForm.password}">
<div class="strength-indicators">
<span th:class="${#strings.matches(password, '.*[a-z].*')} ? 'valid' : 'invalid'">소문자</span>
<span th:class="${#strings.matches(password, '.*[A-Z].*')} ? 'valid' : 'invalid'">대문자</span>
<span th:class="${#strings.matches(password, '.*[0-9].*')} ? 'valid' : 'invalid'">숫자</span>
<span th:class="${#strings.matches(password, '.*[!@#$%^&*(),.?\":{}|<>].*')} ? 'valid' : 'invalid'">특수문자</span>
<span th:class="${#strings.length(password) >= 8} ? 'valid' : 'invalid'">8자 이상</span>
</div>
<!-- 전체 강도 평가 -->
<div th:with="strongPassword=${#strings.matches(password, '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,})}">
<p th:if="${strongPassword}" class="strong">🔒 강력한 비밀번호</p>
<p th:unless="${strongPassword}" class="weak">⚠️ 비밀번호를 더 강력하게 만드세요</p>
</div>
</div>
<!-- URL 유효성 검사 -->
<div class="url-validation">
<a th:if="${#strings.matches(user.website, '^https?://.*)}"
th:href="${user.website}"
th:text="${user.website}">웹사이트</a>
<span th:unless="${#strings.matches(user.website, '^https?://.*)}"
th:text="${user.website}">유효하지 않은 URL</span>
</div>
</div>
입력 값 검증과 형식화
<div class="input-validation">
<!-- 사용자명 검증 -->
<div class="username-validation" th:with="username=${userForm.username}">
<div class="validation-rules">
<p th:class="${#strings.matches(username, '^[a-zA-Z0-9_]{3,20})} ? 'valid' : 'invalid'">
영문, 숫자, 밑줄만 사용, 3-20자
</p>
<p th:class="${#strings.matches(username, '^[a-zA-Z].*')} ? 'valid' : 'invalid'">
영문으로 시작
</p>
<p th:class="${not #strings.matches(username, '.*[_]{2,}.*')} ? 'valid' : 'invalid'">
연속된 밑줄 사용 금지
</p>
</div>
</div>
<!-- 주민등록번호 검증 (예시) -->
<div class="ssn-validation" th:if="${userForm.ssn}">
<p th:if="${#strings.matches(userForm.ssn, '^[0-9]{6}-[1-4][0-9]{6})}" class="valid">
✅ 올바른 주민등록번호 형식
</p>
<p th:unless="${#strings.matches(userForm.ssn, '^[0-9]{6}-[1-4][0-9]{6})}" class="invalid">
❌ 주민등록번호 형식이 올바르지 않습니다
</p>
</div>
<!-- 신용카드 번호 검증 -->
<div class="card-validation" th:if="${paymentForm.cardNumber}">
<div th:switch="${true}">
<p th:case="${#strings.matches(paymentForm.cardNumber, '^4[0-9]{12}(?:[0-9]{3})?)}">
💳 Visa 카드
</p>
<p th:case="${#strings.matches(paymentForm.cardNumber, '^5[1-5][0-9]{14})}">
💳 MasterCard
</p>
<p th:case="${#strings.matches(paymentForm.cardNumber, '^3[47][0-9]{13})}">
💳 American Express
</p>
<p th:case="*" class="invalid">
❌ 지원하지 않는 카드입니다
</p>
</div>
</div>
<!-- IP 주소 검증 -->
<div class="ip-validation" th:if="${serverConfig.ipAddress}">
<p th:if="${#strings.matches(serverConfig.ipAddress, '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))}" class="valid">
🌐 유효한 IPv4 주소
</p>
</div>
</div>
텍스트 추출과 변환
<div class="text-extraction">
<!-- 해시태그 추출 -->
<div class="hashtag-extraction" th:if="${post.content}">
<div class="hashtags">
<!-- JavaScript로 처리하는 예시 (Thymeleaf에서는 복잡한 정규식 추출이 제한적) -->
<span th:text="${post.content}" style="display: none;" th:attr="data-content=${post.content}"></span>
<div id="extracted-hashtags"></div>
</div>
<!-- 멘션 추출 -->
<div class="mentions">
<span th:if="${#strings.contains(post.content, '@')}" class="has-mentions">
이 게시글에 멘션이 포함되어 있습니다.
</span>
</div>
</div>
<!-- 코드 블록 감지 -->
<div class="code-detection" th:if="${comment.text}">
<div th:if="${#strings.contains(comment.text, '```') or #strings.contains(comment.text, '<code>')}"
class="code-comment">
💻 코드가 포함된 댓글
</div>
</div>
<!-- 마크다운 링크 감지 -->
<div class="markdown-detection" th:if="${document.content}">
<span th:if="${#strings.matches(document.content, '.*\\[.*\\]\\(.*\\).*')}" class="has-links">
🔗 링크 포함
</span>
<span th:if="${#strings.matches(document.content, '.*!\\[.*\\]\\(.*\\).*')}" class="has-images">
🖼️ 이미지 포함
</span>
</div>
<!-- 금지어 필터링 -->
<div class="content-filter" th:with="bannedWords=${'욕설1|욕설2|금지어'}">
<div th:if="${#strings.matches(userInput, '.*(' + bannedWords + ').*')}" class="content-warning">
⚠️ 부적절한 내용이 감지되었습니다.
</div>
</div>
</div>
<script>
// 해시태그 추출을 위한 JavaScript (Thymeleaf 보완)
document.addEventListener('DOMContentLoaded', function() {
const contentElement = document.querySelector('[data-content]');
if (contentElement) {
const content = contentElement.getAttribute('data-content');
const hashtags = content.match(/#[a-zA-Z0-9가-힣_]+/g);
const hashtagContainer = document.getElementById('extracted-hashtags');
if (hashtags && hashtagContainer) {
hashtags.forEach(tag => {
const span = document.createElement('span');
span.className = 'hashtag';
span.textContent = tag;
hashtagContainer.appendChild(span);
});
}
}
});
</script>
실용적인 정규식 활용 예제
<div class="practical-regex-examples">
<!-- 파일 확장자 검사 -->
<div class="file-upload-validation" th:each="file : ${uploadedFiles}">
<div th:switch="${true}">
<span th:case="${#strings.matches(file.name, '.*\\.(jpg|jpeg|png|gif))}" class="image-file">
🖼️ <span th:text="${file.name}">이미지</span>
</span>
<span th:case="${#strings.matches(file.name, '.*\\.(pdf|doc|docx))}" class="document-file">
📄 <span th:text="${file.name}">문서</span>
</span>
<span th:case="${#strings.matches(file.name, '.*\\.(mp4|avi|mov))}" class="video-file">
🎬 <span th:text="${file.name}">비디오</span>
</span>
<span th:case="*" class="other-file">
📎 <span th:text="${file.name}">기타</span>
</span>
</div>
</div>
<!-- 버전 번호 비교 -->
<div class="version-check" th:with="currentVersion=${'1.2.3'}, requiredVersion=${'1.2.0'}">
<p th:if="${#strings.matches(currentVersion, '^[0-9]+\\.[0-9]+\\.[0-9]+)}" class="valid-version">
현재 버전: <span th:text="${currentVersion}">1.2.3</span>
</p>
</div>
<!-- HTML 태그 제거 검증 -->
<div class="html-sanitization" th:if="${userInput}">
<div th:if="${#strings.matches(userInput, '.*<[^>]+>.*')}" class="html-detected">
⚠️ HTML 태그가 감지되었습니다. 텍스트만 입력해주세요.
</div>
</div>
<!-- 색상 코드 검증 -->
<div class="color-validation" th:if="${themeForm.primaryColor}">
<div th:if="${#strings.matches(themeForm.primaryColor, '^#[0-9A-Fa-f]{6})}"
th:style="|background-color: ${themeForm.primaryColor}; color: white; padding: 5px;|">
유효한 색상 코드: <span th:text="${themeForm.primaryColor}">#FF5733</span>
</div>
</div>
<!-- 시간 형식 검증 -->
<div class="time-validation" th:if="${scheduleForm.startTime}">
<span th:if="${#strings.matches(scheduleForm.startTime, '^([01]?[0-9]|2[0-3]):[0-5][0-9])}" class="valid">
⏰ 시작 시간: <span th:text="${scheduleForm.startTime}">14:30</span>
</span>
</div>
</div>
이 4장에서는 Thymeleaf의 표현식을 더욱 깊이 있게 다루었습니다. 조건 연산자, Elvis 연산자, 문자열 처리, 산술 연산, 논리 연산, 그리고 정규 표현식까지 실무에서 자주 사용되는 고급 기능들을 살펴보았습니다.
핵심 요약
- 조건 연산자: 삼항 연산자를 통한 간단한 조건 분기
- Elvis 연산자: null 안전한 기본값 설정
- No-Operation 토큰: 조건부 속성 설정 시 유용
- 문자열 연산: 동적 문자열 생성과 검증
- 산술/논리 연산: 복잡한 비즈니스 로직 구현
- 정규 표현식: 입력 검증과 텍스트 처리
이러한 고급 표현식들을 잘 활용하면 더욱 동적이고 유연한 템플릿을 만들 수 있습니다. 다음 장에서는 Thymeleaf의 표준 속성들에 대해 상세히 알아보겠습니다.
반응형