顯示具有 正規表示法 標籤的文章。 顯示所有文章
顯示具有 正規表示法 標籤的文章。 顯示所有文章

2024年4月14日 星期日

檢查 unclodsed(unpaired) html tag 並將其修正 - 使用 Java

在這篇文言,
我想紀錄一下並分享一個用 Java 檢查 HTML unclosed (或 unpaired) tag,並嘗試將其補上缺少的 開頭 tag(openTag)/結尾 tag (closeTag) 的方法。

Java 並不是重點,只要了解邏輯,用什麼程式語言都可以,只是我習慣 Java 所以用 Java 來做例子。

在 HTML 中,除了一些不用成對的 tag ,例如 <img/>, <b/> 等,
大多數的 tag 都是需要成對的,例如 <div> , <p>, <span> 等,需要配合使用 </div>, </p>, </span> 來使之頭尾成對才是合法的 HTML tag。

我們希望能用程式來幫助我們成出一串 HTML 中不成對的非法 tag,
並嘗試將缺少的 openTag 或 closeTag 補上將其修正成合法的 HTML,
要注意到的是,沒有一定正確的修正方法,因為我們不會知道原來寫這串 HTML 的原作者是哪裡忘記少加 openTag/closeTag 了,
例如現在有一串 HTML:

<div>
  xxx
<div>
  xxx

原作者到底原本是想寫

<div>
  xxx
</div>
<div>
  xxx
</div>

還是

<div>
  xxx
  <div>
    xxx
  </div>
</div>

呢?

我們不得而知,而兩種修正方法最後修完的 HTML 也確實都是有成對 tag 的合法 HTML。
在這篇文章裡,我的修法邏輯是採用上述第二種,將缺少的 tag 從外圍補上的方法。

先來直接看最後成品的 Java 程式碼例子,說明及最後的輸出結果都注釋在程式碼中了:

package test;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JustT {

	public static void main(String[] args) {		
		String htmlStr = "<div><div id='kk'>xxx</p></p><div><div>\n" + "<div></p><div>";
		// 在這裡我們只修正 div 和 p 這兩個 tag 做例子
		// 如果還要修正其他 tag 的話,可以補在 tagToFix 變數中,用 | 符號來分隔
		String tagsToFix = "div|p";
		String regularExpression = "<(\\/?(?:" + tagsToFix + "))(?:\\s+?(?:.*?))?>";
		System.out.println("===========================");
		System.out.println("測試測試的句子:\n" + htmlStr);
		System.out.println("正規表示法: " + regularExpression);

		Pattern pattern = Pattern.compile(regularExpression, Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(htmlStr);

		Deque<String> foundOpenTagStack = new ArrayDeque<>();
		int nextOffset = 0;
		int currentOffset = 0;
		for (int matchCount = 1; matcher.find(); matchCount++) {
			// groupCount不包括匹配的字串,即matcher.group(0)
			System.out.println("---------------------------");
			System.out.println("第" + matchCount + "次匹配,找到" + matcher.groupCount() + "個Group:");
			System.out.println("匹配的字串為: " + matcher.group(0));
			System.out.println("start: " + matcher.start() + ", end: " + matcher.end());

			for (int groupCount = 1; groupCount <= matcher.groupCount(); groupCount++) {
				System.out.println("第" + groupCount + "個Group: " + matcher.group(groupCount));
				String tag = matcher.group(groupCount);

				boolean isOpenTag = !tag.contains("/");
				System.out.println("isOpenTag: " + isOpenTag);
				if (isOpenTag) {
					currentOffset = nextOffset;
					System.out.println("是 openTag, 將其放進堆疊中");
					foundOpenTagStack.push(tag);
					showStackContent(foundOpenTagStack);
				} else {
					while (true) {
						if (foundOpenTagStack.isEmpty()) {
							// add missing open tag
							System.out.println("最後還是找不到與 closeTag(" + tag + ") 相對的 openTag,\n"
									+ "代表 closeTag 缺少對應的 openTag,需補上對應的 openTag 做修正");
							String strNeedToAdd = "<" + tag.replace("/", "") + ">";
							htmlStr = strNeedToAdd + htmlStr;
							nextOffset += strNeedToAdd.length();
							System.out.println("修正後的 HTML: " + htmlStr);
							break;
						}

						String openTag = foundOpenTagStack.pop();
						System.out.println("取出堆疊中的 openTag(" + openTag + ")");
						showStackContent(foundOpenTagStack);
						if (openTag.equalsIgnoreCase(tag.replace("/", ""))) {
							// found matched open tag
							System.out.println("找到與 closeTag(" + tag + ")相對應的 openTag,此tag不需修正");
							System.out.println("目前 HTML: " + htmlStr);
							break;
						}

						// add missing end tag
                        String strNeedToAdd = "</" + openTag + ">";
						StringBuilder sb = new StringBuilder(htmlStr);						
						sb.insert(matcher.start() + currentOffset, strNeedToAdd);
						htmlStr = sb.toString();
						nextOffset += strNeedToAdd.length();
						System.out.println("找到與 closeTag(" + tag + ")不相對應的 openTag(" + openTag + "),\n"
								+ "代表 openTag 缺少對應的 closeTag,需補上對應的 closeTag 做修正");
						System.out.println("修正後的 HTML: " + htmlStr);
					}
				}
			}
		}

		// handle left unclosed open tags, add missing close tag
		while (!foundOpenTagStack.isEmpty()) {
			System.out.println("發現堆疊中留有缺少 closeTag 的 openTag 未修正");
			String openTag = foundOpenTagStack.pop();
			System.out.println("取出堆疊中的 openTag");
			showStackContent(foundOpenTagStack);
			htmlStr = htmlStr + "</" + openTag + ">";
			System.out.println("補上 openTag 缺少的相對應 closeTag");
			System.out.println("修正後的 HTML: " + htmlStr);
		}

		System.out.println("===========================");
		System.out.println();
		System.out.println("最後修正完後的 HTML: " + htmlStr);
		
		//輸出的結果:
		/*
		 ===========================
		測試測試的句子:
		<div><div id='kk'>xxx</p></p><div><div>
		<div></p><div>
		正規表示法: <(\/?(?:div|p))(?:\s+?(?:.*?))?>
		---------------------------
		第1次匹配,找到1個Group:
		匹配的字串為: <div>
		start: 0, end: 5
		第1個Group: div
		isOpenTag: true
		是 openTag, 將其放進堆疊中
		目前堆疊成員:
		<==>|div|
		---------------------------
		第2次匹配,找到1個Group:
		匹配的字串為: <div id='kk'>
		start: 5, end: 18
		第1個Group: div
		isOpenTag: true
		是 openTag, 將其放進堆疊中
		目前堆疊成員:
		<==>|div|div|
		---------------------------
		第3次匹配,找到1個Group:
		匹配的字串為: </p>
		start: 21, end: 25
		第1個Group: /p
		isOpenTag: false
		取出堆疊中的 openTag(div)
		目前堆疊成員:
		<==>|div|
		找到與 closeTag(/p)不相對應的 openTag(div),
		代表 openTag 缺少對應的 closeTag,需補上對應的 closeTag 做修正
		修正後的 HTML: <div><div id='kk'>xxx</div></p></p><div><div>
		<div></p><div>
		取出堆疊中的 openTag(div)
		目前堆疊成員:
		<==>|
		找到與 closeTag(/p)不相對應的 openTag(div),
		代表 openTag 缺少對應的 closeTag,需補上對應的 closeTag 做修正
		修正後的 HTML: <div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></p><div>
		最後還是找不到與 closeTag(/p) 相對的 openTag,
		代表 closeTag 缺少對應的 openTag,需補上對應的 openTag 做修正
		修正後的 HTML: <p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></p><div>
		---------------------------
		第4次匹配,找到1個Group:
		匹配的字串為: </p>
		start: 25, end: 29
		第1個Group: /p
		isOpenTag: false
		最後還是找不到與 closeTag(/p) 相對的 openTag,
		代表 closeTag 缺少對應的 openTag,需補上對應的 openTag 做修正
		修正後的 HTML: <p><p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></p><div>
		---------------------------
		第5次匹配,找到1個Group:
		匹配的字串為: <div>
		start: 29, end: 34
		第1個Group: div
		isOpenTag: true
		是 openTag, 將其放進堆疊中
		目前堆疊成員:
		<==>|div|
		---------------------------
		第6次匹配,找到1個Group:
		匹配的字串為: <div>
		start: 34, end: 39
		第1個Group: div
		isOpenTag: true
		是 openTag, 將其放進堆疊中
		目前堆疊成員:
		<==>|div|div|
		---------------------------
		第7次匹配,找到1個Group:
		匹配的字串為: <div>
		start: 40, end: 45
		第1個Group: div
		isOpenTag: true
		是 openTag, 將其放進堆疊中
		目前堆疊成員:
		<==>|div|div|div|
		---------------------------
		第8次匹配,找到1個Group:
		匹配的字串為: </p>
		start: 45, end: 49
		第1個Group: /p
		isOpenTag: false
		取出堆疊中的 openTag(div)
		目前堆疊成員:
		<==>|div|div|
		找到與 closeTag(/p)不相對應的 openTag(div),
		代表 openTag 缺少對應的 closeTag,需補上對應的 closeTag 做修正
		修正後的 HTML: <p><p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></div></p><div>
		取出堆疊中的 openTag(div)
		目前堆疊成員:
		<==>|div|
		找到與 closeTag(/p)不相對應的 openTag(div),
		代表 openTag 缺少對應的 closeTag,需補上對應的 closeTag 做修正
		修正後的 HTML: <p><p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></div></div></p><div>
		取出堆疊中的 openTag(div)
		目前堆疊成員:
		<==>|
		找到與 closeTag(/p)不相對應的 openTag(div),
		代表 openTag 缺少對應的 closeTag,需補上對應的 closeTag 做修正
		修正後的 HTML: <p><p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></div></div></div></p><div>
		最後還是找不到與 closeTag(/p) 相對的 openTag,
		代表 closeTag 缺少對應的 openTag,需補上對應的 openTag 做修正
		修正後的 HTML: <p><p><p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></div></div></div></p><div>
		---------------------------
		第9次匹配,找到1個Group:
		匹配的字串為: <div>
		start: 49, end: 54
		第1個Group: div
		isOpenTag: true
		是 openTag, 將其放進堆疊中
		目前堆疊成員:
		<==>|div|
		發現堆疊中留有缺少 closeTag 的 openTag 未修正
		取出堆疊中的 openTag
		目前堆疊成員:
		<==>|
		補上 openTag 缺少的相對應 closeTag
		修正後的 HTML: <p><p><p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></div></div></div></p><div></div>
		===========================
		
		最後修正完後的 HTML: <p><p><p><div><div id='kk'>xxx</div></div></p></p><div><div>
		<div></div></div></div></p><div></div>
		*/
	}
	
	public static <T> void showStackContent(Deque<T> stack) {
		System.out.println("目前堆疊成員:");
		System.out.print("<==>|");
		stack.forEach((T member) -> {
			System.out.print(member + "|");
		});
		System.out.println("");
	}
}

說明:
在這次的例子中,先以只修正 div 和 p 為例,有需要修正其他的 tag 的話可自行補充到 tagsToFix 變數中 (用 | 符號分隔)。
實現的邏輯很簡單,首先我們要找出所有要修正的 tag 的 openTag 和 closeTag,
尋找 tag 使用了如下的正規表達示:
<(\/?(?:div|p))(?:\s+?(?:.*?))?>

找到了以後,
1. 我們依序看找到的 tag,如果每個 tag 都看過了,就進到第2步,否則進到第1步。

1-1. 如果是屬於 openTag 就放到一個名為 foundOpenTagStack 的堆疊 (Stack) 中,否則到第1-2步。

1-2. 如果是屬於 closeTag,就從堆疊中取出 openTag 來作比較。

1-2-1. 如果被取出的 openTag 和 closeTag 是成對的,那即是代表這兩個 tag 是一對的,不用對 HTML 做修正,並且因為這個 closeTag 已經處理完成,我們可以繼續處理下一個找到的 closeTag,回到第1步。

1-2-2. 如果被取出的 openTag 和 closeTag 是不成對的,那即是代表這個 openTag 缺少了對應的 closeTag,我們便必需對 HTML 做修正,在 HTML 中找到 closeTag 的位置前面補上 openTag 缺少的結尾 tag ,
因為 closeTag 還沒被處理好,可能堆疊中還有與其成對的 openTag,所以回到 1-2 步繼續取出堆疊中的 openTag 做檢查。

1-2-3. 如果堆疊中的 openTag 都拿出來處理過並看過了 (此時堆疊中應該是已經沒有任何東西了),
但還是找不到與 closeTag 相應的 openTag 的話,代表 closeTag 缺少相對應的開頭 Tag,
所以我們必須要修正 HTML,在 HTML 的開頭加上對應的開頭 tag,處理完 closeTag 後,回到第1步繼續處理下一個 closeTag。

2. 當每個 tag 都看過了,此時 closeTag 都處理完畢了,但堆疊中可能還留有未處理的 openTag,例如如果 HTML 是 <div><div> 的話,因為沒有 closeTag,所以上述步驟執行完後會發現堆疊中還留有未處理的 openTag,而此時堆疊中的 openTag 都是缺少成對結尾 tag 的,
所以我們可以一個個把 openTag 取出,並對其修正 HTML,在 HTML 的尾端補上缺少的結尾 tag 。

當上述步驟都做完了以後,我們就能得到被修正好的 HTML 了。

另外補充一點,在 1-2-2 補上 openTag 的 closeTag 時,我們可以利用 matcher.start() 來得知要在 HTML 句子中的哪個位置補上 closeTag,但是因為在每個步驟補上 closeTag 和 openTag 後會改變 HTML 句子的長度,造成 matcher.start() 還要在位移一下才能正確地指到現在 htmlStr 要補上 closeTag 的位置,所以我們使用 currentOffset 和 nextOffset 進行累加要偏移的字數進行修正。

而當第一個 closeTag 處理完畢後,堆疊中的所有 openTag 都會被處理完並被取出堆疊的關係,
所以其他的 closeTag 在 1-2-3 補上對應的開頭 tag 時,只要把開頭 tag 加在 HTML 開頭就行了。

在第2步,只有一開始 HTML 裡都沒有 closeTag 時才會發生堆疊中留有 openTag,此時對應的缺少結尾 tag 只要加在 HTML 尾端就行了。

2019年6月3日 星期一

正規表示法的 Positive/Negative Lookhead 和 Positive/Negative Lookbehind

Positive Lookhead :
a(?=b)
後面跟著 "b" 才match "a" ("b"不在match的結果裡)
例如:
可以match:ab, abc, bab
不會match:ac, ade, bad

Negative Lookhead :
a(?!b)
後面不跟著 "b" 才match "a" ("b"不在match的結果裡)
例如:
可以match:ac, ade, bad
不會match:ab, abc, bab


Positive Lookbehind:
(?<=a)b
前面跟著 "a" 才match "b" ("a"不在match的結果裡)
例如:
可以match:ab, abc, bab
不會match:xb, xbc, xbb


Negative Lookbehind:
(?<!a)b
前面不跟著 "a" 才match "b" ("a"不在match的結果裡)
例如:
可以match:xb, xbc, xbb
不會match:ab, abc, bab

應用範例:
找出單獨的 "a",不要match連續的 "a" (ex: aaa )
(?<!a)a(?!a)
解釋:
前面不跟著 "a", 才 match a(?!a),
後面不跟著 "a", 才 match a

Note:
以下網站不支援  Positive/Negative  Lookbehind
Regexper
DebuggexBeta

可以用 regular expressions 101 網站測試。
Javascript, Java 等程式語言也有支援  Positive/Negative  Lookbehind

參考資料:
  1. Lookahead and Lookbehind Zero-Length Assertions

2018年8月4日 星期六

IIS httpd.ini處理url有帶?加參數的情況

IIS 的轉址設定檔httpd.ini中,可以對特定的Url轉換成另一種Url,但如果要被轉的Url是有帶參數的網址,我們想把參數加在轉換後的Url後面,要如何做呢?

在這裡介紹一個可用的方法

首先例如有一下轉換設定:

RewriteRule /url-(.*?).html     /url.jsp?id=$1

可以進行例如以下url的轉換
/url-123.html     ==>     /url.jsp?id=123

但是如果是有帶參數的url就無法成功匹配轉換規則了
/url-123.html?param=abc    ==>     ???

有兩種解法,

一種是多加一條規則去處理有帶參數的情況:
RewriteRule /url-(.*?).html?(.*?)     /url.jsp?id=$1&$2
這樣就可成功轉換,例如以下:
/url-123.html?param=abc     ==>     /url.jsp?id=123&param=abc
但是這樣需要設定兩條規則:
RewriteRule /url-(.*?).html     /url.jsp?id=$1
RewriteRule /url-(.*?).html?(.*?)     /url.jsp?id=$1&$2

另一種寫法只要設定一條規則就行了,規則如下:
RewriteRule /url-(.*?).html(\?.*?)?     /url.jsp(?2$2&:?)id=$1
此寫法可以做到以下url轉換:
/url-123.html     ==>     /url.jsp?id=123
/url-123.html?param=abc     ==>     /url.jsp?param=abc&id=123

接下來來講解第二種方法的原理,
其用到了類似很多程式都有的"三元運算子"規能,規則如下:
(?NtrueExpression:falseExpression)
當有抓到第N匹配群組時,執行trueExpression,沒有時則執行falseEpression,
所以
/url.jsp(?2$2&:?)id=$1
的意恩就是當有抓到2匹配群組時,執行"$2&",否則則執行"?",
而$2就是抓到的url參數部份
在這裡
/url-(.*?).html(\?.*?)?
中的(\?.*?)?   即是代表第2群組  (\?.*?)  出現的次數可為0次或1次,

所以當出現為1次時,就把抓到的參數(連同?),例如上例的 ?param=abc,
放到 /url.jsp的後面再加個 &,而id=$1就放在&的後面,

如果出現為0次時,就直接再 /url.jsp的後面加個 ? ,再把id=$1放在 ? 的後面

參考資料:

  1. ISAPI_Rewrite 2 documentation  的 "Conditional expressions" 部份
  2. Topic: Query String and IIS error msg

2017年10月5日 星期四

正規表逹式 - Java 範例

紀錄下Java的正規表示法使用方法範例:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegTest {

 public static void main(String[] args) { 
  //(?i) 代表大小寫忽略
  //.*?為非貪婪演算,盡可能找最小範圍的結果值
  doRegularExpression("[ABC/def]kk[123/456][ABC/def]kk[123/456]", "(?i)\\[(a.*?)/(.*?)\\]");
  //.*為貪婪演算,盡可能找最大範圍的結果值
  doRegularExpression("[ABC/def]kk[123/456][ABC/def]kk[123/456]", "(?i)\\[(a.*)/(.*)\\]");
  
  /* 輸出為:
  ===========================
  測試的句子: [ABC/def]kk[123/456][ABC/def]kk[123/456]
  正規表示法: (?i)\[(a.*?)/(.*?)\]
  ---------------------------
  第1次匹配,找到2個Group:
  匹配的字串為: [ABC/def]
  第1個Group: ABC
  第2個Group: def
  ---------------------------
  第2次匹配,找到2個Group:
  匹配的字串為: [ABC/def]
  第1個Group: ABC
  第2個Group: def
  ===========================
  
  ===========================
  測試的句子: [ABC/def]kk[123/456][ABC/def]kk[123/456]
  正規表示法: (?i)\[(a.*)/(.*)\]
  ---------------------------
  第1次匹配,找到2個Group:
  匹配的字串為: [ABC/def]kk[123/456][ABC/def]kk[123/456]
  第1個Group: ABC/def]kk[123/456][ABC/def]kk[123
  第2個Group: 456
  ===========================
  */
 }
 
 static void doRegularExpression(String text, String regularExpression) {
  System.out.println("===========================");
  System.out.println("測試的句子: " + text);
  System.out.println("正規表示法: " + regularExpression);
  
  Pattern pattern = Pattern.compile(regularExpression);
  Matcher matcher = pattern.matcher(text);  
  
  
  for(int matchCount = 1 ; matcher.find(); matchCount++) {
   // groupCount不包括匹配的字串,即matcher.group(0)
   System.out.println("---------------------------");
   System.out.println("第" + matchCount + "次匹配,找到" + matcher.groupCount() + "個Group:"); 
   System.out.println("匹配的字串為: " + matcher.group(0));
   for(int groupCount = 1; groupCount <= matcher.groupCount(); groupCount++) {
          System.out.println("第" + groupCount + "個Group: " + matcher.group(groupCount));
      }
  }
  System.out.println("===========================");
  System.out.println();
 }

}


2017年4月30日 星期日

能找到最內層的tag的正規表示法

今天要來介紹一個能找到最內層一組tag的正規表示法,

例如有一個需求如下:
tag格式為[quotePostId=XX]YY[/quotePostId],XX填數字,YY填文字,
可嵌套,如何從以下tag嵌套中找出最內層的XX和YY:
[tagId=1]kk[tagId=2][tagId=3]kk[/tagId]kk[/tagId][/tagId]

這裡提供一個解法,匹配的Group1即為XX、Group2為YY
\[tagId=(\d+)\](?=((?:[^\[]|\[(?!(?:tagId=\d+\]|\/tagId\])))*))\2\[\/tagId\]

下面來做解釋:
思路是:

  1. 必須符合[tagId=XX]YY[/tagId]。
  2. XX必須為非空值之數字。
  3. YY可為空值或任何字元。
  4. YY不可含有[tagId=??]或[/tagId],因為含有即代表此次匹配YY不為最內層tag之內容。

實作步驟(???為還未確定的部份,用紅色標色,當下確定的地方用藍色標色,之前確定的用黑色標色):

  1. 首先因為必須符合[tagId=XX]YY[/tagId],即XX必須為非空值之數字,所以先寫出一開始的正規表示法。
    \[tagId=(\d+)\](???)\[\/tagId\]
  2. 歩驟1的???需要符合條件(思路4)才須匹配,否則匹配不成功,因為x(?=y)這個語法不會把y算在匹配成功的字串內,所以用\2來裝Group2選到的YY放到tag中間。
    \[tagId=(\d+)\](?=(???))\2\[\/tagId\]
  3. YY可為空值,所以在Group2裡面放一個不記Group編號的Group,然後此Group可以0到多個。
    \[tagId=(\d+)\](?=((?:???)*))\2\[\/tagId\]
  4. 開始匹配YY,分成碰到非左中括弧( [ )、或是左中括弧的兩種情況。
    當是左中括弧時,根據後面接的字串決定要不要匹配。
    \[tagId=(\d+)\](?=((?:[^\[]|\[(???))*))\2\[\/tagId\]
  5. 如是是左中括弧時,根據後面接的字串情況決定要不要匹配,不匹配的情況有兩種,
    第一種:為[tagId=x]
    \[tagId=(\d+)\](?=((?:[^\[]|\[(?!(?:tagId=\d+\]|???)))*))\2\[\/tagId\]
    第二種:為[/tagId]
    \[tagId=(\d+)\](?=((?:[^\[]|\[(?!(?:tagId=\d+\]|\/tagId\])))*))\2\[\/tagId\]

最後我們完成了正規表示法:
\[tagId=(\d+)\](?=((?:[^\[]|\[(?!(?:tagId=\d+\]|\/tagId\])))*))\2\[\/tagId\]

以下為可測試的線上工具:

  1. 圖示化正規表示法
  2. 正規表示法線上實驗,可以檢查Group1、Group2有沒有取到。

2017年2月17日 星期五

正規表示法 - 幫數字字串的每三個位數標上逗號

碰到一個需求是將數字字串的每三個位數標上逗號,
例如:
1234567 ==> 1,234,456

發現原來正規表示法也可以辦到,特別在這邊做個紀錄。

以Javascript為例,以下的方法numberWithCommas()可以將輸入的整數字串每隔3位數標上逗號

function numberWithCommas(x) {
  return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

使用的正規表示法為
\B(?=(\d{3})+(?!\d))

先來看看它的圖型化表示

圖型化表示連結


解釋:
我們要匹配的是從低位數數起,每三個位數的位置(是位置,不是字元),但不包括左邊沒數字的位置
例如123456789,匹配的位置有
3跟4這間、6跟7中間,但1左邊不應該要有。



\B   :   非字元邊界,在此列中即左邊不應該有空白

x(?=y)   :   x接著y才匹配成功,並且y不列入匹配成功的字串中,只匹配出x,如果x沒寫,則 匹配出來的為位置

\d{3}  :   3個數字字元

x(?!y)   :   x後面不跟著y才匹配成功

所以組合出來的就是

  1. 左邊不要空格(即不要左邊界) ==>  \B
  2. 比對後面符合我們要的條件的位置  ==> \B(?=)
  3. 條件之一,1到多個的,3個為一組的數字字元  ==>  \B(?=(\d{3})+)
  4. 條件之二,條件之一的最後面不可是數字字元  ==>  \B(?=(\d{3})+(?!\d))
最後,找到位置以後,把位置替換成逗號就行了。