using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Threading;
namespace AS2JS
{
public partial class MainWindow : Window
{
private string _lastInputFilePath = null; // 마지막으로 읽어들인 .as 파일 경로
private bool _isSyncing = false; // 스크롤 동기화 중복 호출 방지 플래그
// ─────────────────────────────────────────────────────────────────────
// 1) 변환 규칙 정의용 클래스
private class TransformRule
{
public Regex Pattern; // 매칭할 정규식
public string Replacement; // 치환할 문자열
}
// 2) ActionScript → JavaScript 변환 규칙 목록
// 필요한 만큼 항목을 추가하면 됩니다.
private static readonly List<TransformRule> _rules = new List<TransformRule>
{
new TransformRule {
Pattern = new Regex(@"\bvar\s+(\w+):\w+\s*=", RegexOptions.Compiled),
Replacement = "let $1 ="
},
new TransformRule {
Pattern = new Regex(@"\btrace\s*\(", RegexOptions.Compiled),
Replacement = "console.log("
},
// 예시: 클래스/함수 변환 규칙 추가 방법
// new TransformRule {
// Pattern = new Regex(@"\bfunction\s+(\w+)\s*\(", RegexOptions.Compiled),
// Replacement = "$1("
// },
// new TransformRule {
// Pattern = new Regex(@"\bclass\s+(\w+)\b", RegexOptions.Compiled),
// Replacement = "class $1"
// },
};
public MainWindow()
{
InitializeComponent();
}
// 드래그 중 커서 처리 (복사 모양 유지)
private void Window_PreviewDragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
// 파일 드롭 이벤트 핸들러
private void Window_Drop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length == 0 || Path.GetExtension(files[0]).ToLower() != ".as") return;
_lastInputFilePath = files[0];
// 1) Shift-JIS로 .as 파일 읽기
string asText = File.ReadAllText(_lastInputFilePath, Encoding.GetEncoding(932));
var asLines = asText.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
// 2) 각 줄을 변환하면서 하이라이트 범위 계산
var converted = asLines
.Select(line => ConvertLineWithSpans(line))
.ToList();
// 3) 원본 AS 코드 표시
AsRichTextBox.Document.Blocks.Clear();
foreach (var line in asLines)
AsRichTextBox.Document.Blocks.Add(new Paragraph(new Run(line)));
// 4) 변환된 JS 코드 + 하이라이트 표시
JsRichTextBox.Document.Blocks.Clear();
foreach (var cl in converted)
JsRichTextBox.Document.Blocks.Add(BuildParagraphFromSpans(cl));
// 5) 드롭 직후에 스크롤 싱크 설정
Dispatcher.BeginInvoke(new Action(SyncScroll), DispatcherPriority.ApplicationIdle);
}
// ─────────────────────────────────────────────────────────────────────
// DTO: 변환된 텍스트와 하이라이트 스팬(시작위치, 길이) 저장용
private class ConvertedLine
{
public string Text;
public List<(int Start, int Length)> Spans;
}
// 3) 한 줄을 규칙에 따라 변환하고, 치환된 부분만 스팬으로 기록
private ConvertedLine ConvertLineWithSpans(string original)
{
string working = original;
var spans = new List<(int, int)>();
foreach (var rule in _rules)
{
// 매치되는 모든 부분 찾기
var matches = rule.Pattern.Matches(working).Cast<Match>().ToList();
int offset = 0;
foreach (var m in matches)
{
// 치환 결과 문자열
string rep = m.Result(rule.Replacement);
// 치환 전 위치 + 이전 오프셋
int start = m.Index + offset;
spans.Add((start, rep.Length));
// 오프셋 보정
offset += rep.Length - m.Length;
}
// 한 번에 치환
working = rule.Pattern.Replace(working, rule.Replacement);
}
return new ConvertedLine { Text = working, Spans = spans };
}
// 4) Recorded 스팬을 이용해 Paragraph 구성 (배경 + 빨간 글씨)
private Paragraph BuildParagraphFromSpans(ConvertedLine cl)
{
var para = new Paragraph();
// 변환된 토큰이 하나라도 있으면 줄 전체 배경색
if (cl.Spans.Any())
para.Background = Brushes.LightGoldenrodYellow;
int pos = 0;
foreach (var (start, len) in cl.Spans.OrderBy(s => s.Start))
{
// 변경 전 텍스트 부분
if (start > pos)
para.Inlines.Add(new Run(cl.Text.Substring(pos, start - pos)));
// 변경된 텍스트 부분 (빨간색)
para.Inlines.Add(new Run(cl.Text.Substring(start, len))
{
Foreground = Brushes.Red
});
pos = start + len;
}
// 남은 일반 텍스트
if (pos < cl.Text.Length)
para.Inlines.Add(new Run(cl.Text.Substring(pos)));
return para;
}
// 저장 버튼 클릭 이벤트
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
if (JsRichTextBox.Document == null) return;
var dlg = new Microsoft.Win32.SaveFileDialog
{
FileName = Path.GetFileNameWithoutExtension(_lastInputFilePath) + ".js",
Filter = "JavaScript 파일 (*.js)|*.js"
};
if (dlg.ShowDialog() != true) return;
// RichTextBox 전체 텍스트 추출
var text = new TextRange(
JsRichTextBox.Document.ContentStart,
JsRichTextBox.Document.ContentEnd
).Text;
File.WriteAllText(dlg.FileName, text, Encoding.GetEncoding(932));
MessageBox.Show("저장 완료", "완료", MessageBoxButton.OK, MessageBoxImage.Information);
}
// 5) 스크롤 동기화 설정
private void SyncScroll()
{
var sv1 = GetScrollViewer(AsRichTextBox);
var sv2 = GetScrollViewer(JsRichTextBox);
if (sv1 == null || sv2 == null) return;
// 중복 핸들러 방지
sv1.ScrollChanged -= Left_ScrollChanged;
sv2.ScrollChanged -= Right_ScrollChanged;
sv1.ScrollChanged += Left_ScrollChanged;
sv2.ScrollChanged += Right_ScrollChanged;
}
// 왼쪽 RichTextBox 스크롤 이벤트
private void Left_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (_isSyncing) return;
_isSyncing = true;
var sv1 = (ScrollViewer)sender;
var sv2 = GetScrollViewer(JsRichTextBox);
sv2?.ScrollToVerticalOffset(sv1.VerticalOffset);
_isSyncing = false;
}
// 오른쪽 RichTextBox 스크롤 이벤트
private void Right_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (_isSyncing) return;
_isSyncing = true;
var sv2 = (ScrollViewer)sender;
var sv1 = GetScrollViewer(AsRichTextBox);
sv1?.ScrollToVerticalOffset(sv2.VerticalOffset);
_isSyncing = false;
}
// VisualTreeHelper를 이용해 ScrollViewer 추출
private ScrollViewer GetScrollViewer(DependencyObject depObj)
{
if (depObj is ScrollViewer) return (ScrollViewer)depObj;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetScrollViewer(child);
if (result != null) return result;
}
return null;
}
}
}