1 #title = "R+"
  2 #tooltip = "ロイヤルインクリメント"
  3 #include "lib\sgc4jsee.jsee"
  4 
  5 /**
  6  * @fileOverview
  7  * マクロ起動時に表示される入力ダイアログに特殊記号を含む正規表現を入力することで、数値を柔軟に変換することができます。<br />
  8  * 変換対象はドキュメント全体です。 ([部分編集の設定/解除]との併用がお勧めです。)<br />
  9  * <br />
 10  * 特殊記号には以下のような種類があります。<br />
 11  * 特殊記号をエスケープしたい場合、"#"の後に"\"を入力してください。<br />
 12  * 
 13  * <ul>
 14  * <li>#+   : インクリメント</li>
 15  * <li>#+2  : インクリメント (増加分:2)</li>
 16  * <li>#-   : デクリメント</li>
 17  * <li>#-2  : デクリメント (減少分:2)</li>
 18  * <li>#@   : 1から始まる連番</li>
 19  * <li>#@2  : 2から始まる連番</li>
 20  * <li>#@05 : 05から始まる2桁に0埋めされた連番</li>
 21  * </ul>
 22  * 
 23  * 選択文字列がある場合、選択文字列の数値部分が連番になるような値を入力ダイアログに設定します。<br />
 24  * 選択文字列がない場合、前回指定した文字列を入力ダイアログに設定します。<br />
 25  * <br />
 26  * インクリメントする場合、必ず変換前の0埋めを保持します。<br />
 27  * デクリメントする場合、変換対象のどれか1つでも0埋めされているものがあれば、変換前の0埋めを保持します。(詳しくは下記のサンプルを見てください。)<br />
 28  * 
 29  * <pre>
 30  * // 1. ドキュメントの中身が以下の場合
 31  * 【-001】
 32  * 【000】
 33  *   【001】
 34  * 【9】
 35  * 【5
 36  * 123【4】567【8】9
 37  * 
 38  * // 1-1. 入力ダイアログに"【#+】"を入力したときの結果
 39  * 【000】
 40  * 【001】
 41  *   【002】
 42  * 【10】
 43  * 【5
 44  * 123【5】567【9】9
 45  * 
 46  * // 1-2. 入力ダイアログに"#-2$"を入力したときの結果
 47  * 【-001】
 48  * 【000】
 49  *   【001】
 50  * 【9】
 51  * 【3
 52  * 123【4】567【8】7
 53  * 
 54  * // 1-3. 入力ダイアログに"【#@05】"を入力したときの結果
 55  * 【05】
 56  * 【06】
 57  *   【07】
 58  * 【08】
 59  * 【5
 60  * 123【09】567【10】9
 61  * 
 62  * // 2. ドキュメントの中身が以下の場合
 63  * 3
 64  * 10
 65  * 
 66  * // 2-1. 入力ダイアログに"#-"を入力したときの結果
 67  * 2
 68  * 9
 69  * 
 70  * // 3. ドキュメントの中身が以下の場合
 71  * 03
 72  * 10
 73  * 
 74  * // 3-1. 入力ダイアログに"#-"を入力したときの結果
 75  * 02
 76  * 09
 77  * </pre>
 78  * 
 79  * @author gecca from 雪月花 (http://setsugecca.org/)
 80  * @version 1.00 for EmEditor v10.0
 81  */
 82 
 83 (function() {
 84 	// 部分編集かどうか事前にチェックするならtrue、しないならfalse
 85 	var checkPartialEdit = false;
 86 	
 87 	var logger = JseeUtil.getLogger(false, "ロイヤルインクリメント.jsee");
 88 	
 89 	var tempFilePath = JseeUtil.getTempDirPath() + "EmEditorMacros_ロイヤルインクリメント.tmp";
 90 	
 91 	// 選択範囲の保存
 92 	var topX = document.selection.GetTopPointX(eePosLogical);
 93 	var topY = document.selection.GetTopPointY(eePosLogical);
 94 	var bottomX = document.selection.GetBottomPointX(eePosLogical);
 95 	var bottomY = document.selection.GetBottomPointY(eePosLogical);
 96 	
 97 	// 初期選択文字列
 98 	var selectedText = document.selection.Text;
 99 	
100 	if(checkPartialEdit) {
101 		logger.log("");
102 		logger.log("■部分編集チェック");
103 		if(!(function() {
104 			document.selection.StartOfDocument(false);
105 			var startPos = document.selection.GetActivePointY(eePosLogical);
106 			document.selection.EndOfDocument(false);
107 			var endPos = document.selection.GetActivePointY(eePosLogical);
108 			if(startPos == 1 && endPos == document.getLines()) {
109 				// 選択範囲を復元してから確認ダイアログを出す (強制Redrawされるため)
110 				document.selection.SetActivePoint(eePosLogical, topX, topY);
111 				document.selection.SetActivePoint(eePosLogical, bottomX, bottomY, true);
112 				return confirm("部分編集の状態にはないため、処理がドキュメント全体に及びます。処理を続けてますか?");
113 			}
114 			return true;
115 		})()) {
116 			return;
117 		}
118 	}
119 	
120 	logger.log("■入力ダイアログの初期値を決定");
121 	var initValue = (function() {
122 		if(!selectedText) {
123 			logger.log("    選択範囲がなければファイルから復元した前回入力値を初期値に設定");
124 			var savedInput = JseeUtil.readFile(tempFilePath);
125 			logger.log("savedInput:" + savedInput);
126 			if(savedInput) {
127 				logger.log("    ファイルから復元できた場合はそれを初期値に設定");
128 				return savedInput;
129 			} else {
130 				logger.log("    ファイルから復元できない場合は規定値を初期値に設定");
131 				return "#@";
132 			}
133 		} else {
134 			logger.log("    選択範囲があれば選択範囲を基に初期値を設定");
135 			return JseeUtil.quote(selectedText.match(/^([^\r\n]+)/)[0].replace(/-?\d+/g, "#@"));
136 		}
137 	})();
138 	logger.log("initValue:" + initValue);
139 	
140 	logger.log("■入力ダイアログを表示して入力値を取得");
141 	var input = prompt("#+や#+2、#-や#-2、#@や#@2や#@03などのいずれか1つを含む正規表現", initValue);
142 	if(!input) return;
143 	logger.log("input:" + input);
144 	
145 	logger.log("■生の入力値を保存");
146 	JseeUtil.writeFile(tempFilePath, input.replace(/\r/g, "\\r").replace(/\n/g, "\\n"));
147 	
148 	logger.log("");
149 	logger.log("■入力値チェック");
150 	// 丸括弧チェック
151 	var unescaped = input.match(/[()]/g);
152 	var escaped = input.match(/\\[()]/g);
153 	if(unescaped && escaped && unescaped.length != escaped.length) {
154 		alert("入力値にエスケープされていない丸括弧は使用できません。");
155 		return
156 	}
157 	// 正規表現の整合性チェック
158 	try {
159 		new RegExp(input);
160 	} catch(e) {
161 		alert("正規表現として正しくありません。\n\n" + e.message);
162 		return;
163 	}
164 	
165 	logger.log("");
166 	logger.log("■入力値の調整");
167 	(function() {
168 		var m = document.getLine(1, eeGetLineWithNewLines).match(/(\r\n|\r|\n)/);
169 		if(m) {
170 			var docBr = m[0];
171 			logger.log("docBr:" + docBr.replace(/\r/g, "\\r").replace(/\n/g, "\\n"));
172 		}
173 		
174 		var onlyLf = /(^|[^\\r])\\n/g;
175 		if(logger.available()) {
176 			logger.log("input.match(onlyLf):" + input.match(onlyLf));
177 		}
178 		if(docBr == "\r\n" && input.match(onlyLf)) {
179 			if(confirm("入力値の\"\\n\"を\"\\r\\n\"に置き換えますか?")) {
180 				input = input.replace(onlyLf, "$1\r\n");
181 			}
182 		}
183 	})();
184 	
185 	logger.log("■調整済みの入力値を保存");
186 	JseeUtil.writeFile(tempFilePath, input.replace(/\r/g, "\\r").replace(/\n/g, "\\n"));
187 	
188 	logger.log("");
189 	logger.log("■特殊記号を抽出");
190 	var mark = (function() {
191 		var marks = input.match(/#[+\-@]\d*/g);
192 		logger.log("marks:" + marks);
193 		if(!marks) {
194 			alert("#+や#+2、#-や#-2、#@や#@2や#@03などの\nいずれか1つを含むように指定してください。\n\n[入力値]\n" + input);
195 			return;
196 		} else if(marks.length > 1) {
197 			alert("#+や#+2、#-や#-2、#@や#@2や#@03などは\nいずれか1種類のみ指定してください。\n\n[入力値]\n" + input);
198 			return;
199 		}
200 		return marks[0];
201 	})();
202 	logger.log("mark:" + mark);
203 	if(JseeUtil.isUndef(mark)) return;
204 	
205 	logger.log("");
206 	logger.log("■置換対象文字列へのマッチャー");
207 	var replaceTargetMatcher = (function() {
208 		var splitted = input.split(mark);
209 		var leftword = splitted[0];
210 		var rightword = splitted[1];
211 		logger.log("leftword:" + leftword);
212 		logger.log("rightword:" + rightword);
213 		return new RegExp("(" + leftword + ")(\\d+|-\\d+)(" + rightword + ")", "gm");
214 	})();
215 	logger.log("replaceTargetMatcher:" + replaceTargetMatcher);
216 	
217 	// 全選択し、変換対象文字列を保持
218 	var targetText = JseeUtil.getAllText();
219 	
220 	logger.log("");
221 	logger.log("■変換処理を定義");
222 	var changeNumber = (function() {
223 		if(mark.match(/#\+(\d*)/)) {
224 			logger.log("◆インクリメントして0埋めして元の桁数に戻す");
225 			var howInc = Number(RegExp.$1 ? RegExp.$1 : "1");
226 			logger.log("howInc:" + howInc);
227 			return function(num, digit) {
228 				return JseeUtil.zeroFill(Number(num) + howInc, digit);
229 			};
230 		}
231 		if(mark.match(/#\-(\d*)/)) {
232 			logger.log("◆ゼロ埋めして元の桁数に戻すかどうか");
233 			var isNeededZeroFill = (function() {
234 				var splitted = input.split(mark);
235 				var leftword = splitted[0];
236 				var rightword = splitted[1];
237 				return new RegExp("(" + leftword + ")0(\\d+|-\\d+)(" + rightword + ")", "gm").test(targetText);
238 			})();
239 			logger.log("isNeededZeroFill:" + isNeededZeroFill);
240 			
241 			logger.log(isNeededZeroFill ? "◆デクリメントして0埋めして元の桁数に戻す" : "◆デクリメント");
242 			var howDec = Number(RegExp.$1 ? RegExp.$1 : "1");
243 			logger.log("howDec:" + howDec);
244 			return function(num, digit) {
245 				if(isNeededZeroFill) {
246 					return JseeUtil.zeroFill(Number(num) - howDec, digit);
247 				} else {
248 					return Number(num) - howDec;
249 				}
250 			};
251 		}
252 		if(mark.match(/#@(\d*)/g)) {
253 			logger.log("◆特殊記号の情報を基に、0埋めされた連番を返却");
254 			var matched = RegExp.$1 ? RegExp.$1 : "1";
255 			var cnt = Number(matched);
256 			var digit = matched.length;
257 			logger.log("cnt:" + cnt);
258 			logger.log("digit:" + digit);
259 			return function(num) {
260 				return JseeUtil.zeroFill(cnt++, digit);
261 			};
262 		}
263 	})();
264 	logger.log("↓changeNumber\n" + changeNumber);
265 	
266 	logger.log("");
267 	logger.log("■置換処理");
268 	var changeCnt = 0;
269 	var resultAllText = (function() {
270 		// 結果を格納するバッファ
271 		var buf = "";
272 		
273 		// 前回マッチ位置の初期値は0
274 		var prevIndex = 0;
275 		
276 		// マッチしなくなるまでループ
277 		for(;;changeCnt++) {
278 			logger.log("------------------------------------------------------");
279 			
280 			// マッチ
281 			var execed = replaceTargetMatcher.exec(targetText);
282 			logger.log("execed:" + execed);
283 			if(!execed) {
284 				// マッチしなくなったら、前回マッチした位置から最後までをバッファに詰めて終了
285 				buf += targetText.substring(prevIndex);
286 				break;
287 			}
288 			
289 			// マッチ結果を整理
290 			var originalWord = execed[0];
291 			var leftword = execed[1];
292 			var originalNum = execed[2];
293 			var rightword = execed[3];
294 			var originalNumDigit = (Number(originalNum) >= 0) ? originalNum.length : originalNum.length - 1;
295 			
296 			if(logger.available()) {
297 				logger.log(execed);
298 				logger.log("    originalWord:" + originalWord);
299 				logger.log("    leftword:" + leftword);
300 				logger.log("    originalNum:" + originalNum);
301 				logger.log("    rightword:" + rightword);
302 				logger.log("    originalNumDigit:" + originalNumDigit);
303 				logger.log("    prevIndex:" + prevIndex);
304 				logger.log("    replaceTargetMatcher.lastIndex:" + replaceTargetMatcher.lastIndex);
305 				logger.log("    originalNum.length:" + originalNum.length);
306 				logger.log("    replaceTargetMatcher.lastIndex - originalWord.length:" + String(replaceTargetMatcher.lastIndex - originalWord.length));
307 			}
308 			
309 			// 前回マッチした位置から、今回マッチした位置までをバッファに詰める
310 			buf += targetText.substring(prevIndex, replaceTargetMatcher.lastIndex - originalWord.length);
311 			
312 			// 数値変換処理
313 			var changedNum = changeNumber(originalNum, originalNumDigit);
314 			logger.log("    changedNum:" + changedNum);
315 			
316 			// 今回マッチした数値の代わりに、変換後の数値をバッファに詰める
317 			buf += leftword + changedNum + rightword;
318 			
319 			// 今回のマッチ位置を前回のマッチ位置に設定
320 			prevIndex = replaceTargetMatcher.lastIndex;
321 		}
322 		
323 		return buf;
324 	})();
325 	
326 	if(changeCnt == 0) {
327 		alert("指定したフォーマットにマッチする文字列がありません。");
328 		return;
329 	}
330 	
331 	logger.log("");
332 	logger.log("■結果反映");
333 	document.selection.SelectAll();
334 	document.selection.Text = resultAllText;
335 	
336 	logger.log("");
337 	logger.log("■キャレット位置復元");
338 	document.selection.SetActivePoint(eePosLogical, topX, topY);
339 	
340 	logger.log("");
341 	logger.log("■ハイライトと置換件数表示");
342 	document.selection.Find(replaceTargetMatcher.source ,eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeFindAround | eeFindReplaceRegExp | eeFindCount);
343 	alert(changeCnt + "個置換しました。");
344 })();
345