markdown-to-document-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/dist/cli.d.ts +10 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +335 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/index.d.ts +32 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +165 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/services/ContentValidator.d.ts +50 -0
  12. package/dist/services/ContentValidator.d.ts.map +1 -0
  13. package/dist/services/ContentValidator.js +385 -0
  14. package/dist/services/ContentValidator.js.map +1 -0
  15. package/dist/services/MarkdownPreprocessor.d.ts +29 -0
  16. package/dist/services/MarkdownPreprocessor.d.ts.map +1 -0
  17. package/dist/services/MarkdownPreprocessor.js +168 -0
  18. package/dist/services/MarkdownPreprocessor.js.map +1 -0
  19. package/dist/services/PandocService.d.ts +82 -0
  20. package/dist/services/PandocService.d.ts.map +1 -0
  21. package/dist/services/PandocService.js +285 -0
  22. package/dist/services/PandocService.js.map +1 -0
  23. package/dist/types/index.d.ts +105 -0
  24. package/dist/types/index.d.ts.map +1 -0
  25. package/dist/types/index.js +5 -0
  26. package/dist/types/index.js.map +1 -0
  27. package/dist/types/validators.d.ts +31 -0
  28. package/dist/types/validators.d.ts.map +1 -0
  29. package/dist/types/validators.js +5 -0
  30. package/dist/types/validators.js.map +1 -0
  31. package/dist/utils/common.d.ts +19 -0
  32. package/dist/utils/common.d.ts.map +1 -0
  33. package/dist/utils/common.js +73 -0
  34. package/dist/utils/common.js.map +1 -0
  35. package/dist/utils/constants.d.ts +24 -0
  36. package/dist/utils/constants.d.ts.map +1 -0
  37. package/dist/utils/constants.js +156 -0
  38. package/dist/utils/constants.js.map +1 -0
  39. package/dist/utils/fileUtils.d.ts +17 -0
  40. package/dist/utils/fileUtils.d.ts.map +1 -0
  41. package/dist/utils/fileUtils.js +70 -0
  42. package/dist/utils/fileUtils.js.map +1 -0
  43. package/dist/utils/markdownUtils.d.ts +27 -0
  44. package/dist/utils/markdownUtils.d.ts.map +1 -0
  45. package/dist/utils/markdownUtils.js +171 -0
  46. package/dist/utils/markdownUtils.js.map +1 -0
  47. package/package.json +70 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Markdown to Document CLI - Main Entry Point
3
+ */
4
+ import type { ConversionOptions, ConversionResult } from './types/index.js';
5
+ export declare class MarkdownToDocument {
6
+ private pandocService;
7
+ private preprocessor;
8
+ private validator;
9
+ constructor(pandocPath?: string);
10
+ /**
11
+ * Initialize and check dependencies
12
+ */
13
+ initialize(): Promise<{
14
+ success: boolean;
15
+ error?: string;
16
+ }>;
17
+ /**
18
+ * Convert markdown to EPUB/PDF
19
+ */
20
+ convert(options: ConversionOptions): Promise<ConversionResult>;
21
+ /**
22
+ * Get Pandoc installation instructions
23
+ */
24
+ static getInstallInstructions(): string;
25
+ }
26
+ export * from './types/index.js';
27
+ export * from './types/validators.js';
28
+ export { PandocService } from './services/PandocService.js';
29
+ export { MarkdownPreprocessor } from './services/MarkdownPreprocessor.js';
30
+ export { ContentValidator } from './services/ContentValidator.js';
31
+ export { DEFAULT_CONFIG } from './utils/constants.js';
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAO5E,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,SAAS,CAAmB;gBAExB,UAAU,CAAC,EAAE,MAAM;IAM/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAkBjE;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAuIpE;;OAEG;IACH,MAAM,CAAC,sBAAsB,IAAI,MAAM;CAG1C;AAGD,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Markdown to Document CLI - Main Entry Point
3
+ */
4
+ import { PandocService } from './services/PandocService.js';
5
+ import { MarkdownPreprocessor } from './services/MarkdownPreprocessor.js';
6
+ import { ContentValidator } from './services/ContentValidator.js';
7
+ import { ensureDirectory, sanitizeFilename, getTempDir } from './utils/fileUtils.js';
8
+ import { Logger } from './utils/common.js';
9
+ import * as path from 'path';
10
+ import * as fs from 'fs';
11
+ export class MarkdownToDocument {
12
+ pandocService;
13
+ preprocessor;
14
+ validator;
15
+ constructor(pandocPath) {
16
+ this.pandocService = new PandocService(pandocPath);
17
+ this.preprocessor = new MarkdownPreprocessor();
18
+ this.validator = new ContentValidator();
19
+ }
20
+ /**
21
+ * Initialize and check dependencies
22
+ */
23
+ async initialize() {
24
+ const pandocInfo = await this.pandocService.checkPandocAvailable();
25
+ if (!pandocInfo.available) {
26
+ return {
27
+ success: false,
28
+ error: pandocInfo.error || 'Pandoc을 찾을 수 없습니다.',
29
+ };
30
+ }
31
+ Logger.info('Initialized successfully', {
32
+ pandocVersion: pandocInfo.version,
33
+ pandocPath: pandocInfo.path,
34
+ });
35
+ return { success: true };
36
+ }
37
+ /**
38
+ * Convert markdown to EPUB/PDF
39
+ */
40
+ async convert(options) {
41
+ const errors = [];
42
+ const warnings = [];
43
+ try {
44
+ // Validate input
45
+ if (!fs.existsSync(options.inputPath)) {
46
+ return {
47
+ success: false,
48
+ errors: [`입력 파일을 찾을 수 없습니다: ${options.inputPath}`],
49
+ warnings: [],
50
+ };
51
+ }
52
+ // Read markdown content
53
+ let content = fs.readFileSync(options.inputPath, 'utf-8');
54
+ // Step 1: Validate content
55
+ let validationReport;
56
+ if (options.validateContent !== false) {
57
+ validationReport = await this.validator.validate(content, options.inputPath);
58
+ if (validationReport.errors > 0) {
59
+ errors.push(`검증 오류: ${validationReport.errors}개 발견`);
60
+ }
61
+ if (validationReport.warnings > 0) {
62
+ warnings.push(`검증 경고: ${validationReport.warnings}개 발견`);
63
+ }
64
+ // Auto-fix if enabled
65
+ if (options.autoFix !== false && validationReport.fixedIssues > 0) {
66
+ content = this.validator.autoFix(content, validationReport);
67
+ warnings.push(`${validationReport.fixedIssues}개 문제 자동 수정됨`);
68
+ }
69
+ }
70
+ // Step 2: Preprocess markdown
71
+ const preprocessResult = await this.preprocessor.preprocess(content, options.inputPath, options.format === 'pdf' ? 'pdf' : 'epub');
72
+ warnings.push(...preprocessResult.warnings);
73
+ // Step 3: Generate clean markdown with frontmatter
74
+ const cleanMarkdown = this.preprocessor.generateCleanMarkdown(preprocessResult, options.format === 'pdf' ? 'pdf' : 'epub');
75
+ // Step 4: Write temporary markdown file
76
+ const tempDir = getTempDir();
77
+ const tempMarkdownPath = path.join(tempDir, 'temp.md');
78
+ fs.writeFileSync(tempMarkdownPath, cleanMarkdown, 'utf-8');
79
+ // Step 5: Determine output paths
80
+ const baseName = sanitizeFilename(preprocessResult.metadata.title || 'output');
81
+ const outputDir = options.outputPath || path.dirname(options.inputPath);
82
+ ensureDirectory(outputDir);
83
+ const epubPath = path.join(outputDir, `${baseName}.epub`);
84
+ const pdfPath = path.join(outputDir, `${baseName}.pdf`);
85
+ // Step 6: Convert to EPUB
86
+ if (options.format === 'epub' || options.format === 'both') {
87
+ const epubResult = await this.pandocService.toEpub({
88
+ inputPath: tempMarkdownPath,
89
+ outputPath: epubPath,
90
+ title: preprocessResult.metadata.title || 'Untitled',
91
+ author: preprocessResult.metadata.author,
92
+ language: preprocessResult.metadata.language,
93
+ coverImagePath: options.coverTheme ? undefined : undefined, // TODO: implement cover generation
94
+ cssPath: options.cssPath,
95
+ typographyPreset: options.typographyPreset,
96
+ tocDepth: options.tocDepth,
97
+ enableFontSubsetting: options.enableFontSubsetting,
98
+ content: cleanMarkdown,
99
+ });
100
+ if (!epubResult.success) {
101
+ errors.push(`EPUB 변환 실패: ${epubResult.error}`);
102
+ }
103
+ }
104
+ // Step 7: Convert to PDF
105
+ if (options.format === 'pdf' || options.format === 'both') {
106
+ const pdfResult = await this.pandocService.toPdf({
107
+ inputPath: tempMarkdownPath,
108
+ outputPath: pdfPath,
109
+ title: preprocessResult.metadata.title || 'Untitled',
110
+ author: preprocessResult.metadata.author,
111
+ language: preprocessResult.metadata.language,
112
+ cssPath: options.cssPath,
113
+ typographyPreset: options.typographyPreset,
114
+ pdfEngine: options.pdfEngine,
115
+ paperSize: options.paperSize,
116
+ tocDepth: options.tocDepth,
117
+ includeToc: options.includeToc,
118
+ enableFontSubsetting: options.enableFontSubsetting,
119
+ content: cleanMarkdown,
120
+ });
121
+ if (!pdfResult.success) {
122
+ errors.push(`PDF 변환 실패: ${pdfResult.error}`);
123
+ }
124
+ }
125
+ // Clean up temp file
126
+ try {
127
+ fs.unlinkSync(tempMarkdownPath);
128
+ }
129
+ catch {
130
+ // Ignore cleanup errors
131
+ }
132
+ // Return result
133
+ const success = errors.length === 0;
134
+ return {
135
+ success,
136
+ epubPath: options.format === 'epub' || options.format === 'both' ? epubPath : undefined,
137
+ pdfPath: options.format === 'pdf' || options.format === 'both' ? pdfPath : undefined,
138
+ errors,
139
+ warnings,
140
+ validationReport,
141
+ };
142
+ }
143
+ catch (error) {
144
+ return {
145
+ success: false,
146
+ errors: [error instanceof Error ? error.message : String(error)],
147
+ warnings,
148
+ };
149
+ }
150
+ }
151
+ /**
152
+ * Get Pandoc installation instructions
153
+ */
154
+ static getInstallInstructions() {
155
+ return PandocService.getInstallInstructions();
156
+ }
157
+ }
158
+ // Export types
159
+ export * from './types/index.js';
160
+ export * from './types/validators.js';
161
+ export { PandocService } from './services/PandocService.js';
162
+ export { MarkdownPreprocessor } from './services/MarkdownPreprocessor.js';
163
+ export { ContentValidator } from './services/ContentValidator.js';
164
+ export { DEFAULT_CONFIG } from './utils/constants.js';
165
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Content Validator - 8 Validation Modules
3
+ */
4
+ import type { ValidationReport } from '../types/index.js';
5
+ import type { ValidatorConfig } from '../types/validators.js';
6
+ export declare class ContentValidator {
7
+ private config;
8
+ constructor(config?: Partial<ValidatorConfig>);
9
+ /**
10
+ * Run all validation modules
11
+ */
12
+ validate(content: string, filePath?: string): Promise<ValidationReport>;
13
+ /**
14
+ * Module 1: Frontmatter validation
15
+ */
16
+ private validateFrontmatter;
17
+ /**
18
+ * Module 2: Heading validation
19
+ */
20
+ private validateHeadings;
21
+ /**
22
+ * Module 3: Link validation
23
+ */
24
+ private validateLinks;
25
+ /**
26
+ * Module 4: Image validation
27
+ */
28
+ private validateImages;
29
+ /**
30
+ * Module 5: Table validation
31
+ */
32
+ private validateTables;
33
+ /**
34
+ * Module 6: Syntax validation
35
+ */
36
+ private validateSyntax;
37
+ /**
38
+ * Module 7: Special character validation
39
+ */
40
+ private validateSpecialCharacters;
41
+ /**
42
+ * Module 8: Accessibility validation
43
+ */
44
+ private validateAccessibility;
45
+ /**
46
+ * Auto-fix content based on validation results
47
+ */
48
+ autoFix(content: string, report: ValidationReport): string;
49
+ }
50
+ //# sourceMappingURL=ContentValidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentValidator.d.ts","sourceRoot":"","sources":["../../src/services/ContentValidator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAmB,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGhF,qBAAa,gBAAgB;IACzB,OAAO,CAAC,MAAM,CAAkB;gBAEpB,MAAM,GAAE,OAAO,CAAC,eAAe,CAAM;IAWjD;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkE7E;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuC3B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA8CxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAiDrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAoCtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAoCtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAiCtB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM;CA4B7D"}
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Content Validator - 8 Validation Modules
3
+ */
4
+ import { Logger } from '../utils/common.js';
5
+ export class ContentValidator {
6
+ config;
7
+ constructor(config = {}) {
8
+ this.config = {
9
+ autoFix: config.autoFix ?? true,
10
+ strictMode: config.strictMode ?? false,
11
+ allowedImageFormats: config.allowedImageFormats ?? ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'],
12
+ maxImageSize: config.maxImageSize ?? 10 * 1024 * 1024, // 10MB
13
+ requireAltText: config.requireAltText ?? true,
14
+ checkExternalLinks: config.checkExternalLinks ?? false,
15
+ };
16
+ }
17
+ /**
18
+ * Run all validation modules
19
+ */
20
+ async validate(content, filePath) {
21
+ const issues = [];
22
+ Logger.info('Starting content validation...');
23
+ // Module 1: Frontmatter validation
24
+ const frontmatterResult = this.validateFrontmatter(content);
25
+ issues.push(...frontmatterResult);
26
+ // Module 2: Heading validation
27
+ const headingResult = this.validateHeadings(content);
28
+ issues.push(...headingResult);
29
+ // Module 3: Link validation
30
+ const linkResult = this.validateLinks(content);
31
+ issues.push(...linkResult);
32
+ // Module 4: Image validation
33
+ const imageResult = this.validateImages(content);
34
+ issues.push(...imageResult);
35
+ // Module 5: Table validation
36
+ const tableResult = this.validateTables(content);
37
+ issues.push(...tableResult);
38
+ // Module 6: Syntax validation
39
+ const syntaxResult = this.validateSyntax(content);
40
+ issues.push(...syntaxResult);
41
+ // Module 7: Special character validation
42
+ const specialResult = this.validateSpecialCharacters(content);
43
+ issues.push(...specialResult);
44
+ // Module 8: Accessibility validation
45
+ const accessibilityResult = this.validateAccessibility(content);
46
+ issues.push(...accessibilityResult);
47
+ // Auto-fix if enabled
48
+ let fixedIssues = 0;
49
+ if (this.config.autoFix) {
50
+ for (const issue of issues) {
51
+ if (issue.fixed) {
52
+ fixedIssues++;
53
+ }
54
+ }
55
+ }
56
+ const errors = issues.filter(i => i.type === 'error');
57
+ const warnings = issues.filter(i => i.type === 'warning');
58
+ Logger.info('Validation completed', {
59
+ total: issues.length,
60
+ errors: errors.length,
61
+ warnings: warnings.length,
62
+ fixed: fixedIssues,
63
+ });
64
+ return {
65
+ totalIssues: issues.length,
66
+ fixedIssues,
67
+ warnings: warnings.length,
68
+ errors: errors.length,
69
+ details: issues,
70
+ };
71
+ }
72
+ /**
73
+ * Module 1: Frontmatter validation
74
+ */
75
+ validateFrontmatter(content) {
76
+ const issues = [];
77
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
78
+ const match = content.match(frontmatterRegex);
79
+ if (!match) {
80
+ issues.push({
81
+ type: 'warning',
82
+ category: 'frontmatter',
83
+ message: 'YAML frontmatter가 없습니다. 메타데이터를 추가하는 것을 권장합니다.',
84
+ suggestion: '문서 상단에 ---로 감싸진 YAML frontmatter를 추가하세요.',
85
+ });
86
+ return issues;
87
+ }
88
+ const yamlContent = match[1];
89
+ const lines = yamlContent.split('\n');
90
+ for (let i = 0; i < lines.length; i++) {
91
+ const line = lines[i];
92
+ const trimmed = line.trim();
93
+ if (!trimmed || trimmed.startsWith('#'))
94
+ continue;
95
+ const colonIndex = trimmed.indexOf(':');
96
+ if (colonIndex === -1) {
97
+ issues.push({
98
+ type: 'warning',
99
+ category: 'frontmatter',
100
+ message: `YAML 구문 오류: 콜론(:)이 누락됨`,
101
+ line: i + 1,
102
+ suggestion: 'key: value 형식을 확인하세요.',
103
+ });
104
+ }
105
+ }
106
+ return issues;
107
+ }
108
+ /**
109
+ * Module 2: Heading validation
110
+ */
111
+ validateHeadings(content) {
112
+ const issues = [];
113
+ const lines = content.split('\n');
114
+ const h1Titles = [];
115
+ let lastLevel = 0;
116
+ for (let i = 0; i < lines.length; i++) {
117
+ const line = lines[i];
118
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
119
+ if (headingMatch) {
120
+ const level = headingMatch[1].length;
121
+ const title = headingMatch[2].trim();
122
+ // Check for duplicate H1
123
+ if (level === 1) {
124
+ if (h1Titles.includes(title)) {
125
+ issues.push({
126
+ type: 'warning',
127
+ category: 'heading',
128
+ message: `중복된 H1 제목: "${title}"`,
129
+ line: i + 1,
130
+ suggestion: 'H1은 문서당 하나만 사용하는 것을 권장합니다.',
131
+ });
132
+ }
133
+ h1Titles.push(title);
134
+ }
135
+ // Check for heading level gaps
136
+ if (lastLevel > 0 && level > lastLevel + 1) {
137
+ issues.push({
138
+ type: 'warning',
139
+ category: 'heading',
140
+ message: `제목 레벨 갭: H${lastLevel} → H${level}`,
141
+ line: i + 1,
142
+ suggestion: `H${lastLevel + 1}을 건너뛰었습니다.`,
143
+ });
144
+ }
145
+ lastLevel = level;
146
+ }
147
+ }
148
+ return issues;
149
+ }
150
+ /**
151
+ * Module 3: Link validation
152
+ */
153
+ validateLinks(content) {
154
+ const issues = [];
155
+ const lines = content.split('\n');
156
+ // Check for Obsidian links [[link]]
157
+ const obsidianLinkRegex = /\[\[([^\]]+)\]\]/g;
158
+ let match;
159
+ while ((match = obsidianLinkRegex.exec(content)) !== null) {
160
+ issues.push({
161
+ type: 'info',
162
+ category: 'link',
163
+ message: `Obsidian 링크 발견: "${match[1]}"`,
164
+ suggestion: '자동으로 표준 마크다운 링크로 변환됩니다.',
165
+ fixed: true,
166
+ });
167
+ }
168
+ // Check for broken markdown links
169
+ const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
170
+ let linkMatch;
171
+ while ((linkMatch = markdownLinkRegex.exec(content)) !== null) {
172
+ const url = linkMatch[2];
173
+ // Check for empty URLs
174
+ if (!url || url.trim() === '') {
175
+ issues.push({
176
+ type: 'error',
177
+ category: 'link',
178
+ message: '빈 링크 URL',
179
+ suggestion: '링크 URL을 입력하세요.',
180
+ });
181
+ }
182
+ // Check for external links if enabled
183
+ if (this.config.checkExternalLinks && url.startsWith('http')) {
184
+ issues.push({
185
+ type: 'info',
186
+ category: 'link',
187
+ message: `외부 링크: ${url}`,
188
+ suggestion: '외부 링크는 변환 후에도 작동해야 합니다.',
189
+ });
190
+ }
191
+ }
192
+ return issues;
193
+ }
194
+ /**
195
+ * Module 4: Image validation
196
+ */
197
+ validateImages(content) {
198
+ const issues = [];
199
+ // Check for images without alt text
200
+ const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
201
+ let match;
202
+ while ((match = imageRegex.exec(content)) !== null) {
203
+ const alt = match[1];
204
+ const url = match[2];
205
+ // Check for missing alt text
206
+ if (this.config.requireAltText && (!alt || alt.trim() === '')) {
207
+ issues.push({
208
+ type: 'warning',
209
+ category: 'image',
210
+ message: '이미지 alt 텍스트가 없습니다.',
211
+ suggestion: '접근성을 위해 이미지에 설명을 추가하세요.',
212
+ });
213
+ }
214
+ // Check image format
215
+ const ext = url.split('.').pop()?.toLowerCase();
216
+ if (ext && !this.config.allowedImageFormats.includes(ext)) {
217
+ issues.push({
218
+ type: 'warning',
219
+ category: 'image',
220
+ message: `지원하지 않는 이미지 형식: ${ext}`,
221
+ suggestion: `지원 형식: ${this.config.allowedImageFormats.join(', ')}`,
222
+ });
223
+ }
224
+ }
225
+ return issues;
226
+ }
227
+ /**
228
+ * Module 5: Table validation
229
+ */
230
+ validateTables(content) {
231
+ const issues = [];
232
+ const lines = content.split('\n');
233
+ let inTable = false;
234
+ let columnCount = 0;
235
+ for (let i = 0; i < lines.length; i++) {
236
+ const line = lines[i].trim();
237
+ if (line.startsWith('|') && line.endsWith('|')) {
238
+ if (!inTable) {
239
+ inTable = true;
240
+ const cells = line.split('|').filter(c => c.trim() !== '');
241
+ columnCount = cells.length;
242
+ }
243
+ else {
244
+ const cells = line.split('|').filter(c => c.trim() !== '');
245
+ if (cells.length !== columnCount && !line.includes('---')) {
246
+ issues.push({
247
+ type: 'warning',
248
+ category: 'table',
249
+ message: `테이블 열 불일치: ${columnCount}개 예상, ${cells.length}개 발견`,
250
+ line: i + 1,
251
+ suggestion: '모든 행의 열 수를 일치시키세요.',
252
+ });
253
+ }
254
+ }
255
+ }
256
+ else {
257
+ inTable = false;
258
+ columnCount = 0;
259
+ }
260
+ }
261
+ return issues;
262
+ }
263
+ /**
264
+ * Module 6: Syntax validation
265
+ */
266
+ validateSyntax(content) {
267
+ const issues = [];
268
+ // Check for unclosed code blocks
269
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
270
+ const codeBlockMatches = content.match(/```/g);
271
+ if (codeBlockMatches && codeBlockMatches.length % 2 !== 0) {
272
+ issues.push({
273
+ type: 'error',
274
+ category: 'syntax',
275
+ message: '닫히지 않은 코드 블록',
276
+ suggestion: '코드 블록을 ```로 닫으세요.',
277
+ fixed: true,
278
+ });
279
+ }
280
+ // Check for unclosed inline code
281
+ const inlineCodeMatches = content.match(/`[^`]+`/g);
282
+ const backtickCount = (content.match(/`/g) || []).length;
283
+ if (backtickCount % 2 !== 0) {
284
+ issues.push({
285
+ type: 'warning',
286
+ category: 'syntax',
287
+ message: '닫히지 않은 인라인 코드',
288
+ suggestion: '인라인 코드를 `로 닫으세요.',
289
+ });
290
+ }
291
+ return issues;
292
+ }
293
+ /**
294
+ * Module 7: Special character validation
295
+ */
296
+ validateSpecialCharacters(content) {
297
+ const issues = [];
298
+ // Check for emojis (may not render correctly in some formats)
299
+ const emojiRegex = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu;
300
+ const emojis = content.match(emojiRegex);
301
+ if (emojis && emojis.length > 0) {
302
+ issues.push({
303
+ type: 'warning',
304
+ category: 'syntax',
305
+ message: `${emojis.length}개의 이모지 발견`,
306
+ suggestion: '일부 형식에서 이모지가 올바르게 렌더링되지 않을 수 있습니다.',
307
+ });
308
+ }
309
+ // Check for ASCII diagrams
310
+ const asciiDiagramRegex = /[+\-|\/\\]{10,}/g;
311
+ if (asciiDiagramRegex.test(content)) {
312
+ issues.push({
313
+ type: 'info',
314
+ category: 'syntax',
315
+ message: 'ASCII 다이어그램 발견',
316
+ suggestion: '다이어그램은 이미지로 변환하는 것을 권장합니다.',
317
+ });
318
+ }
319
+ return issues;
320
+ }
321
+ /**
322
+ * Module 8: Accessibility validation
323
+ */
324
+ validateAccessibility(content) {
325
+ const issues = [];
326
+ // Check for proper heading hierarchy
327
+ const lines = content.split('\n');
328
+ let hasH1 = false;
329
+ for (const line of lines) {
330
+ if (line.startsWith('# ')) {
331
+ hasH1 = true;
332
+ break;
333
+ }
334
+ }
335
+ if (!hasH1) {
336
+ issues.push({
337
+ type: 'warning',
338
+ category: 'accessibility',
339
+ message: 'H1 제목이 없습니다.',
340
+ suggestion: '문서에 H1 제목을 추가하세요.',
341
+ });
342
+ }
343
+ // Check for long paragraphs (hard to read)
344
+ const paragraphs = content.split('\n\n');
345
+ for (let i = 0; i < paragraphs.length; i++) {
346
+ const paragraph = paragraphs[i].trim();
347
+ const wordCount = paragraph.split(/\s+/).length;
348
+ if (wordCount > 300) {
349
+ issues.push({
350
+ type: 'info',
351
+ category: 'accessibility',
352
+ message: `긴 문단 발견 (${wordCount}단어)`,
353
+ suggestion: '가독성을 위해 문단을 나누는 것을 권장합니다.',
354
+ });
355
+ }
356
+ }
357
+ return issues;
358
+ }
359
+ /**
360
+ * Auto-fix content based on validation results
361
+ */
362
+ autoFix(content, report) {
363
+ let fixedContent = content;
364
+ for (const issue of report.details) {
365
+ if (issue.fixed && issue.category === 'link') {
366
+ // Fix Obsidian links
367
+ fixedContent = fixedContent.replace(/\[\[([^\]]+)\]\]/g, (match, link) => {
368
+ const parts = link.split('|');
369
+ const text = parts[1] || parts[0];
370
+ const target = parts[0];
371
+ return target.startsWith('http') ? `[${text}](${target})` : `[${text}](${target}.md)`;
372
+ });
373
+ }
374
+ if (issue.fixed && issue.category === 'syntax') {
375
+ // Fix unclosed code blocks
376
+ const codeBlockCount = (fixedContent.match(/```/g) || []).length;
377
+ if (codeBlockCount % 2 !== 0) {
378
+ fixedContent += '\n```';
379
+ }
380
+ }
381
+ }
382
+ return fixedContent;
383
+ }
384
+ }
385
+ //# sourceMappingURL=ContentValidator.js.map