@@ -32,15 +32,17 @@ export function buildMermaidErDiagram(
3232 const fkColumnNames = new Set ( table . foreignKeys . map ( ( fk ) => fk . column_name ) ) ;
3333
3434 const aliasDiffersFromOriginal = alias !== table . tableName ;
35- const header = aliasDiffersFromOriginal ? ` ${ alias } ["${ escapeQuotes ( table . tableName ) } "] {` : ` ${ alias } {` ;
35+ const header = aliasDiffersFromOriginal
36+ ? ` ${ alias } ["${ sanitizeQuotedText ( table . tableName ) } "] {`
37+ : ` ${ alias } {` ;
3638 lines . push ( header ) ;
3739
3840 if ( table . structure . length === 0 ) {
3941 lines . push ( ' string _empty_ "no columns"' ) ;
4042 } else {
4143 for ( const column of table . structure ) {
42- const dataType = sanitizeIdentifier ( column . data_type || column . udt_name || 'unknown' ) ;
43- const colName = sanitizeIdentifier ( column . column_name ) ;
44+ const dataType = toAttributeWord ( column . data_type || column . udt_name || 'unknown' ) ;
45+ const colName = toAttributeWord ( column . column_name ) ;
4446 const markers : Array < string > = [ ] ;
4547 if ( pkColumnNames . has ( column . column_name ) ) markers . push ( 'PK' ) ;
4648 if ( fkColumnNames . has ( column . column_name ) ) markers . push ( 'FK' ) ;
@@ -58,7 +60,7 @@ export function buildMermaidErDiagram(
5860 for ( const fk of table . foreignKeys ) {
5961 const targetAlias = aliasByTable . get ( fk . referenced_table_name ) ;
6062 if ( ! targetAlias ) continue ;
61- const label = `"${ escapeQuotes ( fk . column_name ) } -> ${ escapeQuotes ( fk . referenced_column_name ) } "` ;
63+ const label = `"${ sanitizeQuotedText ( fk . column_name ) } -> ${ sanitizeQuotedText ( fk . referenced_column_name ) } "` ;
6264 lines . push ( ` ${ sourceAlias } }o--|| ${ targetAlias } : ${ label } ` ) ;
6365 relationshipCount ++ ;
6466 }
@@ -110,12 +112,24 @@ function buildColumnComment(column: TableStructureDS): string {
110112 parts . push ( `max length: ${ column . character_maximum_length } ` ) ;
111113 }
112114 const text = parts . join ( '; ' ) ;
113- return text ? `"${ escapeQuotes ( text ) } "` : '' ;
115+ return text ? `"${ sanitizeQuotedText ( text ) } "` : '' ;
114116}
115117
118+ const MERMAID_ENTITY_RESERVED_WORDS = new Set < string > ( [
119+ 'erDiagram' ,
120+ 'style' ,
121+ 'class' ,
122+ 'classDef' ,
123+ 'one' ,
124+ 'many' ,
125+ 'to' ,
126+ 'zero' ,
127+ ] ) ;
128+
129+ const MERMAID_ATTRIBUTE_KEY_WORDS = new Set < string > ( [ 'PK' , 'FK' , 'UK' ] ) ;
130+
116131function makeUniqueAlias ( name : string , used : Set < string > ) : string {
117- let base = sanitizeIdentifier ( name ) ;
118- if ( base . length === 0 || / ^ [ 0 - 9 ] / . test ( base ) ) base = `t_${ base } ` ;
132+ const base = toEntityAlias ( name ) ;
119133 let candidate = base ;
120134 let suffix = 1 ;
121135 while ( used . has ( candidate ) ) {
@@ -125,10 +139,26 @@ function makeUniqueAlias(name: string, used: Set<string>): string {
125139 return candidate ;
126140}
127141
142+ function toEntityAlias ( value : string ) : string {
143+ const sanitized = sanitizeIdentifier ( value ) ;
144+ if ( sanitized . length === 0 || / ^ [ 0 - 9 ] / . test ( sanitized ) || MERMAID_ENTITY_RESERVED_WORDS . has ( sanitized ) ) {
145+ return `t_${ sanitized } ` ;
146+ }
147+ return sanitized ;
148+ }
149+
150+ function toAttributeWord ( value : string ) : string {
151+ const sanitized = sanitizeIdentifier ( value ) ;
152+ if ( sanitized . length === 0 || / ^ [ 0 - 9 ] / . test ( sanitized ) || MERMAID_ATTRIBUTE_KEY_WORDS . has ( sanitized ) ) {
153+ return `_${ sanitized } ` ;
154+ }
155+ return sanitized ;
156+ }
157+
128158function sanitizeIdentifier ( value : string ) : string {
129159 return value . replace ( / [ ^ A - Z a - z 0 - 9 _ ] / g, '_' ) ;
130160}
131161
132- function escapeQuotes ( value : string ) : string {
133- return value . replace ( / " / g, "'" ) ;
162+ function sanitizeQuotedText ( value : string ) : string {
163+ return value . replace ( / " / g, "'" ) . replace ( / [ \r \n \t ] + / g , ' ' ) ;
134164}
0 commit comments