diff --git a/package-lock.json b/package-lock.json index 813a7b4..b8e88b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "@tginternal/editor", "version": "1.18.0", "dependencies": { + "bidi-js": "^1.0.3", "entities": "^6.0.1" }, "devDependencies": { @@ -4213,6 +4214,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", @@ -10696,6 +10706,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", diff --git a/package.json b/package.json index dd8cd4c..a4d2b2f 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ }, "//dependencies": "entities pinned to 6.x — latest dual CJS/ESM release; 7+ are ESM-only and break the CommonJS jest tests and the .cjs build", "dependencies": { + "bidi-js": "^1.0.3", "entities": "^6.0.1" } } diff --git a/src/bidi-js.d.ts b/src/bidi-js.d.ts new file mode 100644 index 0000000..ccbf092 --- /dev/null +++ b/src/bidi-js.d.ts @@ -0,0 +1,32 @@ +declare module "bidi-js" { + export type BidiCharTypeName = + | "L" + | "R" + | "AL" + | "EN" + | "ES" + | "ET" + | "AN" + | "CS" + | "NSM" + | "BN" + | "B" + | "S" + | "WS" + | "ON" + | "LRE" + | "LRO" + | "RLE" + | "RLO" + | "PDF" + | "LRI" + | "RLI" + | "FSI" + | "PDI"; + + export interface Bidi { + getBidiCharTypeName(char: string): BidiCharTypeName; + } + + export default function bidiFactory(): Bidi; +} diff --git a/src/parser/htmlIsolatesPlugin.ts b/src/parser/htmlIsolatesPlugin.ts index 4550e63..0cb656c 100644 --- a/src/parser/htmlIsolatesPlugin.ts +++ b/src/parser/htmlIsolatesPlugin.ts @@ -8,8 +8,9 @@ import { } from "@codemirror/view"; import { Prec } from "@codemirror/state"; import { syntaxTree } from "@codemirror/language"; -import { Tree } from "@lezer/common"; +import { SyntaxNodeRef, Tree } from "@lezer/common"; import { RangeSetBuilder } from "@codemirror/state"; +import bidiFactory from "bidi-js"; export const htmlIsolatesPlugin = ViewPlugin.fromClass( class { @@ -62,14 +63,19 @@ function computeIsolates(view: EditorView) { from, to, enter(node) { - if ( - node.name === "HtmlTagOpen" || - node.name === "HtmlTagClose" || - node.name === "Expression" - ) { + if (node.name === "HtmlTagOpen" || node.name === "HtmlTagClose") { set.add(node.from, node.to, isolateLTR); - } - if (node.name === "TextRoot" || node.name === "Text") { + } else if (node.name === "Expression") { + // A valid placeholder is markup and stays LTR. An invalid one is + // arbitrary user text, so it follows its own content's direction + // instead of being forced LTR (which would scramble RTL content). + const followsRtlContent = + isInvalidExpression(node) && + contentDirection( + view.state.doc.sliceString(node.from, node.to) + ) === Direction.RTL; + set.add(node.from, node.to, followsRtlContent ? isolateRTL : isolateLTR); + } else if (node.name === "TextRoot" || node.name === "Text") { set.add(node.from, node.to, isolateRTL); } }, @@ -77,3 +83,23 @@ function computeIsolates(view: EditorView) { } return set.finish(); } + +function isInvalidExpression(node: SyntaxNodeRef) { + return node.node.firstChild?.nextSibling?.name === "InvalidExpressionBody"; +} + +const bidi = bidiFactory(); + +// First-strong direction (UAX#9 rules P2-P3), character classes from bidi-js. +function contentDirection(text: string): Direction { + for (const char of text) { + const type = bidi.getBidiCharTypeName(char); + if (type === "R" || type === "AL") { + return Direction.RTL; + } + if (type === "L") { + return Direction.LTR; + } + } + return Direction.LTR; +} diff --git a/src/parser/lezer/tokenizer/tokenizer.ts b/src/parser/lezer/tokenizer/tokenizer.ts index aa43679..020c94b 100644 --- a/src/parser/lezer/tokenizer/tokenizer.ts +++ b/src/parser/lezer/tokenizer/tokenizer.ts @@ -1,6 +1,11 @@ -import { Text, TextNested } from "../tolgeeParser.terms"; +import { + InvalidExpressionContent, + Text, + TextNested, +} from "../tolgeeParser.terms"; import { ExternalTokenizer, InputStream } from "@lezer/lr"; import { matchText } from "./matchText"; +import { fromCodePoint } from "@codemirror/state"; function getEscapable(isNested: boolean) { const result = new Set(["{", "}"]); @@ -48,3 +53,26 @@ export const textNested = new ExternalTokenizer((input) => { textToken: TextNested, }); }); + +// Everything inside a `{...}` up to the closing brace, used when the contents +// aren't a valid ICU expression (e.g. `placeholder:space`). Kept as one token so +// the whole `{...}` stays a single Expression node — else RTL bidi isolation +// splits it. `extend: true` lets it coexist with the real `Param`/`space` tokens +// so both parses are explored; InvalidExpressionBody's negative dynamicPrecedence +// then makes a valid format/select parse win whenever one exists. +export const invalidExpression = new ExternalTokenizer( + (input) => { + const startPosition = input.pos; + while (input.next !== -1) { + const char = fromCodePoint(input.next); + if (char === "{" || char === "}") { + break; + } + input.advance(); + } + if (startPosition < input.pos) { + input.acceptToken(InvalidExpressionContent); + } + }, + { extend: true } +); diff --git a/src/parser/lezer/tolgee.grammar b/src/parser/lezer/tolgee.grammar index 512b60e..f9df0e4 100644 --- a/src/parser/lezer/tolgee.grammar +++ b/src/parser/lezer/tolgee.grammar @@ -4,8 +4,12 @@ content { (Text | Expression | PluralPlaceholder ) content? } contentNested { (TextNested | Expression | PluralPlaceholder ) contentNested? } -Expression { ExpressionOpen (FormatExpression | SelectExpression) ExpressionClose } +Expression { ExpressionOpen (FormatExpression | SelectExpression | InvalidExpressionBody) ExpressionClose } +// dynamicPrecedence=-1 makes a real format/select parse win whenever one exists; +// this is only reached for `{...}` that can't parse otherwise. See the +// `invalidExpression` tokenizer for the full rationale. +InvalidExpressionBody[@dynamicPrecedence=-1] { InvalidExpressionContent } FormatExpression { space? Param space? (sep space? FormatFunction space? (sep (space? FormatStyle)+ space?)?)? } SelectExpression { space? Param space? sep space? SelectFunction space? sep (space? Offset)? (space? SelectVariant)+ space? } @@ -26,7 +30,11 @@ FormatStyle { format_style } Text } -@external tokens textNested from "./tokenizer/tokenizer" { +@external tokens invalidExpression from "./tokenizer/tokenizer" { + InvalidExpressionContent +} + +@external tokens textNested from "./tokenizer/tokenizer" { TextNested } diff --git a/src/parser/lezer/tolgeeParser.terms.ts b/src/parser/lezer/tolgeeParser.terms.ts index 1a28c27..c9780d4 100644 --- a/src/parser/lezer/tolgeeParser.terms.ts +++ b/src/parser/lezer/tolgeeParser.terms.ts @@ -1,20 +1,22 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. export const Text = 1, - TextNested = 2, - Root = 3, - Expression = 4, - ExpressionOpen = 5, - FormatExpression = 7, - Param = 8, - FormatFunction = 9, - FormatStyle = 10, - SelectExpression = 11, - SelectFunction = 12, - Offset = 13, - SelectVariant = 14, - VariantDescriptor = 15, - VariantContent = 16, - PluralPlaceholder = 17, - ExpressionClose = 19, - Nested = 20 + InvalidExpressionContent = 2, + TextNested = 3, + Root = 4, + Expression = 5, + ExpressionOpen = 6, + FormatExpression = 8, + Param = 9, + FormatFunction = 10, + FormatStyle = 11, + SelectExpression = 12, + SelectFunction = 13, + Offset = 14, + SelectVariant = 15, + VariantDescriptor = 16, + VariantContent = 17, + PluralPlaceholder = 18, + InvalidExpressionBody = 20, + ExpressionClose = 21, + Nested = 22 diff --git a/src/parser/lezer/tolgeeParser.test.ts b/src/parser/lezer/tolgeeParser.test.ts index 7ee2c40..8bf745e 100644 --- a/src/parser/lezer/tolgeeParser.test.ts +++ b/src/parser/lezer/tolgeeParser.test.ts @@ -30,6 +30,11 @@ function expectToThrow(text = getText()) { expect(() => tolgee(text)).toThrow(); } +function expectIcuOnlyThrows(text = getText()) { + expect(() => icu(text)).toThrow(); + expect(() => tolgee(text)).not.toThrow(); +} + describe("simple formatter", () => { test("simple test", () => { matchIcu(); @@ -84,11 +89,11 @@ describe("simple formatter", () => { }); test("this also { } wrong", () => { - expectToThrowWithIcu(); + expectIcuOnlyThrows(); }); test("this plain { , } wrong", () => { - expectToThrowWithIcu(); + expectIcuOnlyThrows(); }); test("this is { unexpected", () => { @@ -96,15 +101,15 @@ describe("simple formatter", () => { }); test("this is obviously bad { yo yo }", () => { - expectToThrowWithIcu(); + expectIcuOnlyThrows(); }); test("this is obviously bad { yo, }", () => { - expectToThrowWithIcu(); + expectIcuOnlyThrows(); }); test(`test {', number, ::percent} in param name`, () => { - expectToThrowWithIcu(); + expectIcuOnlyThrows(); }); // plurals @@ -149,4 +154,27 @@ describe("simple formatter", () => { "Auto translated {test, plural, =1 {# translation} other {# translations}}" ); }); + + test("Leniently accepts invalid param contents {placeholder:space}", () => { + expectIcuOnlyThrows("{placeholder:space}"); + }); + + test("Lenient invalid expression is a single Expression node", () => { + const tree = parser.parse( + "some rtl text {placeholder:space} more rtl text" + ); + const expressions: [number, number][] = []; + tree.iterate({ + enter(node) { + if (node.name === "Expression") { + expressions.push([node.from, node.to]); + } + }, + }); + expect(expressions).toEqual([[14, 33]]); + }); + + test("Lenient acceptance doesn't swallow following select variants", () => { + matchIcu("{name, plural, one {# one} other {# other}}"); + }); }); diff --git a/src/parser/lezer/tolgeeParser.ts b/src/parser/lezer/tolgeeParser.ts index 92ae77e..aab11fa 100644 --- a/src/parser/lezer/tolgeeParser.ts +++ b/src/parser/lezer/tolgeeParser.ts @@ -1,17 +1,18 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. import {LRParser} from "@lezer/lr" -import {text, textNested} from "./tokenizer/tokenizer" +import {text, invalidExpression, textNested} from "./tokenizer/tokenizer" export const parser = LRParser.deserialize({ version: 14, - states: "/SQQOTOOQ]OUOOOOOO'#Ca'#CaOOOR'#Cm'#CmOhOSO'#C`OpOTO'#CtQOOOOOO!OOUO'#C}OOOO'#Cd'#CdO!aOSO'#CcOkOSO'#CcO!lOSO,58zOOOO,59`,59`OOOO,59i,59iO!qOWO,58}O!|OSO,58}O#UOSO,58}OOOR'#Co'#CoOOOR1G.f1G.fOOOO'#Ce'#CeO#aOSO1G.iO!tOWO1G.iOOOO'#Ch'#ChO#lOSO1G.mO#tOWO1G.iO$POSO1G.iO$XO`O7+$TO$aOSO7+$TO$iOSO7+$TO$tOSO7+$XO$|OpO7+$XO$wOSO7+$XO!tOWO7+$TO%XOWO7+$TOOOO'#Cf'#CfOOOO'#Cq'#CqO$[O`O'#CqO%dO`O<`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]'_!cmSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'US+o_mSOX+j^p+jq|+j}#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+jS,qP;=`<%l+j],wP;=`<%l'U~-PYi~X^,zpq,z#y#z,z$f$g,z#BY#BZ,z$IS$I_,z$I|$JO,z$JT$JU,z$KV$KW,z&FU&FV,zT-v_mSrPOX+j^p+jq|+j}#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j[.z!cmSOX3V^p3Vq|+j}!Q+j!Q![3V![!c+j!c!}3V!}#R+j#R#S3V#S#T+j#T#o3V#o#q+j#r#s+j#s#y3V#z$f3V$g$n+j$n$o3V$o$p+j$p$q3V$q$s+j$s$t3V$t$u+j$u$v3V$v$x+j$x$|3V$|$}+j$}%R3V%R%S+j%S%V3V%V%W+j%W%o3V%o%p+j%p&a3V&a&b+j&b#BY3V#BZ$IS3V$I_$Id3V$Id$I|+j$JO$JT3V$JU$Je+j$Je$Jg3V$Jg$Jz+j$Jz$J{3V$J{$KV+j$KW%#t3V%#t%3d+j%3d%6z3V%6z%Dl+j%Dl%E[3V%E[&/x+j&/x&;O3V&;O&=t+j&=t&FU3V&FV&FY+j&FY&F^3V&F^&Fw+j&Fw&GW3V&GW&GX+j&GX;'S3V;'S;=`7i<%l?>`3V?>`?>b+j?>b?DR3V?DR?DT+j?DTO3V[3^!cmSpWOX3V^p3Vq|+j}!Q+j!Q![3V![!c+j!c!}3V!}#R+j#R#S3V#S#T+j#T#o3V#o#q+j#r#s+j#s#y3V#z$f3V$g$n+j$n$o3V$o$p+j$p$q3V$q$s+j$s$t3V$t$u+j$u$v3V$v$x+j$x$|3V$|$}+j$}%R3V%R%S+j%S%V3V%V%W+j%W%o3V%o%p+j%p&a3V&a&b+j&b#BY3V#BZ$IS3V$I_$Id3V$Id$I|+j$JO$JT3V$JU$Je+j$Je$Jg3V$Jg$Jz+j$Jz$J{3V$J{$KV+j$KW%#t3V%#t%3d+j%3d%6z3V%6z%Dl+j%Dl%E[3V%E[&/x+j&/x&;O3V&;O&=t+j&=t&FU3V&FV&FY+j&FY&F^3V&F^&Fw+j&Fw&GW3V&GW&GX+j&GX;'S3V;'S;=`7i<%l?>`3V?>`?>b+j?>b?DR3V?DR?DT+j?DTO3V[7lP;=`<%l3V~7tOk~[7y!emSOX3V^p3Vq{+j{|.u}!O.u!O!Q+j!Q![3V![!c+j!c!}3V!}#R+j#R#S3V#S#T+j#T#o3V#o#q+j#r#s+j#s#y3V#z$f3V$g$n+j$n$o3V$o$p+j$p$q3V$q$s+j$s$t3V$t$u+j$u$v3V$v$x+j$x$|3V$|$}+j$}%R3V%R%S+j%S%V3V%V%W+j%W%o3V%o%p+j%p&a3V&a&b+j&b#BY3V#BZ$IS3V$I_$Id3V$Id$I|+j$JO$JT3V$JU$Je+j$Je$Jg3V$Jg$Jz+j$Jz$J{3V$J{$KV+j$KW%#t3V%#t%3d+j%3d%6z3V%6z%Dl+j%Dl%E[3V%E[&/x+j&/x&;O3V&;O&=t+j&=t&FU3V&FV&FY+j&FY&F^3V&F^&Fw+j&Fw&GW3V&GW&GX+j&GX;'S3V;'S;=`7i<%l?>`3V?>`?>b+j?>b?DR3V?DR?DT+j?DTO3V_`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_@|!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#h'U#h#iE_#i#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_Eh!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#YIy#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_JU!clQmSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_Nj!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#i'U#i#j!${#j#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!%U!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#a'U#a#b!)g#b#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!)p!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#U'U#U#V!.R#V#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!.[!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y!2m#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!2v!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#f'U#f#gIy#g#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!7b!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#Y'U#Y#Z!;s#Z#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!;|!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#Y'U#Y#Z!@_#Z#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!@h!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#g'U#g#h!Dy#h#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!ES!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y!Ie#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!In!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#h'U#h#i!NP#i#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!NY!dmSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!]#$h!]!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U[#$mcmSOX+j^p+jq{+j{|#%x}!O#%x!O!Q+j!Q![#'S![#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j[#%}amSOX+j^p+jq|+j}!Q+j!Q![#'S![#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j[#'ZamSoWOX+j^p+jq|+j}!Q+j!Q![#'S![#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j_#(i!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#`'U#`#a#,z#a#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#-T!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#i'U#i#j#1f#j#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#1o!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#f'U#f#g#6Q#g#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#6Z!dmSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#U#:i#U#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#:r!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#`'U#`#a#?T#a#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#?`!cmSjPnQpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#Ct!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y#HV#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#H`!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#`'U#`#a#Lq#a#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#Lz!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y$#]#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$#f!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#V'U#V#W$'w#W#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$(Q!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#h'U#h#i$,c#i#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$,n!emSjPnQpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#c'U#c#d$1P#d#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$1Y!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#f'U#f#g$5k#g#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$5t!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#W'U#W#X$:V#X#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$:`!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#]'U#]#^$>q#^#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$>z!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#b'U#b#c#6Q#c#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$Cf!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#]'U#]#^$Gw#^#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$HQ!emSjPpWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#a'U#a#bE_#b#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'UT$Lj_UPmSOX+j^p+jq|+j}#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j~$MnOb~", - tokenizers: [text, textNested, 0, 1, 2, 3], - topRules: {"Root":[0,3],"Nested":[1,20]}, - tokenPrec: 582 + tokenData: "$Mn~R#POX'UX^,z^p'Upq,zqs+jst-ot{+j{|.u|}7o}!O.u!O!Q+j!Q!['U![!_+j!_!`7t!`!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#W'U#W#X<[#X#b'U#b#cNa#c#d!7X#d#e#(`#e#g'U#g#h#Ck#h#i$C]#i#o'U#o#p$Lc#p#q+j#q#r$Mi#r#s+j#s#y'U#y#z,z#z$f'U$f$g,z$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BY#BZ,z#BZ$IS'U$IS$I_,z$I_$Id'U$Id$I|+j$I|$JO,z$JO$JT'U$JT$JU,z$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KV$KW,z$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FU&FV,z&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]'_!coSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'US+o_oSOX+j^p+jq|+j}#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+jS,qP;=`<%l+j],wP;=`<%l'U~-PYk~X^,zpq,z#y#z,z$f$g,z#BY#BZ,z$IS$I_,z$I|$JO,z$JT$JU,z$KV$KW,z&FU&FV,zT-v_oStPOX+j^p+jq|+j}#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j[.z!coSOX3V^p3Vq|+j}!Q+j!Q![3V![!c+j!c!}3V!}#R+j#R#S3V#S#T+j#T#o3V#o#q+j#r#s+j#s#y3V#z$f3V$g$n+j$n$o3V$o$p+j$p$q3V$q$s+j$s$t3V$t$u+j$u$v3V$v$x+j$x$|3V$|$}+j$}%R3V%R%S+j%S%V3V%V%W+j%W%o3V%o%p+j%p&a3V&a&b+j&b#BY3V#BZ$IS3V$I_$Id3V$Id$I|+j$JO$JT3V$JU$Je+j$Je$Jg3V$Jg$Jz+j$Jz$J{3V$J{$KV+j$KW%#t3V%#t%3d+j%3d%6z3V%6z%Dl+j%Dl%E[3V%E[&/x+j&/x&;O3V&;O&=t+j&=t&FU3V&FV&FY+j&FY&F^3V&F^&Fw+j&Fw&GW3V&GW&GX+j&GX;'S3V;'S;=`7i<%l?>`3V?>`?>b+j?>b?DR3V?DR?DT+j?DTO3V[3^!coSrWOX3V^p3Vq|+j}!Q+j!Q![3V![!c+j!c!}3V!}#R+j#R#S3V#S#T+j#T#o3V#o#q+j#r#s+j#s#y3V#z$f3V$g$n+j$n$o3V$o$p+j$p$q3V$q$s+j$s$t3V$t$u+j$u$v3V$v$x+j$x$|3V$|$}+j$}%R3V%R%S+j%S%V3V%V%W+j%W%o3V%o%p+j%p&a3V&a&b+j&b#BY3V#BZ$IS3V$I_$Id3V$Id$I|+j$JO$JT3V$JU$Je+j$Je$Jg3V$Jg$Jz+j$Jz$J{3V$J{$KV+j$KW%#t3V%#t%3d+j%3d%6z3V%6z%Dl+j%Dl%E[3V%E[&/x+j&/x&;O3V&;O&=t+j&=t&FU3V&FV&FY+j&FY&F^3V&F^&Fw+j&Fw&GW3V&GW&GX+j&GX;'S3V;'S;=`7i<%l?>`3V?>`?>b+j?>b?DR3V?DR?DT+j?DTO3V[7lP;=`<%l3V~7tOm~[7y!eoSOX3V^p3Vq{+j{|.u}!O.u!O!Q+j!Q![3V![!c+j!c!}3V!}#R+j#R#S3V#S#T+j#T#o3V#o#q+j#r#s+j#s#y3V#z$f3V$g$n+j$n$o3V$o$p+j$p$q3V$q$s+j$s$t3V$t$u+j$u$v3V$v$x+j$x$|3V$|$}+j$}%R3V%R%S+j%S%V3V%V%W+j%W%o3V%o%p+j%p&a3V&a&b+j&b#BY3V#BZ$IS3V$I_$Id3V$Id$I|+j$JO$JT3V$JU$Je+j$Je$Jg3V$Jg$Jz+j$Jz$J{3V$J{$KV+j$KW%#t3V%#t%3d+j%3d%6z3V%6z%Dl+j%Dl%E[3V%E[&/x+j&/x&;O3V&;O&=t+j&=t&FU3V&FV&FY+j&FY&F^3V&F^&Fw+j&Fw&GW3V&GW&GX+j&GX;'S3V;'S;=`7i<%l?>`3V?>`?>b+j?>b?DR3V?DR?DT+j?DTO3V_`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_@|!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#h'U#h#iE_#i#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_Eh!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#YIy#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_JU!cnQoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_Nj!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#i'U#i#j!${#j#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!%U!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#a'U#a#b!)g#b#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!)p!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#U'U#U#V!.R#V#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!.[!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y!2m#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_!2v!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#f'U#f#gIy#g#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!7b!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#Y'U#Y#Z!;s#Z#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!;|!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#Y'U#Y#Z!@_#Z#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!@h!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#g'U#g#h!Dy#h#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!ES!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y!Ie#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!In!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#h'U#h#i!NP#i#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U]!NY!doSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!]#$h!]!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U[#$mcoSOX+j^p+jq{+j{|#%x}!O#%x!O!Q+j!Q![#'S![#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j[#%}aoSOX+j^p+jq|+j}!Q+j!Q![#'S![#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j[#'ZaoSqWOX+j^p+jq|+j}!Q+j!Q![#'S![#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j_#(i!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#`'U#`#a#,z#a#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#-T!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#i'U#i#j#1f#j#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#1o!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#f'U#f#g#6Q#g#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#6Z!doSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#U#:i#U#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#:r!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#`'U#`#a#?T#a#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#?`!coSlPpQrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#Ct!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y#HV#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#H`!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#`'U#`#a#Lq#a#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_#Lz!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#X'U#X#Y$#]#Y#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$#f!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#V'U#V#W$'w#W#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$(Q!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#h'U#h#i$,c#i#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$,n!eoSlPpQrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#c'U#c#d$1P#d#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$1Y!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#f'U#f#g$5k#g#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$5t!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#W'U#W#X$:V#X#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$:`!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#]'U#]#^$>q#^#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$>z!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#b'U#b#c#6Q#c#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$Cf!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#]'U#]#^$Gw#^#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'U_$HQ!eoSlPrWOX'U^p'Uq|+j}!Q+j!Q!['U![!c+j!c!}'U!}#R+j#R#S'U#S#T+j#T#a'U#a#bE_#b#o'U#o#q+j#r#s+j#s#y'U#z$f'U$g$n+j$n$o'U$o$p+j$p$q'U$q$s+j$s$t'U$t$u+j$u$v'U$v$x+j$x$|'U$|$}+j$}%R'U%R%S+j%S%V'U%V%W+j%W%o'U%o%p+j%p&a'U&a&b+j&b#BY'U#BZ$IS'U$I_$Id'U$Id$I|+j$JO$JT'U$JU$Je+j$Je$Jg'U$Jg$Jz+j$Jz$J{'U$J{$KV+j$KW%#t'U%#t%3d+j%3d%6z'U%6z%Dl+j%Dl%E['U%E[&/x+j&/x&;O'U&;O&=t+j&=t&FU'U&FV&FY+j&FY&F^'U&F^&Fw+j&Fw&GW'U&GW&GX+j&GX;'S'U;'S;=`,t<%l?>`'U?>`?>b+j?>b?DR'U?DR?DT+j?DTO'UT$Lj_VPoSOX+j^p+jq|+j}#q+j#r#y+j#z$f+j$g#BY+j#BZ$IS+j$I_$I|+j$JO$JT+j$JU$KV+j$KW&FU+j&FV;'S+j;'S;=`,n<%lO+j~$MnOc~", + tokenizers: [text, invalidExpression, textNested, 0, 1, 2, 3], + topRules: {"Root":[0,4],"Nested":[1,22]}, + dynamicPrecedences: {"20":-1}, + tokenPrec: 585 }) diff --git a/src/parser/placeholders/getInvalidPlaceholders.test.ts b/src/parser/placeholders/getInvalidPlaceholders.test.ts new file mode 100644 index 0000000..720671d --- /dev/null +++ b/src/parser/placeholders/getInvalidPlaceholders.test.ts @@ -0,0 +1,80 @@ +import { getInvalidPlaceholders } from "./getInvalidPlaceholders"; + +describe("getInvalidPlaceholders", () => { + it("returns nothing for plain text", () => { + expect(getInvalidPlaceholders("Nothing here")).toEqual([]); + }); + + it("returns nothing for a valid placeholder", () => { + expect(getInvalidPlaceholders("Hello {name}")).toEqual([]); + }); + + it("returns nothing for numeric params", () => { + expect(getInvalidPlaceholders("Hello {0} and {1, number}")).toEqual([]); + }); + + it("returns nothing for a valid plural", () => { + expect( + getInvalidPlaceholders("{count, plural, one {# item} other {# items}}") + ).toEqual([]); + }); + + it("detects an invalid ICU placeholder", () => { + expect(getInvalidPlaceholders("before {placeholder:space} after")).toEqual([ + { position: { start: 7, end: 26 }, value: "{placeholder:space}" }, + ]); + }); + + it("detects a dotted key as invalid", () => { + expect(getInvalidPlaceholders("{some.key}")).toEqual([ + { position: { start: 0, end: 10 }, value: "{some.key}" }, + ]); + }); + + it("detects multiple invalid placeholders", () => { + expect( + getInvalidPlaceholders("{a:b} and {c.d}").map((p) => p.value) + ).toEqual(["{a:b}", "{c.d}"]); + }); + + it("keeps valid placeholders out of the result", () => { + expect( + getInvalidPlaceholders("{name} and {bad:one}").map((p) => p.value) + ).toEqual(["{bad:one}"]); + }); + + it("captures a comma-containing invalid body as one", () => { + expect( + getInvalidPlaceholders("x {placeholder:space, foo} y").map((p) => p.value) + ).toEqual(["{placeholder:space, foo}"]); + }); + + it("captures an invalid body starting with a bidi mark as one", () => { + const rlm = "‏"; + expect( + getInvalidPlaceholders(`x {${rlm}placeholder:space} y`).map((p) => p.value) + ).toEqual([`{${rlm}placeholder:space}`]); + }); + + it("still reports invalid placeholders when the string has other errors", () => { + expect(getInvalidPlaceholders("{good:bad} {}").map((p) => p.value)).toEqual( + ["{good:bad}"] + ); + expect( + getInvalidPlaceholders("{good:bad} more {unclosed").map((p) => p.value) + ).toEqual(["{good:bad}"]); + }); + + it("detects an invalid placeholder nested in a plural variant", () => { + expect( + getInvalidPlaceholders( + "{count, plural, one {# {bad:x}} other {# items}}" + ).map((p) => p.value) + ).toEqual(["{bad:x}"]); + }); + + it("ignores an unclosed expression", () => { + expect(getInvalidPlaceholders("unclosed {")).toEqual([]); + expect(getInvalidPlaceholders("unclosed {abc")).toEqual([]); + }); +}); diff --git a/src/parser/placeholders/getInvalidPlaceholders.ts b/src/parser/placeholders/getInvalidPlaceholders.ts new file mode 100644 index 0000000..13659ca --- /dev/null +++ b/src/parser/placeholders/getInvalidPlaceholders.ts @@ -0,0 +1,35 @@ +import { + Expression, + ExpressionClose, + InvalidExpressionBody, +} from "../lezer/tolgeeParser.terms"; +import { InvalidPlaceholder } from "../types"; +import { parseTolgee } from "./parseTolgee"; + +export const getInvalidPlaceholders = ( + input: string, + nested?: boolean +): InvalidPlaceholder[] => { + // non-strict parse: unrelated syntax errors elsewhere in the string + // (`{}`, an unclosed `{`) must not suppress the warnings + const tree = parseTolgee(input, nested, false); + + const result: InvalidPlaceholder[] = []; + tree.iterate({ + enter(node) { + if (node.type.id !== Expression) { + return; + } + const body = node.node.firstChild?.nextSibling; + const closed = node.node.lastChild?.type.id === ExpressionClose; + if (body?.type.id === InvalidExpressionBody && closed) { + result.push({ + position: { start: node.from, end: node.to }, + value: input.substring(node.from, node.to), + }); + } + }, + }); + + return result; +}; diff --git a/src/parser/placeholders/getPlaceholders.test.ts b/src/parser/placeholders/getPlaceholders.test.ts index 7851850..dbb76ef 100644 --- a/src/parser/placeholders/getPlaceholders.test.ts +++ b/src/parser/placeholders/getPlaceholders.test.ts @@ -122,4 +122,15 @@ describe("get placeholders", () => { const placeholders = getPlaceholders('z'); expect(placeholders!.map((p) => p.type)).toEqual(["tagOpen", "tagClose"]); }); + + it("doesn't surface an invalid expression as a placeholder", () => { + expect(getPlaceholders("before {placeholder:space} after")).toEqual([]); + }); + + it("still parses a valid placeholder next to text", () => { + const placeholders = getPlaceholders("before {placeholder} after"); + expect(placeholders!.map((p) => p.normalizedValue)).toEqual([ + "{placeholder}", + ]); + }); }); diff --git a/src/parser/placeholders/getPlaceholders.ts b/src/parser/placeholders/getPlaceholders.ts index 801504e..e6b30f2 100644 --- a/src/parser/placeholders/getPlaceholders.ts +++ b/src/parser/placeholders/getPlaceholders.ts @@ -1,4 +1,3 @@ -import { parser } from "../lezer/tolgeeParser"; import { Expression, FormatExpression, @@ -14,6 +13,7 @@ import type { SyntaxNode, Tree } from "@lezer/common"; import { Placeholder } from "../types"; import { TagInfoType, findTags } from "./findTags"; import { findEntities } from "./findEntities"; +import { parseTolgee } from "./parseTolgee"; function getAllChildren(node: SyntaxNode) { const result: SyntaxNode[] = []; @@ -29,12 +29,7 @@ function getAllChildren(node: SyntaxNode) { export const getPlaceholders = (input: string, nested?: boolean) => { let tree: Tree; try { - tree = parser - .configure({ - strict: true, - top: nested ? "Nested" : "Root", - }) - .parse(input); + tree = parseTolgee(input, nested, true); } catch (e) { return null; } diff --git a/src/parser/placeholders/parseTolgee.ts b/src/parser/placeholders/parseTolgee.ts new file mode 100644 index 0000000..f278bd4 --- /dev/null +++ b/src/parser/placeholders/parseTolgee.ts @@ -0,0 +1,13 @@ +import { parser } from "../lezer/tolgeeParser"; + +export const parseTolgee = ( + input: string, + nested: boolean | undefined, + strict: boolean +) => + parser + .configure({ + strict, + top: nested ? "Nested" : "Root", + }) + .parse(input); diff --git a/src/parser/types.ts b/src/parser/types.ts index c5fe2f1..0d1cd63 100644 --- a/src/parser/types.ts +++ b/src/parser/types.ts @@ -11,6 +11,11 @@ export type Placeholder = { normalizedValue: string; }; +export type InvalidPlaceholder = { + position: Position; + value: string; +}; + export type TolgeeFormat = { parameter?: string; variants: Partial>; diff --git a/src/tolgee-editor.ts b/src/tolgee-editor.ts index 2328f4c..3b2c691 100644 --- a/src/tolgee-editor.ts +++ b/src/tolgee-editor.ts @@ -1,4 +1,5 @@ export { getPlaceholders } from "./parser/placeholders/getPlaceholders"; +export { getInvalidPlaceholders } from "./parser/placeholders/getInvalidPlaceholders"; export { PlaceholderPlugin } from "./parser/PlaceholderPlugin"; export { parser as tolgeeParser } from "./parser/lezer/tolgeeParser"; export { tolgeeSyntax } from "./parser/tolgeeSyntax"; diff --git a/vite.config.ts b/vite.config.ts index 4d13a30..e294326 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -18,8 +18,8 @@ export default defineConfig({ }, plugins: [ react(), - // bundle `entities` so consumers don't need it as a direct dependency - externalizeDeps({ except: ["entities"] }), + // bundle these so consumers don't need them as direct dependencies + externalizeDeps({ except: ["entities", "bidi-js"] }), { name: "build-parser", watchChange(id) {