1/*@internal*/ 2namespace ts { 3 export function transformJsx(context: TransformationContext) { 4 interface PerFileState { 5 importSpecifier?: string; 6 filenameDeclaration?: VariableDeclaration & { name: Identifier; }; 7 utilizedImplicitRuntimeImports?: Map<Map<ImportSpecifier>>; 8 } 9 10 const { 11 factory, 12 getEmitHelperFactory: emitHelpers, 13 } = context; 14 const compilerOptions = context.getCompilerOptions(); 15 let currentSourceFile: SourceFile; 16 let currentFileState!: PerFileState; 17 18 return chainBundle(context, transformSourceFile); 19 20 function getCurrentFileNameExpression(): Identifier { 21 if (currentFileState.filenameDeclaration) { 22 return currentFileState.filenameDeclaration.name; 23 } 24 const declaration = factory.createVariableDeclaration(factory.createUniqueName("_jsxFileName", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel), /*exclaimationToken*/ undefined, /*type*/ undefined, factory.createStringLiteral(currentSourceFile.fileName)); 25 currentFileState.filenameDeclaration = declaration as VariableDeclaration & { name: Identifier }; 26 return currentFileState.filenameDeclaration.name; 27 } 28 29 function getJsxFactoryCalleePrimitive(childrenLength: number): "jsx" | "jsxs" | "jsxDEV" { 30 return compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsxDEV" : childrenLength > 1 ? "jsxs" : "jsx"; 31 } 32 33 function getJsxFactoryCallee(childrenLength: number) { 34 const type = getJsxFactoryCalleePrimitive(childrenLength); 35 return getImplicitImportForName(type); 36 } 37 38 function getImplicitJsxFragmentReference() { 39 return getImplicitImportForName("Fragment"); 40 } 41 42 function getImplicitImportForName(name: string) { 43 const importSource = name === "createElement" 44 ? currentFileState.importSpecifier! 45 : getJSXRuntimeImport(currentFileState.importSpecifier, compilerOptions)!; 46 const existing = currentFileState.utilizedImplicitRuntimeImports?.get(importSource)?.get(name); 47 if (existing) { 48 return existing.name; 49 } 50 if (!currentFileState.utilizedImplicitRuntimeImports) { 51 currentFileState.utilizedImplicitRuntimeImports = createMap(); 52 } 53 let specifierSourceImports = currentFileState.utilizedImplicitRuntimeImports.get(importSource); 54 if (!specifierSourceImports) { 55 specifierSourceImports = createMap(); 56 currentFileState.utilizedImplicitRuntimeImports.set(importSource, specifierSourceImports); 57 } 58 const generatedName = factory.createUniqueName(`_${name}`, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel | GeneratedIdentifierFlags.AllowNameSubstitution); 59 const specifier = factory.createImportSpecifier(factory.createIdentifier(name), generatedName); 60 generatedName.generatedImportReference = specifier; 61 specifierSourceImports.set(name, specifier); 62 return generatedName; 63 } 64 65 /** 66 * Transform JSX-specific syntax in a SourceFile. 67 * 68 * @param node A SourceFile node. 69 */ 70 function transformSourceFile(node: SourceFile) { 71 if (node.isDeclarationFile) { 72 return node; 73 } 74 75 currentSourceFile = node; 76 currentFileState = {}; 77 currentFileState.importSpecifier = getJSXImplicitImportBase(compilerOptions, node); 78 let visited = visitEachChild(node, visitor, context); 79 addEmitHelpers(visited, context.readEmitHelpers()); 80 let statements: readonly Statement[] = visited.statements; 81 if (currentFileState.filenameDeclaration) { 82 statements = insertStatementAfterCustomPrologue(statements.slice(), factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([currentFileState.filenameDeclaration], NodeFlags.Const))); 83 } 84 if (currentFileState.utilizedImplicitRuntimeImports) { 85 for (const [importSource, importSpecifiersMap] of arrayFrom(currentFileState.utilizedImplicitRuntimeImports.entries())) { 86 if (isExternalModule(node)) { 87 // Add `import` statement 88 const importStatement = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*typeOnly*/ false, /*name*/ undefined, factory.createNamedImports(arrayFrom(importSpecifiersMap.values()))), factory.createStringLiteral(importSource)); 89 setParentRecursive(importStatement, /*incremental*/ false); 90 statements = insertStatementAfterCustomPrologue(statements.slice(), importStatement); 91 } 92 else if (isExternalOrCommonJsModule(node)) { 93 // Add `require` statement 94 const requireStatement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ 95 factory.createVariableDeclaration( 96 factory.createObjectBindingPattern(map(arrayFrom(importSpecifiersMap.values()), s => factory.createBindingElement(/*dotdotdot*/ undefined, s.propertyName, s.name))), 97 /*exclaimationToken*/ undefined, 98 /*type*/ undefined, 99 factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [factory.createStringLiteral(importSource)]) 100 ) 101 ], NodeFlags.Const)); 102 setParentRecursive(requireStatement, /*incremental*/ false); 103 statements = insertStatementAfterCustomPrologue(statements.slice(), requireStatement); 104 } 105 else { 106 // Do nothing (script file) - consider an error in the checker? 107 } 108 } 109 } 110 if (statements !== visited.statements) { 111 visited = factory.updateSourceFile(visited, statements); 112 } 113 currentFileState = undefined!; 114 return visited; 115 } 116 117 function visitor(node: Node): VisitResult<Node> { 118 if (node.transformFlags & TransformFlags.ContainsJsx) { 119 return visitorWorker(node); 120 } 121 else { 122 return node; 123 } 124 } 125 126 function visitorWorker(node: Node): VisitResult<Node> { 127 switch (node.kind) { 128 case SyntaxKind.JsxElement: 129 return visitJsxElement(<JsxElement>node, /*isChild*/ false); 130 131 case SyntaxKind.JsxSelfClosingElement: 132 return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ false); 133 134 case SyntaxKind.JsxFragment: 135 return visitJsxFragment(<JsxFragment>node, /*isChild*/ false); 136 137 case SyntaxKind.JsxExpression: 138 return visitJsxExpression(<JsxExpression>node); 139 140 default: 141 return visitEachChild(node, visitor, context); 142 } 143 } 144 145 function transformJsxChildToExpression(node: JsxChild): Expression | undefined { 146 switch (node.kind) { 147 case SyntaxKind.JsxText: 148 return visitJsxText(node); 149 150 case SyntaxKind.JsxExpression: 151 return visitJsxExpression(node); 152 153 case SyntaxKind.JsxElement: 154 return visitJsxElement(node, /*isChild*/ true); 155 156 case SyntaxKind.JsxSelfClosingElement: 157 return visitJsxSelfClosingElement(node, /*isChild*/ true); 158 159 case SyntaxKind.JsxFragment: 160 return visitJsxFragment(node, /*isChild*/ true); 161 162 default: 163 return Debug.failBadSyntaxKind(node); 164 } 165 } 166 167 /** 168 * The react jsx/jsxs transform falls back to `createElement` when an explicit `key` argument comes after a spread 169 */ 170 function hasKeyAfterPropsSpread(node: JsxOpeningLikeElement) { 171 let spread = false; 172 for (const elem of node.attributes.properties) { 173 if (isJsxSpreadAttribute(elem)) { 174 spread = true; 175 } 176 else if (spread && isJsxAttribute(elem) && elem.name.escapedText === "key") { 177 return true; 178 } 179 } 180 return false; 181 } 182 183 function shouldUseCreateElement(node: JsxOpeningLikeElement) { 184 return currentFileState.importSpecifier === undefined || hasKeyAfterPropsSpread(node); 185 } 186 187 function visitJsxElement(node: JsxElement, isChild: boolean) { 188 const tagTransform = shouldUseCreateElement(node.openingElement) ? visitJsxOpeningLikeElementCreateElement : visitJsxOpeningLikeElementJSX; 189 return tagTransform(node.openingElement, node.children, isChild, /*location*/ node); 190 } 191 192 function visitJsxSelfClosingElement(node: JsxSelfClosingElement, isChild: boolean) { 193 const tagTransform = shouldUseCreateElement(node) ? visitJsxOpeningLikeElementCreateElement : visitJsxOpeningLikeElementJSX; 194 return tagTransform(node, /*children*/ undefined, isChild, /*location*/ node); 195 } 196 197 function visitJsxFragment(node: JsxFragment, isChild: boolean) { 198 const tagTransform = currentFileState.importSpecifier === undefined ? visitJsxOpeningFragmentCreateElement : visitJsxOpeningFragmentJSX; 199 return tagTransform(node.openingFragment, node.children, isChild, /*location*/ node); 200 } 201 202 function convertJsxChildrenToChildrenPropObject(children: readonly JsxChild[]) { 203 const nonWhitespaceChildren = getSemanticJsxChildren(children); 204 if (length(nonWhitespaceChildren) === 1) { 205 const result = transformJsxChildToExpression(nonWhitespaceChildren[0]); 206 return result && factory.createObjectLiteralExpression([ 207 factory.createPropertyAssignment("children", result) 208 ]); 209 } 210 const result = mapDefined(children, transformJsxChildToExpression); 211 return !result.length ? undefined : factory.createObjectLiteralExpression([ 212 factory.createPropertyAssignment("children", factory.createArrayLiteralExpression(result)) 213 ]); 214 } 215 216 function visitJsxOpeningLikeElementJSX(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) { 217 const tagName = getTagName(node); 218 let objectProperties: Expression; 219 const keyAttr = find(node.attributes.properties, p => !!p.name && isIdentifier(p.name) && p.name.escapedText === "key") as JsxAttribute | undefined; 220 const attrs = keyAttr ? filter(node.attributes.properties, p => p !== keyAttr) : node.attributes.properties; 221 222 let segments: Expression[] = []; 223 if (attrs.length) { 224 // Map spans of JsxAttribute nodes into object literals and spans 225 // of JsxSpreadAttribute nodes into expressions. 226 segments = flatten( 227 spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread 228 ? map(attrs, transformJsxSpreadAttributeToExpression) 229 : factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement)) 230 ) 231 ); 232 233 if (isJsxSpreadAttribute(attrs[0])) { 234 // We must always emit at least one object literal before a spread 235 // argument.factory.createObjectLiteral 236 segments.unshift(factory.createObjectLiteralExpression()); 237 } 238 } 239 if (children && children.length) { 240 const result = convertJsxChildrenToChildrenPropObject(children); 241 if (result) { 242 segments.push(result); 243 } 244 } 245 246 if (segments.length === 0) { 247 objectProperties = factory.createObjectLiteralExpression([]); 248 // When there are no attributes, React wants {} 249 } 250 else { 251 // Either emit one big object literal (no spread attribs), or 252 // a call to the __assign helper. 253 objectProperties = singleOrUndefined(segments) || emitHelpers().createAssignHelper(segments); 254 } 255 256 return visitJsxOpeningLikeElementOrFragmentJSX(tagName, objectProperties, keyAttr, length(getSemanticJsxChildren(children || emptyArray)), isChild, location); 257 } 258 259 function visitJsxOpeningLikeElementOrFragmentJSX(tagName: Expression, objectProperties: Expression, keyAttr: JsxAttribute | undefined, childrenLength: number, isChild: boolean, location: TextRange) { 260 const args: Expression[] = [tagName, objectProperties, !keyAttr ? factory.createVoidZero() : transformJsxAttributeInitializer(keyAttr.initializer)]; 261 if (compilerOptions.jsx === JsxEmit.ReactJSXDev) { 262 const originalFile = getOriginalNode(currentSourceFile); 263 if (originalFile && isSourceFile(originalFile)) { 264 // isStaticChildren development flag 265 args.push(childrenLength > 1 ? factory.createTrue() : factory.createFalse()); 266 // __source development flag 267 const lineCol = getLineAndCharacterOfPosition(originalFile, location.pos); 268 args.push(factory.createObjectLiteralExpression([ 269 factory.createPropertyAssignment("fileName", getCurrentFileNameExpression()), 270 factory.createPropertyAssignment("lineNumber", factory.createNumericLiteral(lineCol.line + 1)), 271 factory.createPropertyAssignment("columnNumber", factory.createNumericLiteral(lineCol.character + 1)) 272 ])); 273 // __self development flag 274 args.push(factory.createThis()); 275 } 276 } 277 const element = setTextRange(factory.createCallExpression(getJsxFactoryCallee(childrenLength), /*typeArguments*/ undefined, args), location); 278 279 if (isChild) { 280 startOnNewLine(element); 281 } 282 283 return element; 284 } 285 286 function visitJsxOpeningLikeElementCreateElement(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) { 287 const tagName = getTagName(node); 288 let objectProperties: Expression | undefined; 289 const attrs = node.attributes.properties; 290 if (attrs.length === 0) { 291 objectProperties = factory.createNull(); 292 // When there are no attributes, React wants "null" 293 } 294 else { 295 // Map spans of JsxAttribute nodes into object literals and spans 296 // of JsxSpreadAttribute nodes into expressions. 297 const segments = flatten<Expression | ObjectLiteralExpression>( 298 spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread 299 ? map(attrs, transformJsxSpreadAttributeToExpression) 300 : factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement)) 301 ) 302 ); 303 304 if (isJsxSpreadAttribute(attrs[0])) { 305 // We must always emit at least one object literal before a spread 306 // argument.factory.createObjectLiteral 307 segments.unshift(factory.createObjectLiteralExpression()); 308 } 309 310 // Either emit one big object literal (no spread attribs), or 311 // a call to the __assign helper. 312 objectProperties = singleOrUndefined(segments); 313 if (!objectProperties) { 314 objectProperties = emitHelpers().createAssignHelper(segments); 315 } 316 } 317 318 const callee = currentFileState.importSpecifier === undefined 319 ? createJsxFactoryExpression( 320 factory, 321 context.getEmitResolver().getJsxFactoryEntity(currentSourceFile), 322 compilerOptions.reactNamespace!, // TODO: GH#18217 323 node 324 ) 325 : getImplicitImportForName("createElement"); 326 327 const element = createExpressionForJsxElement( 328 factory, 329 callee, 330 tagName, 331 objectProperties, 332 mapDefined(children, transformJsxChildToExpression), 333 location 334 ); 335 336 if (isChild) { 337 startOnNewLine(element); 338 } 339 340 return element; 341 } 342 343 function visitJsxOpeningFragmentJSX(_node: JsxOpeningFragment, children: readonly JsxChild[], isChild: boolean, location: TextRange) { 344 let childrenProps: Expression | undefined; 345 if (children && children.length) { 346 const result = convertJsxChildrenToChildrenPropObject(children); 347 if (result) { 348 childrenProps = result; 349 } 350 } 351 return visitJsxOpeningLikeElementOrFragmentJSX( 352 getImplicitJsxFragmentReference(), 353 childrenProps || factory.createObjectLiteralExpression([]), 354 /*keyAttr*/ undefined, 355 length(getSemanticJsxChildren(children)), 356 isChild, 357 location 358 ); 359 } 360 361 function visitJsxOpeningFragmentCreateElement(node: JsxOpeningFragment, children: readonly JsxChild[], isChild: boolean, location: TextRange) { 362 const element = createExpressionForJsxFragment( 363 factory, 364 context.getEmitResolver().getJsxFactoryEntity(currentSourceFile), 365 context.getEmitResolver().getJsxFragmentFactoryEntity(currentSourceFile), 366 compilerOptions.reactNamespace!, // TODO: GH#18217 367 mapDefined(children, transformJsxChildToExpression), 368 node, 369 location 370 ); 371 372 if (isChild) { 373 startOnNewLine(element); 374 } 375 376 return element; 377 } 378 379 function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) { 380 return visitNode(node.expression, visitor, isExpression); 381 } 382 383 function transformJsxAttributeToObjectLiteralElement(node: JsxAttribute) { 384 const name = getAttributeName(node); 385 const expression = transformJsxAttributeInitializer(node.initializer); 386 return factory.createPropertyAssignment(name, expression); 387 } 388 389 function transformJsxAttributeInitializer(node: StringLiteral | JsxExpression | undefined): Expression { 390 if (node === undefined) { 391 return factory.createTrue(); 392 } 393 else if (node.kind === SyntaxKind.StringLiteral) { 394 // Always recreate the literal to escape any escape sequences or newlines which may be in the original jsx string and which 395 // Need to be escaped to be handled correctly in a normal string 396 const singleQuote = node.singleQuote !== undefined ? node.singleQuote : !isStringDoubleQuoted(node, currentSourceFile); 397 const literal = factory.createStringLiteral(tryDecodeEntities(node.text) || node.text, singleQuote); 398 return setTextRange(literal, node); 399 } 400 else if (node.kind === SyntaxKind.JsxExpression) { 401 if (node.expression === undefined) { 402 return factory.createTrue(); 403 } 404 return visitNode(node.expression, visitor, isExpression); 405 } 406 else { 407 return Debug.failBadSyntaxKind(node); 408 } 409 } 410 411 function visitJsxText(node: JsxText): StringLiteral | undefined { 412 const fixed = fixupWhitespaceAndDecodeEntities(node.text); 413 return fixed === undefined ? undefined : factory.createStringLiteral(fixed); 414 } 415 416 /** 417 * JSX trims whitespace at the end and beginning of lines, except that the 418 * start/end of a tag is considered a start/end of a line only if that line is 419 * on the same line as the closing tag. See examples in 420 * tests/cases/conformance/jsx/tsxReactEmitWhitespace.tsx 421 * See also https://www.w3.org/TR/html4/struct/text.html#h-9.1 and https://www.w3.org/TR/CSS2/text.html#white-space-model 422 * 423 * An equivalent algorithm would be: 424 * - If there is only one line, return it. 425 * - If there is only whitespace (but multiple lines), return `undefined`. 426 * - Split the text into lines. 427 * - 'trimRight' the first line, 'trimLeft' the last line, 'trim' middle lines. 428 * - Decode entities on each line (individually). 429 * - Remove empty lines and join the rest with " ". 430 */ 431 function fixupWhitespaceAndDecodeEntities(text: string): string | undefined { 432 let acc: string | undefined; 433 // First non-whitespace character on this line. 434 let firstNonWhitespace = 0; 435 // Last non-whitespace character on this line. 436 let lastNonWhitespace = -1; 437 // These initial values are special because the first line is: 438 // firstNonWhitespace = 0 to indicate that we want leading whitsepace, 439 // but lastNonWhitespace = -1 as a special flag to indicate that we *don't* include the line if it's all whitespace. 440 441 for (let i = 0; i < text.length; i++) { 442 const c = text.charCodeAt(i); 443 if (isLineBreak(c)) { 444 // If we've seen any non-whitespace characters on this line, add the 'trim' of the line. 445 // (lastNonWhitespace === -1 is a special flag to detect whether the first line is all whitespace.) 446 if (firstNonWhitespace !== -1 && lastNonWhitespace !== -1) { 447 acc = addLineOfJsxText(acc, text.substr(firstNonWhitespace, lastNonWhitespace - firstNonWhitespace + 1)); 448 } 449 450 // Reset firstNonWhitespace for the next line. 451 // Don't bother to reset lastNonWhitespace because we ignore it if firstNonWhitespace = -1. 452 firstNonWhitespace = -1; 453 } 454 else if (!isWhiteSpaceSingleLine(c)) { 455 lastNonWhitespace = i; 456 if (firstNonWhitespace === -1) { 457 firstNonWhitespace = i; 458 } 459 } 460 } 461 462 return firstNonWhitespace !== -1 463 // Last line had a non-whitespace character. Emit the 'trimLeft', meaning keep trailing whitespace. 464 ? addLineOfJsxText(acc, text.substr(firstNonWhitespace)) 465 // Last line was all whitespace, so ignore it 466 : acc; 467 } 468 469 function addLineOfJsxText(acc: string | undefined, trimmedLine: string): string { 470 // We do not escape the string here as that is handled by the printer 471 // when it emits the literal. We do, however, need to decode JSX entities. 472 const decoded = decodeEntities(trimmedLine); 473 return acc === undefined ? decoded : acc + " " + decoded; 474 } 475 476 /** 477 * Replace entities like " ", "{", and "�" with the characters they encode. 478 * See https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references 479 */ 480 function decodeEntities(text: string): string { 481 return text.replace(/&((#((\d+)|x([\da-fA-F]+)))|(\w+));/g, (match, _all, _number, _digits, decimal, hex, word) => { 482 if (decimal) { 483 return utf16EncodeAsString(parseInt(decimal, 10)); 484 } 485 else if (hex) { 486 return utf16EncodeAsString(parseInt(hex, 16)); 487 } 488 else { 489 const ch = entities.get(word); 490 // If this is not a valid entity, then just use `match` (replace it with itself, i.e. don't replace) 491 return ch ? utf16EncodeAsString(ch) : match; 492 } 493 }); 494 } 495 496 /** Like `decodeEntities` but returns `undefined` if there were no entities to decode. */ 497 function tryDecodeEntities(text: string): string | undefined { 498 const decoded = decodeEntities(text); 499 return decoded === text ? undefined : decoded; 500 } 501 502 function getTagName(node: JsxElement | JsxOpeningLikeElement): Expression { 503 if (node.kind === SyntaxKind.JsxElement) { 504 return getTagName(node.openingElement); 505 } 506 else { 507 const name = node.tagName; 508 if (isIdentifier(name) && isIntrinsicJsxName(name.escapedText)) { 509 return factory.createStringLiteral(idText(name)); 510 } 511 else { 512 return createExpressionFromEntityName(factory, name); 513 } 514 } 515 } 516 517 /** 518 * Emit an attribute name, which is quoted if it needs to be quoted. Because 519 * these emit into an object literal property name, we don't need to be worried 520 * about keywords, just non-identifier characters 521 */ 522 function getAttributeName(node: JsxAttribute): StringLiteral | Identifier { 523 const name = node.name; 524 const text = idText(name); 525 if (/^[A-Za-z_]\w*$/.test(text)) { 526 return name; 527 } 528 else { 529 return factory.createStringLiteral(text); 530 } 531 } 532 533 function visitJsxExpression(node: JsxExpression) { 534 return visitNode(node.expression, visitor, isExpression); 535 } 536 } 537 538 const entities = new Map(getEntries({ 539 quot: 0x0022, 540 amp: 0x0026, 541 apos: 0x0027, 542 lt: 0x003C, 543 gt: 0x003E, 544 nbsp: 0x00A0, 545 iexcl: 0x00A1, 546 cent: 0x00A2, 547 pound: 0x00A3, 548 curren: 0x00A4, 549 yen: 0x00A5, 550 brvbar: 0x00A6, 551 sect: 0x00A7, 552 uml: 0x00A8, 553 copy: 0x00A9, 554 ordf: 0x00AA, 555 laquo: 0x00AB, 556 not: 0x00AC, 557 shy: 0x00AD, 558 reg: 0x00AE, 559 macr: 0x00AF, 560 deg: 0x00B0, 561 plusmn: 0x00B1, 562 sup2: 0x00B2, 563 sup3: 0x00B3, 564 acute: 0x00B4, 565 micro: 0x00B5, 566 para: 0x00B6, 567 middot: 0x00B7, 568 cedil: 0x00B8, 569 sup1: 0x00B9, 570 ordm: 0x00BA, 571 raquo: 0x00BB, 572 frac14: 0x00BC, 573 frac12: 0x00BD, 574 frac34: 0x00BE, 575 iquest: 0x00BF, 576 Agrave: 0x00C0, 577 Aacute: 0x00C1, 578 Acirc: 0x00C2, 579 Atilde: 0x00C3, 580 Auml: 0x00C4, 581 Aring: 0x00C5, 582 AElig: 0x00C6, 583 Ccedil: 0x00C7, 584 Egrave: 0x00C8, 585 Eacute: 0x00C9, 586 Ecirc: 0x00CA, 587 Euml: 0x00CB, 588 Igrave: 0x00CC, 589 Iacute: 0x00CD, 590 Icirc: 0x00CE, 591 Iuml: 0x00CF, 592 ETH: 0x00D0, 593 Ntilde: 0x00D1, 594 Ograve: 0x00D2, 595 Oacute: 0x00D3, 596 Ocirc: 0x00D4, 597 Otilde: 0x00D5, 598 Ouml: 0x00D6, 599 times: 0x00D7, 600 Oslash: 0x00D8, 601 Ugrave: 0x00D9, 602 Uacute: 0x00DA, 603 Ucirc: 0x00DB, 604 Uuml: 0x00DC, 605 Yacute: 0x00DD, 606 THORN: 0x00DE, 607 szlig: 0x00DF, 608 agrave: 0x00E0, 609 aacute: 0x00E1, 610 acirc: 0x00E2, 611 atilde: 0x00E3, 612 auml: 0x00E4, 613 aring: 0x00E5, 614 aelig: 0x00E6, 615 ccedil: 0x00E7, 616 egrave: 0x00E8, 617 eacute: 0x00E9, 618 ecirc: 0x00EA, 619 euml: 0x00EB, 620 igrave: 0x00EC, 621 iacute: 0x00ED, 622 icirc: 0x00EE, 623 iuml: 0x00EF, 624 eth: 0x00F0, 625 ntilde: 0x00F1, 626 ograve: 0x00F2, 627 oacute: 0x00F3, 628 ocirc: 0x00F4, 629 otilde: 0x00F5, 630 ouml: 0x00F6, 631 divide: 0x00F7, 632 oslash: 0x00F8, 633 ugrave: 0x00F9, 634 uacute: 0x00FA, 635 ucirc: 0x00FB, 636 uuml: 0x00FC, 637 yacute: 0x00FD, 638 thorn: 0x00FE, 639 yuml: 0x00FF, 640 OElig: 0x0152, 641 oelig: 0x0153, 642 Scaron: 0x0160, 643 scaron: 0x0161, 644 Yuml: 0x0178, 645 fnof: 0x0192, 646 circ: 0x02C6, 647 tilde: 0x02DC, 648 Alpha: 0x0391, 649 Beta: 0x0392, 650 Gamma: 0x0393, 651 Delta: 0x0394, 652 Epsilon: 0x0395, 653 Zeta: 0x0396, 654 Eta: 0x0397, 655 Theta: 0x0398, 656 Iota: 0x0399, 657 Kappa: 0x039A, 658 Lambda: 0x039B, 659 Mu: 0x039C, 660 Nu: 0x039D, 661 Xi: 0x039E, 662 Omicron: 0x039F, 663 Pi: 0x03A0, 664 Rho: 0x03A1, 665 Sigma: 0x03A3, 666 Tau: 0x03A4, 667 Upsilon: 0x03A5, 668 Phi: 0x03A6, 669 Chi: 0x03A7, 670 Psi: 0x03A8, 671 Omega: 0x03A9, 672 alpha: 0x03B1, 673 beta: 0x03B2, 674 gamma: 0x03B3, 675 delta: 0x03B4, 676 epsilon: 0x03B5, 677 zeta: 0x03B6, 678 eta: 0x03B7, 679 theta: 0x03B8, 680 iota: 0x03B9, 681 kappa: 0x03BA, 682 lambda: 0x03BB, 683 mu: 0x03BC, 684 nu: 0x03BD, 685 xi: 0x03BE, 686 omicron: 0x03BF, 687 pi: 0x03C0, 688 rho: 0x03C1, 689 sigmaf: 0x03C2, 690 sigma: 0x03C3, 691 tau: 0x03C4, 692 upsilon: 0x03C5, 693 phi: 0x03C6, 694 chi: 0x03C7, 695 psi: 0x03C8, 696 omega: 0x03C9, 697 thetasym: 0x03D1, 698 upsih: 0x03D2, 699 piv: 0x03D6, 700 ensp: 0x2002, 701 emsp: 0x2003, 702 thinsp: 0x2009, 703 zwnj: 0x200C, 704 zwj: 0x200D, 705 lrm: 0x200E, 706 rlm: 0x200F, 707 ndash: 0x2013, 708 mdash: 0x2014, 709 lsquo: 0x2018, 710 rsquo: 0x2019, 711 sbquo: 0x201A, 712 ldquo: 0x201C, 713 rdquo: 0x201D, 714 bdquo: 0x201E, 715 dagger: 0x2020, 716 Dagger: 0x2021, 717 bull: 0x2022, 718 hellip: 0x2026, 719 permil: 0x2030, 720 prime: 0x2032, 721 Prime: 0x2033, 722 lsaquo: 0x2039, 723 rsaquo: 0x203A, 724 oline: 0x203E, 725 frasl: 0x2044, 726 euro: 0x20AC, 727 image: 0x2111, 728 weierp: 0x2118, 729 real: 0x211C, 730 trade: 0x2122, 731 alefsym: 0x2135, 732 larr: 0x2190, 733 uarr: 0x2191, 734 rarr: 0x2192, 735 darr: 0x2193, 736 harr: 0x2194, 737 crarr: 0x21B5, 738 lArr: 0x21D0, 739 uArr: 0x21D1, 740 rArr: 0x21D2, 741 dArr: 0x21D3, 742 hArr: 0x21D4, 743 forall: 0x2200, 744 part: 0x2202, 745 exist: 0x2203, 746 empty: 0x2205, 747 nabla: 0x2207, 748 isin: 0x2208, 749 notin: 0x2209, 750 ni: 0x220B, 751 prod: 0x220F, 752 sum: 0x2211, 753 minus: 0x2212, 754 lowast: 0x2217, 755 radic: 0x221A, 756 prop: 0x221D, 757 infin: 0x221E, 758 ang: 0x2220, 759 and: 0x2227, 760 or: 0x2228, 761 cap: 0x2229, 762 cup: 0x222A, 763 int: 0x222B, 764 there4: 0x2234, 765 sim: 0x223C, 766 cong: 0x2245, 767 asymp: 0x2248, 768 ne: 0x2260, 769 equiv: 0x2261, 770 le: 0x2264, 771 ge: 0x2265, 772 sub: 0x2282, 773 sup: 0x2283, 774 nsub: 0x2284, 775 sube: 0x2286, 776 supe: 0x2287, 777 oplus: 0x2295, 778 otimes: 0x2297, 779 perp: 0x22A5, 780 sdot: 0x22C5, 781 lceil: 0x2308, 782 rceil: 0x2309, 783 lfloor: 0x230A, 784 rfloor: 0x230B, 785 lang: 0x2329, 786 rang: 0x232A, 787 loz: 0x25CA, 788 spades: 0x2660, 789 clubs: 0x2663, 790 hearts: 0x2665, 791 diams: 0x2666 792 })); 793} 794