Skip to content

Commit 84abc58

Browse files
authored
Merge pull request #7693 from dull-bird/fix/quadrant-chart-unicode-support
fix(quadrant-chart): add UNICODE_TEXT support for CJK and emoji
2 parents 92dc556 + 652cfa5 commit 84abc58

3 files changed

Lines changed: 179 additions & 1 deletion

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'mermaid': patch
3+
---
4+
5+
fix(quadrant-chart): allow CJK, emoji, Latin-1 accented characters, and other non-ASCII text in unquoted axis/quadrant/point labels.
6+
7+
Previously the lexer only matched ASCII `[A-Za-z]+` for text tokens, even though the grammar referenced `UNICODE_TEXT`. Bare Chinese, Japanese, Korean, emoji, and accented Latin characters in labels caused a parse error. Added a `[^\x00-\x7F]+` lexer rule to emit `UNICODE_TEXT` and included it in the `alphaNumToken` grammar rule.
8+
9+
Fixes #7120.

packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
5757
" "*"quadrantChart"" "* return 'QUADRANT';
5858

5959
[A-Za-z]+ return 'ALPHA';
60+
// BMP non-ASCII: CJK, emoji, accented Latin, Cyrillic, etc.
61+
[^\x00-\x7F]+ return 'UNICODE_TEXT';
6062
":" return 'COLON';
6163
\+ return 'PLUS';
6264
"," return 'COMMA';
@@ -180,7 +182,7 @@ alphaNum
180182
;
181183

182184

183-
alphaNumToken : PUNCTUATION | AMP | NUM| ALPHA | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
185+
alphaNumToken : PUNCTUATION | AMP | NUM| ALPHA | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE | UNICODE_TEXT;
184186

185187
textNoTagsToken: alphaNumToken | SPACE | MINUS;
186188

packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,171 @@ describe('Testing quadrantChart jison file', () => {
433433
expect(parserFnConstructor(str)).not.toThrow();
434434
expect(mockDB.addClass).toHaveBeenCalledWith('constructor', ['fill:#ff0000']);
435435
});
436+
437+
describe('Unicode support (CJK + Emoji)', () => {
438+
it('should be able to parse Chinese text in quadrant labels', () => {
439+
let str = 'quadrantChart\nquadrant-1 需要扩展';
440+
expect(parserFnConstructor(str)).not.toThrow();
441+
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({
442+
text: '需要扩展',
443+
type: 'text',
444+
});
445+
446+
clearMocks();
447+
str = 'quadrantChart\nquadrant-2 需要推广\nquadrant-3 重新评估\nquadrant-4 可以改进';
448+
expect(parserFnConstructor(str)).not.toThrow();
449+
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({
450+
text: '需要推广',
451+
type: 'text',
452+
});
453+
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({
454+
text: '重新评估',
455+
type: 'text',
456+
});
457+
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({
458+
text: '可以改进',
459+
type: 'text',
460+
});
461+
});
462+
463+
it('should be able to parse Chinese text in x-axis', () => {
464+
const str = 'quadrantChart\nx-axis 低覆盖率 --> 高覆盖率';
465+
expect(parserFnConstructor(str)).not.toThrow();
466+
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
467+
text: '低覆盖率',
468+
type: 'text',
469+
});
470+
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({
471+
text: '高覆盖率',
472+
type: 'text',
473+
});
474+
});
475+
476+
it('should be able to parse Chinese text in y-axis', () => {
477+
const str = 'quadrantChart\ny-axis 低参与度 --> 高参与度';
478+
expect(parserFnConstructor(str)).not.toThrow();
479+
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
480+
text: '低参与度',
481+
type: 'text',
482+
});
483+
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({
484+
text: '高参与度',
485+
type: 'text',
486+
});
487+
});
488+
489+
it('should be able to parse Chinese point names', () => {
490+
const str = 'quadrantChart\n产品A: [0.3, 0.6]';
491+
expect(parserFnConstructor(str)).not.toThrow();
492+
expect(mockDB.addPoint).toHaveBeenCalledWith(
493+
{ text: '产品A', type: 'text' },
494+
'',
495+
'0.3',
496+
'0.6',
497+
[]
498+
);
499+
});
500+
501+
it('should be able to parse a full chart with Chinese', () => {
502+
const str = `quadrantChart
503+
title 分析象限图
504+
x-axis 低覆盖率 --> 高覆盖率
505+
y-axis 低参与度 --> 高参与度
506+
quadrant-1 需要扩展
507+
quadrant-2 需要推广
508+
quadrant-3 重新评估
509+
quadrant-4 可以改进
510+
产品A: [0.3, 0.6]
511+
产品B: [0.45, 0.23]`;
512+
expect(parserFnConstructor(str)).not.toThrow();
513+
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('分析象限图');
514+
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
515+
text: '低覆盖率',
516+
type: 'text',
517+
});
518+
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({
519+
text: '高覆盖率',
520+
type: 'text',
521+
});
522+
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
523+
text: '低参与度',
524+
type: 'text',
525+
});
526+
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({
527+
text: '高参与度',
528+
type: 'text',
529+
});
530+
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({
531+
text: '需要扩展',
532+
type: 'text',
533+
});
534+
expect(mockDB.addPoint).toHaveBeenCalledWith(
535+
{ text: '产品A', type: 'text' },
536+
'',
537+
'0.3',
538+
'0.6',
539+
[]
540+
);
541+
expect(mockDB.addPoint).toHaveBeenCalledWith(
542+
{ text: '产品B', type: 'text' },
543+
'',
544+
'0.45',
545+
'0.23',
546+
[]
547+
);
548+
});
549+
550+
it('should be able to parse Japanese text', () => {
551+
const str = 'quadrantChart\nquadrant-1 拡張が必要\nx-axis 低い --> 高い';
552+
expect(parserFnConstructor(str)).not.toThrow();
553+
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({
554+
text: '拡張が必要',
555+
type: 'text',
556+
});
557+
});
558+
559+
it('should be able to parse Korean text', () => {
560+
const str = 'quadrantChart\nx-axis 낮음 --> 높음';
561+
expect(parserFnConstructor(str)).not.toThrow();
562+
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
563+
text: '낮음',
564+
type: 'text',
565+
});
566+
});
567+
568+
it('should be able to parse emoji in text', () => {
569+
const str = 'quadrantChart\nquadrant-2 🚀Growth';
570+
expect(parserFnConstructor(str)).not.toThrow();
571+
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({
572+
text: '🚀Growth',
573+
type: 'text',
574+
});
575+
});
576+
577+
it('should parse Latin-1 accented text (French/Spanish/German)', () => {
578+
const str = 'quadrantChart\nx-axis Café --> Größe';
579+
expect(parserFnConstructor(str)).not.toThrow();
580+
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
581+
text: 'Café',
582+
type: 'text',
583+
});
584+
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({
585+
text: 'Größe',
586+
type: 'text',
587+
});
588+
});
589+
590+
it('should parse accented characters in quadrant labels', () => {
591+
const str = 'quadrantChart\nquadrant-1 catégoría\nquadrant-2 naïve';
592+
expect(parserFnConstructor(str)).not.toThrow();
593+
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({
594+
text: 'catégoría',
595+
type: 'text',
596+
});
597+
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({
598+
text: 'naïve',
599+
type: 'text',
600+
});
601+
});
602+
});
436603
});

0 commit comments

Comments
 (0)