sitegen/src/q+a/simple-markdown.ts

867 lines
30 KiB
TypeScript
Raw Normal View History

// @ts-nocheck
// # Simple-Markdown Core
//
// This is a fork of Khan-academy's Simple-Markdown[1], initially forked in 2022
// to add Svelte support[2], and used for paper clover's q+a markdown flavor.
//
// 1: https://github.com/Khan/perseus/tree/main/packages/simple-markdown/src
// 2: https://github.com/paperclover/svelte-simple-markdown
export type Rules = Record<string, ParserRule>;
export interface ParserRule {
name: string;
match: MatchFunction;
parse: ParseFunction;
quality?: QualityFunction;
}
export class RuleList extends Array<ParserRule> {
constructor(input?: ArrayLike<ParserRule>) {
super();
if (input) {
this.push(...Array.from(input));
}
}
insertBefore(rule: string, newRule: ParserRule): void {
const index = this.findIndex((r) => r.name === rule);
if (index === -1) {
throw new Error(`Rule ${rule} not found`);
}
this.splice(index, 0, newRule);
}
insertAfter(rule: string, newRule: ParserRule): void {
const index = this.findIndex((r) => r.name === rule);
if (index === -1) {
throw new Error(`Rule ${rule} not found`);
}
this.splice(index + 1, 0, newRule);
}
toRuleObject(): Record<string, ParserRule> {
const result: Record<string, ParserRule> = {};
this.forEach((rule) => {
result[rule.name] = rule;
});
return result;
}
add(rule: ParserRule): void {
this.push(rule);
}
get(rule: string): ParserRule | undefined {
return this.find((r) => r.name === rule);
}
remove(rule: string): void {
const index = this.findIndex((r) => r.name === rule);
if (index === -1) {
throw new Error(`Rule ${rule} not found`);
}
this.splice(index, 1);
}
clone() {
return new RuleList(this);
}
}
/**
* Creates a parser for a given set of rules, with the precedence
* specified as a list of rules.
*
* @param rules
* an object containing
* rule type -> {match, order, parse} objects
* (lower order is higher precedence)
* @param [defaultState]
*
* @returns
* The resulting parse function, with the following parameters:
* @source: the input source string to be parsed
* @state: an optional object to be threaded through parse
* calls. Allows clients to add stateful operations to
* parsing, such as keeping track of how many levels deep
* some nesting is. For an example use-case, see passage-ref
* parsing in src/widgets/passage/passage-markdown.jsx
*/
export function createParser(
ruleListInput: RuleList,
defaultState: Partial<ParserState> = {},
) {
let rules = ruleListInput.toRuleObject();
let ruleList = Object.keys(rules);
let latestState: ParserState;
let nestedParse = function (source: string, state?: ParserState) {
let result: ASTNode[] = [];
state = state || latestState;
latestState = state;
while (source) {
// store the best match, it's rule, and quality:
let ruleType = null;
let rule = null;
let capture = null;
let quality = NaN; // loop control variables:
let i = 0;
let currRuleType = ruleList[0];
let currRule = rules[currRuleType];
do {
let currCapture = currRule.match(source, state);
if (currCapture) {
let currQuality = currRule.quality
? currRule.quality(currCapture, state)
: 0;
// This should always be true the first time because
// the initial quality is NaN (that's why there's the
// condition negation).
if (!(currQuality <= quality)) {
ruleType = currRuleType;
rule = currRule;
capture = currCapture;
quality = currQuality;
}
}
// Move on to the next item.
// Note that this makes `currRule` be the next item
i++;
currRuleType = ruleList[i];
currRule = rules[currRuleType];
} while (
// keep looping while we're still within the ruleList
currRule &&
// if we don't have a match yet, continue
(!capture ||
// or if we have a match, but the next rule is
// at the same order, and has a quality measurement
// functions, then this rule must have a quality
// measurement function (since they are sorted before
// those without), and we need to check if there is
// a better quality match
currRule.quality)
);
if (!rule || !capture || !ruleType) {
throw new Error(
"Could not find a matching rule for the below " +
"content. The rule with highest `order` should " +
"always match content provided to it. Check " +
"the definition of `match` for '" +
ruleList[ruleList.length - 1] +
"'. It seems to not match the following source:\n" +
source,
);
}
if (capture.index) {
// If present and non-zero, i.e. a non-^ regexp result:
throw new Error(
"`match` must return a capture starting at index 0 " +
"(the current parse index). Did you forget a ^ at the " +
"start of the RegExp?",
);
}
let parsed = rule.parse(capture, nestedParse, state);
// We maintain the same object here so that rules can
// store references to the objects they return and
// modify them later. (oops sorry! but this adds a lot
// of power--see reflinks.)
// We also let rules override the default type of
// their parsed node if they would like to, so that
// there can be a single output function for all links,
// even if there are several rules to parse them.
if (!parsed.type) {
parsed.type = ruleType;
}
// Collapse text nodes
if (
parsed.type === "text" && result[result.length - 1]?.type === "text"
) {
result[result.length - 1].content += parsed.content;
} else {
result.push(parsed as ASTNode);
}
state.prevCapture = capture;
source = source.substring(state.prevCapture[0].length);
}
return result;
};
let outerParse = function (
source: string,
state: ParserState = { inline: false },
) {
latestState = populateInitialState(state, defaultState);
if (!latestState.inline && !latestState.disableAutoBlockNewlines) {
source = source + "\n\n";
}
// We store the previous capture so that match functions can
// use some limited amount of lookbehind. Lists use this to
// ensure they don't match arbitrary '- ' or '* ' in inline
// text (see the list rule for more information). This stores
// the full regex capture object, if there is one.
latestState.prevCapture = undefined;
return nestedParse(preprocess(source), latestState);
};
return outerParse;
}
type Multiple<T> = T | T[];
type Nullable<T> = T | null | undefined;
export type MatchFunction = (
source: string,
state: ParserState,
) => Nullable<RegExpMatchArray>;
export type Parser = (source: string, state?: ParserState) => ASTNode[];
export type ParseFunction = (
source: RegExpMatchArray,
nestedParse: Parser,
state: ParserState,
) => TypeOptionalASTNode;
export type QualityFunction = (
capture: RegExpMatchArray,
state: ParserState,
) => number;
export interface ParserState {
inline: boolean;
prevCapture?: RegExpMatchArray;
[key: string]: any;
}
export interface ASTNode {
type: string;
content?: ASTNode[] | string;
[key: string]: any;
}
export type TypeOptionalASTNode = Omit<ASTNode, "type"> & { type?: string };
export interface RefNode {
type: string;
content?: Multiple<ASTNode>;
target?: string;
title?: string;
alt?: string;
}
/** Creates a match function for an inline scoped element from a regex */
export function inlineRegex(regex: RegExp): MatchFunction {
return (source, state) => {
if (state.inline) {
return regex.exec(source);
} else {
return null;
}
};
}
/** Creates a match function for a block scoped element from a regex */
export function blockRegex(regex: RegExp): MatchFunction {
return (source, state) => {
if (state.inline) {
return null;
} else {
return regex.exec(source);
}
};
}
/** Creates a match function from a regex, ignoring block/inline scope */
export function anyScopeRegex(regex: RegExp): MatchFunction {
return (source) => {
return regex.exec(source);
};
}
const UNESCAPE_URL_R = /\\([^0-9A-Za-z\s])/g;
export function unescapeUrl(rawUrlString: string) {
return rawUrlString.replace(UNESCAPE_URL_R, "$1");
}
/**
* Parse some content with the parser `parse`, with state.inline
* set to true. Useful for block elements; not generally necessary
* to be used by inline elements (where state.inline is already true.
*/
export function parseInline(
parse: Parser,
content: string,
state: ParserState,
) {
const isCurrentlyInline = state.inline || false;
state.inline = true;
const result = parse(content, state);
state.inline = isCurrentlyInline;
return result;
}
export function parseBlock(parse: Parser, content: string, state: ParserState) {
const isCurrentlyInline = state.inline || false;
state.inline = false;
const result = parse(content + "\n\n", state);
state.inline = isCurrentlyInline;
return result;
}
export function parseCaptureInline(
capture: RegExpMatchArray,
parse: Parser,
state: ParserState,
) {
return {
content: parseInline(parse, capture[1], state),
};
}
export function ignoreCapture() {
return {};
}
export function sanitizeUrl(url?: string) {
if (url == null) {
return null;
}
try {
const prot = new URL(url, "https://localhost").protocol;
if (
prot.indexOf("javascript:") === 0 ||
prot.indexOf("vbscript:") === 0 ||
prot.indexOf("data:") === 0
) {
return null;
}
} catch (e) {
// invalid URLs should throw a TypeError
// see for instance: `new URL("");`
return null;
}
return url;
}
const CR_NEWLINE_R = /\r\n?/g;
const TAB_R = /\t/g;
const FORMFEED_R = /\f/g;
/**
* Turn various whitespace into easy-to-process whitespace
*/
function preprocess(source: string) {
return source.replace(CR_NEWLINE_R, "\n").replace(FORMFEED_R, "").replace(
TAB_R,
" ",
);
}
function populateInitialState(
givenState: Partial<ParserState>,
defaultState: Partial<ParserState>,
) {
let state = givenState || {};
for (let prop in defaultState) {
if (Object.prototype.hasOwnProperty.call(defaultState, prop)) {
state[prop] = defaultState[prop];
}
}
return state as ParserState;
}
// recognize a `*` `-`, `+`, `1.`, `2.`... list bullet
const LIST_BULLET = "(?:[*+-]|\\d+\\.)";
// recognize the start of a list item:
// leading space plus a bullet plus a space (` * `)
const LIST_ITEM_PREFIX = "( *)(" + LIST_BULLET + ") +";
const LIST_ITEM_PREFIX_R = new RegExp("^" + LIST_ITEM_PREFIX);
// recognize an individual list item:
// * hi
// this is part of the same item
//
// as is this, which is a new paragraph in the same item
//
// * but this is not part of the same item
const LIST_ITEM_R = new RegExp(
LIST_ITEM_PREFIX + "[^\\n]*(?:\\n" + "(?!\\1" + LIST_BULLET +
" )[^\\n]*)*(\n|$)",
"gm",
);
const BLOCK_END_R = /\n{2,}$/;
const INLINE_CODE_ESCAPE_BACKTICKS_R = /^ (?= *`)|(` *) $/g;
// recognize the end of a paragraph block inside a list item:
// two or more newlines at end end of the item
const LIST_BLOCK_END_R = BLOCK_END_R;
const LIST_ITEM_END_R = / *\n+$/;
// check whether a list item has paragraphs: if it does,
// we leave the newlines at the end
const LIST_R = new RegExp(
"^( *)(" +
LIST_BULLET +
") " +
"[\\s\\S]+?(?:\n{2,}(?! )" +
"(?!\\1" +
LIST_BULLET +
" )\\n*" +
// the \\s*$ here is so that we can parse the inside of nested
// lists, where our content might end before we receive two `\n`s
"|\\s*\n*$)",
);
const LIST_LOOKBEHIND_R = /(?:^|\n)( *)$/;
const TABLES = (function () {
const TABLE_ROW_SEPARATOR_TRIM = /^ *\| *| *\| *$/g;
const TABLE_CELL_END_TRIM = / *$/;
const TABLE_RIGHT_ALIGN = /^ *-+: *$/;
const TABLE_CENTER_ALIGN = /^ *:-+: *$/;
const TABLE_LEFT_ALIGN = /^ *:-+ *$/; // TODO: This needs a real type
const parseTableAlignCapture = (alignCapture: string) => {
if (TABLE_RIGHT_ALIGN.test(alignCapture)) {
return "right";
} else if (TABLE_CENTER_ALIGN.test(alignCapture)) {
return "center";
} else if (TABLE_LEFT_ALIGN.test(alignCapture)) {
return "left";
} else {
return null;
}
};
const parseTableAlign = (source: string, trimEndSeparators: boolean) => {
if (trimEndSeparators) {
source = source.replace(TABLE_ROW_SEPARATOR_TRIM, "");
}
const alignText = source.trim().split("|");
return alignText.map(parseTableAlignCapture);
};
const parseTableRow = (
source: string,
parse: Parser,
state: ParserState,
trimEndSeparators: boolean,
) => {
const prevInTable = state.inTable;
state.inTable = true;
const tableRow = parse(source.trim(), state);
state.inTable = prevInTable;
const cells: ASTNode[][] = [[]];
tableRow.forEach(function (node, i) {
if (node.type === "tableSeparator") {
// Filter out empty table separators at the start/end:
if (!trimEndSeparators || (i !== 0 && i !== tableRow.length - 1)) {
// Split the current row:
cells.push([]);
}
} else {
if (
typeof node.content === "string" &&
(tableRow[i + 1] == null || tableRow[i + 1].type === "tableSeparator")
) {
node.content = node.content.replace(TABLE_CELL_END_TRIM, "");
}
cells[cells.length - 1].push(node);
}
});
return cells;
};
/**
* @param {string} source
* @param {SimpleMarkdown.Parser} parse
* @param {SimpleMarkdown.State} state
* @param {boolean} trimEndSeparators
* @returns {SimpleMarkdown.ASTNode[][]}
*/
const parseTableCells = function (
source: string,
parse: Parser,
state: ParserState,
trimEndSeparators: boolean,
) {
const rowsText = source.trim().split("\n");
return rowsText.map(function (rowText) {
return parseTableRow(rowText, parse, state, trimEndSeparators);
});
};
/**
* @param {boolean} trimEndSeparators
* @returns {SimpleMarkdown.SingleNodeParseFunction}
*/
const parseTable = function (trimEndSeparators: boolean) {
return function (
capture: RegExpMatchArray,
parse: Parser,
state: ParserState,
) {
state.inline = true;
const header = parseTableRow(capture[1], parse, state, trimEndSeparators);
const align = parseTableAlign(capture[2], trimEndSeparators);
const cells = parseTableCells(
capture[3],
parse,
state,
trimEndSeparators,
);
state.inline = false;
return {
type: "table",
header: header,
align: align,
cells: cells,
};
};
};
return {
parseTable: parseTable(true),
parseNpTable: parseTable(false),
TABLE_REGEX: /^ *(\|.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/,
NPTABLE_REGEX:
/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
};
})();
const LINK_INSIDE = "(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*";
const LINK_HREF_AND_TITLE =
"\\s*<?((?:\\([^)]*\\)|[^\\s\\\\]|\\\\.)*?)>?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*";
const AUTOLINK_MAILTO_CHECK_R = /mailto:/i;
function parseRef(
capture: RegExpMatchArray,
state: ParserState,
refNode: RefNode,
) {
const ref = (capture[2] || capture[1]).replace(/\s+/g, " ").toLowerCase();
// We store information about previously seen defs on
// state._defs (_ to deconflict with client-defined
// state). If the def for this reflink/refimage has
// already been seen, we can use its target/source
// and title here:
if (state._defs && state._defs[ref]) {
const def = state._defs[ref];
// `refNode` can be a link or an image. Both use
// target and title properties.
refNode.target = def.target;
refNode.title = def.title;
}
// In case we haven't seen our def yet (or if someone
// overwrites that def later on), we add this node
// to the list of ref nodes for that def. Then, when
// we find the def, we can modify this link/image AST
// node :).
// I'm sorry.
state._refs = state._refs || {};
state._refs[ref] = state._refs[ref] || [];
state._refs[ref].push(refNode);
return refNode;
}
export const defaultRules = new RuleList();
{
defaultRules.add({
name: "heading",
match: blockRegex(/^ *(#{1,6})([^\n]+?)#* *(?:\n *)+\n/),
parse: function (capture, parse, state) {
return {
level: capture[1].length,
content: parseInline(parse, capture[2].trim(), state),
};
},
});
defaultRules.add({
name: "nptable",
match: blockRegex(TABLES.NPTABLE_REGEX),
parse: TABLES.parseNpTable,
});
defaultRules.add({
name: "lheading",
match: blockRegex(/^([^\n]+)\n *(=|-){3,} *(?:\n *)+\n/),
parse(capture, parse, state) {
return {
type: "heading",
level: capture[2] === "=" ? 1 : 2,
content: parseInline(parse, capture[1], state),
};
},
});
defaultRules.add({
name: "hr",
match: blockRegex(/^( *[-*_]){3,} *(?:\n *)+\n/),
parse: ignoreCapture,
});
defaultRules.add({
name: "codeBlock",
match: blockRegex(/^(?: {4}[^\n]+\n*)+(?:\n *)+\n/),
parse(capture) {
const content = capture[0].replace(/^ {4}/gm, "").replace(/\n+$/, "");
return {
lang: undefined,
content: content,
};
},
});
defaultRules.add({
name: "fence",
match: blockRegex(
/^ *(`{3,}|~{3,}) *(?:(\S+) *)?\n([\s\S]+?)\n?\1 *(?:\n *)+\n/,
),
parse(capture) {
return {
type: "codeBlock",
lang: capture[2] || undefined,
content: capture[3],
};
},
});
defaultRules.add({
name: "blockQuote",
match: blockRegex(/^( *>[^\n]+(\n[^\n]+)*\n*)+\n{2,}/),
parse(capture, parse, state) {
const content = capture[0].replace(/^ *> ?/gm, "");
return {
content: parse(content, state),
};
},
});
defaultRules.add({
name: "list",
match(source, state) {
// We only want to break into a list if we are at the start of a
// line. This is to avoid parsing "hi * there" with "* there"
// becoming a part of a list.
// You might wonder, "but that's inline, so of course it wouldn't
// start a list?". You would be correct! Except that some of our
// lists can be inline, because they might be inside another list,
// in which case we can parse with inline scope, but need to allow
// nested lists inside this inline scope.
const prevCaptureStr = state.prevCapture == null
? ""
: state.prevCapture[0];
const isStartOfLineCapture = LIST_LOOKBEHIND_R.exec(prevCaptureStr);
const isListBlock = state._list || !state.inline;
if (isStartOfLineCapture && isListBlock) {
source = isStartOfLineCapture[1] + source;
return LIST_R.exec(source);
} else {
return null;
}
},
parse(capture, parse, state) {
const bullet = capture[2];
const ordered = bullet.length > 1;
const start = ordered ? +bullet : undefined;
// We know this will match here, because of how the regexes are defined
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const items = capture[0].replace(LIST_BLOCK_END_R, "\n").match(
LIST_ITEM_R,
)!;
let lastItemWasAParagraph = false;
const itemContent = items.map(function (item, i) {
// We need to see how far indented this item is:
const prefixCapture = LIST_ITEM_PREFIX_R.exec(item);
const space = prefixCapture ? prefixCapture[0].length : 0; // And then we construct a regex to "unindent" the subsequent
// lines of the items by that amount:
const spaceRegex = new RegExp("^ {1," + space + "}", "gm"); // Before processing the item, we need a couple things
const content = item // remove indents on trailing lines:
.replace(spaceRegex, "") // remove the bullet:
.replace(LIST_ITEM_PREFIX_R, ""); // I'm not sur4 why this is necessary again?
// Handling "loose" lists, like:
//
// * this is wrapped in a paragraph
//
// * as is this
//
// * as is this
const isLastItem = i === items.length - 1;
const containsBlocks = content.indexOf("\n\n") !== -1; // Any element in a list is a block if it contains multiple
// newlines. The last element in the list can also be a block
// if the previous item in the list was a block (this is
// because non-last items in the list can end with \n\n, but
// the last item can't, so we just "inherit" this property
// from our previous element).
const thisItemIsAParagraph = containsBlocks ||
(isLastItem && lastItemWasAParagraph);
lastItemWasAParagraph = thisItemIsAParagraph; // backup our state for restoration afterwards. We're going to
// want to set state._list to true, and state.inline depending
// on our list's looseness.
const oldStateInline = state.inline;
const oldStateList = state._list;
state._list = true; // Parse inline if we're in a tight list, or block if we're in
// a loose list.
let adjustedContent;
if (thisItemIsAParagraph) {
state.inline = false;
adjustedContent = content.replace(LIST_ITEM_END_R, "\n\n");
} else {
state.inline = true;
adjustedContent = content.replace(LIST_ITEM_END_R, "");
}
const result = parse(adjustedContent, state); // Restore our state before returning
state.inline = oldStateInline;
state._list = oldStateList;
return result;
});
return {
ordered: ordered,
start: start,
content: itemContent,
};
},
});
defaultRules.add({
name: "def",
// TODO: This will match without a blank line before the next
// block element, which is inconsistent with most of the rest of
// simple-markdown.
match: blockRegex(
/^ *\[([^\]]+)\]: *<?([^\s>]*)>?(?: +["(]([^\n]+)[")])? *\n(?: *\n)*/,
),
parse(capture, parse, state) {
const def = capture[1].replace(/\s+/g, " ").toLowerCase();
const target = capture[2];
const title = capture[3];
// Look for previous links/images using this def
// If any links/images using this def have already been declared,
// they will have added themselves to the state._refs[def] list
// (_ to deconflict with client-defined state). We look through
// that list of reflinks for this def, and modify those AST nodes
// with our newly found information now.
// Sorry :(.
if (state._refs && state._refs[def]) {
// `refNode` can be a link or an image
state._refs[def].forEach((refNode: RefNode) => {
refNode.target = target;
refNode.title = title;
});
}
// Add this def to our map of defs for any future links/images
// In case we haven't found any or all of the refs referring to
// this def yet, we add our def to the table of known defs, so
// that future reflinks can modify themselves appropriately with
// this information.
state._defs = state._defs || {};
state._defs[def] = {
target: target,
title: title,
};
// return the relevant parsed information
// for debugging only.
return {
def: def,
target: target,
title: title,
};
},
});
defaultRules.add({
name: "table",
match: blockRegex(TABLES.TABLE_REGEX),
parse: TABLES.parseTable,
});
defaultRules.add({
name: "newline",
match: blockRegex(/^(?:\n *)*\n/),
parse: ignoreCapture,
});
defaultRules.add({
name: "paragraph",
match: blockRegex(/^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/),
parse: parseCaptureInline,
});
defaultRules.add({
name: "escape",
// We don't allow escaping numbers, letters, or spaces here so that
<EFBFBD><EFBFBD>@VXLL p8<EFBFBD>VxK<e@,vD<EFBFBD>l3<EFBFBD><EFBFBD>5<EFBFBD><EFBFBD> <EFBFBD>???<EFBFBD>????????<EFBFBD>????????<EFBFBD>??????????d<EFBFBD>main.c<EFBFBD>prop<EFBFBD>gt<EFBFBD>p<EFBFBD><EFBFBD><EFBFBD>th<EFBFBD><EFBFBD><EFBFBD>r<EFBFBD>w<EFBFBD>tU<EFBFBD>n0tf<EFBFBD>c<EFBFBD>iwby<EFBFBD>R
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"\DlM<00>ain<><6E>Ke<00>xI<>it0"a<EFBFBD>izPP<EFBFBD>rop<EFBFBD> g0<EFBFBD><EFBFBD>o
<EFBFBD>`pAd<00>3264T<><54><07>Ph<>O<07>e<00>R<><52>w<><10>e<>'<01>p{I*m<>optPa<00>bP N<>mP 1 <0B>Ppac<>O<01><00><07>N<>q*fp cinC<>Q*bpcko<01><07>ch<>p<>wSuPsys<>p)e<>VPr<>5 <09>h<EFBFBD><00>o<01><01><01><01><01><01><01><03><00><03><00><03><00><03><00><03><00><03><00><03><00>ų`<EFBFBD><EFBFBD><EFBFBD><EFBFBD>j<EFBFBD><EFBFBD><EFBFBD><EFBFBD>(0<EFBFBD><EFBFBD>t<EFBFBD> Pro<EFBFBD>ces <EFBFBD><EFBFBD>r<EFBFBD>atPdpTh<EFBFBD> VxK<EFBFBD>x <EFBFBD>vpr<EFBFBD>i<EFBFBD>n<EFBFBD>Bi<EFBFBD>1.12Ar428<EFBFBD>(TR<EFBFBD>l0aPe<EFBFBD>)
U<EFBFBD>p<EFBFBD>gPam<EFBFBD>64-biP an<EFBFBD>d<EFBFBD>t<EFBFBD>op<EFBFBD><EFBFBD>rptng<EFBFBD><EFBFBD>sy<EFBFBD>1 _Q1<EFBFBD>Ful <EFBFBD>5<EFBFBD> h2oR3E<EFBFBD>XE:CPl\<EFBFBD><EFBFBD> F<EFBFBD><EFBFBD> s\G<EFBFBD>tW<EFBFBD>P<EFBFBD> ow<EFBFBD>\0 Zi<EFBFBD>
g<EFBFBD>1 \2 n<EFBFBD><EFBFBD>g<EFBFBD>.p x0<EFBFBD>ZC<EFBFBD>m<EFBFBD><EFBFBD> l<EFBFBD>e]<EFBFBD> 9<EFBFBD> <EFBFBD>-UPoppPgrms<EFBFBD>q u<EFBFBD>3p<EFBFBD>0r<EFBFBD>lp<EFBFBD> =<EFBFBD><EFBFBD>1<EFBFBD>-<EFBFBD>i<EFBFBD>n<EFBFBD>e<EFBFBD>=<EFBFBD>t<EFBFBD>h<EFBFBD>QQ-z<EFBFBD>u<EFBFBD>r%l<EFBFBD>%$
#<EFBFBD>ZS<EFBFBD>c<EFBFBD>q%f<EFBFBD>y<EFBFBD><EFBFBD>q1i0 Qz<EFBFBD> <EFBFBD><EFBFBD><EFBFBD>  q <EFBFBD>%<EFBFBD>.<EFBFBD><EFBFBD><EFBFBD>p!%<EFBFBD><EFBFBD>Ifo<EFBFBD>at%<EFBFBD>e<EFBFBD>e<EFBFBD>s<EFBFBD>vR<EFBFBD>u<EFBFBD>ePf2<EFBFBD>a<EFBFBD>i0<EFBFBD><EFBFBD>DP<EFBFBD>s<EFBFBD>bpeF<EFBFBD><EFBFBD>CP(i<EFBFBD>d<EFBFBD>0r<EFBFBD>A<EFBFBD>&p<EFBFBD><EFBFBD>*c<EFBFBD>]Qcv$V<EFBFBD>ru<EFBFBD>oPf<EFBFBD><EFBFBD> S<EFBFBD><EFBFBD> 1;<EFBFBD>/ss<EFBFBD>o0<EFBFBD>S<EFBFBD><EFBFBD>0P;0 <EFBFBD>1Ak<EFBFBD>"P<EFBFBD>S0r0n<EFBFBD>kR<EFBFBD> d
f<EFBFBD>U14tlp
ap#e<EFBFBD>p DL<EFBFBD>5<EFBFBD>>o<EFBFBD><EFBFBD>&Q<EFBFBD><EFBFBD><EFBFBD> 0s"7<7F><<3C>77<7F>}7;<3B><04>%~3pJ<02><y?1;<>N<EFBFBD><01>Bq"qU<EFBFBD>B<EFBFBD>m<EFBFBD><EFBFBD>w<EFBFBD>B<EFBFBD><EFBFBD>Q_} M<EFBFBD>?r<EFBFBD>F<EFBFBD>SnuUw<EFBFBD>@QQKc<EFBFBD><EFBFBD>1/c<EFBFBD><EFBFBD>rp9s<EFBFBD><EFBFBD>J<EFBFBD><EFBFBD>EP=q;pr AP 1Y<EFBFBD>Xu0B\jUkP@2<EFBFBD>`006<>07h<>tp<03>I<EFBFBD>t<EFBFBD>
W]d<EFBFBD>3[S#<EFBFBD>S8a<EFBFBD>\<EFBFBD>Zb<EFBFBD>P,m<EFBFBD><EFBFBD>q%<EFBFBD>NP<EFBFBD>2e<EFBFBD>d\uU.06\v<EFBFBD> <EFBFBD> I p:qjl<EFBFBD><EFBFBD> WiF0\bPn\;C:8Progam<EFBFBD> <EFBFBD>lesl<EFBFBD>CdmTon 2jI"tJlJ<EFBFBD>r<EFBFBD>js<EFBFBD>ofmv<EFBFBD> <EFBFBD>%ndw[SyCm3<EFBFBD>2G!P)e<EFBFBD>Sh]kv1<EFBFBD>.0-!<EFBFBD>M<EFBFBD>
cAoiofFt<EFBFBD>b<EFBFBD>C P<EFBFBD>-rE<EFBFBD> o<EFBFBD>ma<EFBFBD>kc<EFBFBD> T<EFBFBD>?olk<EFBFBD>!t<EFBFBD>7PuрTY<EFBFBD>U<EFBFBD>=<EFBFBD>bU<EFBFBD><EFBFBD>c<EFBFBD>dl<EFBFBD>+e<EFBFBD>Cc<EFBFBD>5r<EFBFBD>s<EFBFBD>\ATp<EFBFBD>D<EFBFBD> t<EFBFBD>\|L<EFBFBD>
<EFBFBD><EFBFBD>y<EFBFBD>Ё#P<EFBFBD><EFBFBD><EFBFBD>t<EFBFBD>%<EFBFBD><EFBFBD>\<EFBFBD>`y<EFBFBD>l*h<>n<><6E>8<EFBFBD>=S<00><>zi<7A>(t<>><3E><><EFBFBD>"<22>"<22><>iG<69>XKf\<EFBFBD>0m<EFBFBD><EFBFBD><EFBFBD><EFBFBD><0F>.s@ o@Bp@A<>s@Hi@<40>s<EFBFBD><73>M<EFBFBD>˾i@<40><><EFBFBD>o<EFBFBD><6F>YK<59>N<EFBFBD>7bd<62><64>.j@w<>Ga<15>Cp<43>Cn<43> ovU<76>0m<30>&c<>Rr<52>aeb|%<25>"<00><><00><>j<EFBFBD><6A><01><05><00>`"<EFBFBD>``r<EFBFBD>ZS c 0<EFBFBD><EFBFBD>f<EFBFBD>lU` y<>]i` i<EFBFBD>TiU<EFBFBD>[l<EFBFBD> z d<EFBFBD>D<EFBFBD> ]L<EFBFBD><EFBFBD><EFBFBD>w`a<04>cZs`b`g<>.<2E> T<> :<3A><03>w<><06> f
<EFBFBD>u<EFBFBD> a{<EFBFBD> s<EFBFBD> a<EFBFBD> U<EFBFBD>m <EFBFBD>a h<EFBFBD>iA
:
b"<22>?<3F>VxK<> <09>x<EFBFBD>"<EFBFBD>64<EFBFBD>I<EFBFBD><EFBFBD>sZm`n<><6E>w<00><08>e+e8-`sl<EFBFBD><EFBFBD>mf<EFBFBD>k ;<EFBFBD><EFBFBD><EFBFBD>yK<EFBFBD>u<EFBFBD>&<EFBFBD>r`c<><63>?rony^E GaCaR<61><A<><41>pU<70>Fu<46>x\<EFBFBD>]d<><64>- <0B>ţ<EFBFBD>.<2E>(.7<00>-<2D>Bo<42>s`=<EFBFBD><EFBFBD>*<EFBFBD>k<EFBFBD>%m&#<EFBFBD><EFBFBD>\1<EFBFBD><EFBFBD>b`Du/3!b<><62><EFBFBD><EFBFBD><7F><EFBFBD><EFBFBD><EFBFBD>~<7E>~<7E>~<7E>f<EFBFBD>Ke7<>S>9<><39>f?\<02><10>~<7E> <0B> <0B>~ \<06>~~n<>J<EFBFBD>~!<21>*W]<5D>~\<EFBFBD>?1o<31>! P<06>Hq4{11<31>~o<02>~<7E>~<7E>3<7F><00>~<7E>~1<04><><EFBFBD><08><><EFBFBD><><08>_<>?F
<EFBFBD>~r[pGq<EFBFBD>h<EFBFBD>&\<EFBFBD>c<EFBFBD><EFBFBD>poP?<EFBFBD>h<EFBFBD>Um0<EFBFBD>??<EFBFBD>2i<EFBFBD>J<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>U<EFBFBD><EFBFBD>2<EFBFBD>2<EFBFBD><EFBFBD><EFBFBD>
<EFBFBD>
<EFBFBD>~q0&<EFBFBD><EFBFBD><EFBFBD>~<EFBFBD><EFBFBD><EFBFBD>uېr<EFBFBD>wnT<EFBFBD>S;<EFBFBD>d=<EFBFBD>?<EFBFBD>py&@<EFBFBD>pXR<EFBFBD>.<EFBFBD>|<EFBFBD>qXQ\5y<EFBFBD>p<EFBFBD>rP<EFBFBD>:<EFBFBD>xAD<EFBFBD>uAЫ<EFBFBD>I<EFBFBD><EFBFBD>.<EFBFBD>^т P<EFBFBD>><EFBFBD>k<EFBFBD>wa0v<EFBFBD>}np<EFBFBD>߇<EFBFBD>+<EFBFBD>L<EFBFBD>Ѕ R!<EFBFBD>KEP
<EFBFBD>NPL<EFBFBD><EFBFBD>bPQ<EFBFBD><EFBFBD>_Y(_P _<EFBFBD><EFBFBD>)<EFBFBD>vP><EFBFBD><EFBFBD>x<EFBFBD><EFBFBD><EFBFBD>^&<EFBFBD><EFBFBD><EFBFBD><EFBFBD>dp3l<EFBFBD><EFBFBD>s<EFBFBD><EFBFBD><EFBFBD><EFBFBD>)<EFBFBD><EFBFBD><EFBFBD><EFBFBD>U0q1 <EFBFBD>z<EFBFBD>x<EFBFBD><EFBFBD><EFBFBD>r<EFBFBD><EFBFBD> W2_<EFBFBD><EFBFBD><EFBFBD>t<EFBFBD>ԋ<EFBFBD><EFBFBD>@p<EFBFBD><EFBFBD>m<EFBFBD>ڱ<EFBFBD>wk<EFBFBD>+r<EFBFBD><EFBFBD>k;<EFBFBD> w! p<EFBFBD><EFBFBD>4e<EFBFBD><EFBFBD>)<EFBFBD><EFBFBD><EFBFBD><EFBFBD>l<EFBFBD>p<EFBFBD><EFBFBD>1<EFBFBD> 
o<EFBFBD><EFBFBD><EFBFBD>/ <EFBFBD><EFBFBD><EFBFBD><EFBFBD>q]<EFBFBD>\0e_xl"r<EFBFBD><EFBFBD><EFBFBD><EFBFBD>1 UR зx00:7 <EFBFBD><EFBFBD>20<EFBFBD>Qi<EFBFBD>00 (size:h117
52<bytls)<EFBFBD><EFBFBD>j<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
&@<EFBFBD>qThV DL kxn~.dl w"a<00> mp<00>p-d?Fu<1F>ptE 5o eQ<01>"C<EFBFBD>\ProgaM F<EFBFBD>lOs\V5wK x 64 <EFBFBD>"
U<EFBFBD>To<EFBFBD>:d<EFBFBD>d<EFBFBD>4au<EFBFBD>< <EFBFBD><EFBFBD>x<EFBFBD><EFBFBD><EFBFBD><EFBFBD>7ŀ.E<EFBFBD>6A<EFBFBD>m<EFBFBD>
<EFBFBD><EFBFBD>3<EFBFBD><EFBFBD>168<EFBFBD>#<EFBFBD><EFBFBD>"<22><><EFBFBD><EFBFBD>KERN<>LB<><42>:S<>ѢW@Mn@gVo@fANs<4E>{s@@eq<65>T32<>OW"<EFBFBD>OD<EFBFBD>@17<EFBFBD>U<EFBFBD>O43@N&2<EFBFBD><EFBFBD>c<EFBFBD>&<EFBFBD><EFBFBD><EFBFBD>&<EFBFBD>R@owB<EFBFBD>A<EFBFBD><EFBFBD>G<EFBFBD>i<EFBFBD>1p<EFBFBD><EFBFBD>r<EFBFBD>4A<EFBFBD><EFBFBD>A<EFBFBD>âGS-><EFBFBD>DW<EFBFBD>"<00>UN<55>l<EFBFBD>Pb<50>c<>npv-<2D>M<EFBFBD>jk<6A>G<EFBFBD><47>tMsm@n<><6E><EFBFBD>w<EFBFBD><77><EFBFBD><EFBFBD>A)n<>tY,:;<3B>_<EFBFBD>6 <0B>ob0z9 :7`"<EFBFBD>b1-<EFBFBD>:+{<EFBFBD>:<EFBFBD>=t<EFBFBD><EFBFBD><EFBFBD>e<EFBFBD>Vq;b<EFBFBD>`|s<>D<EFBFBD>;<3B>(<28><0E><0E>m <20>vc<>1#<03><>I`<EFBFBD><EFBFBD><EFBFBD>.f<EFBFBD>?<EFBFBD><EFBFBD>!<EFBFBD>h Yt<EFBFBD>P<EFBFBD><EFBFBD>i<EFBFBD>?<EFBFBD>~e ;<EFBFBD>m?E#C<EFBFBD><EFBFBD>A-<EFBFBD>1`<EFBFBD>!E4 <20> 髧#k7r<1D><><EFBFBD>2<EFBFBD>h?<3F>?<3F>?<3F><>'+<12>'<27>'B;hm<68>l7`<EFBFBD>sly'<00>j<EFBFBD>L_-l0%<25>'{{<EFBFBD>uX<EFBFBD>'gy<67>'Q[m0 Sgl<EFBFBD> <EFBFBD><t<EFBFBD><EFBFBD> 0<EFBFBD>~<EFBFBD>Q<EFBFBD>&E<EFBFBD><EFBFBD>E<EFBFBD>&<EFBFBD>?<EFBFBD>6rz204<EFBFBD>&?<EFBFBD>`A<EFBFBD>!<EFBFBD><EFBFBD>_0VsUt0<EFBFBD><EFBFBD>-l<EFBFBD>@____ߎ<EFBFBD>َ<EFBFBD>_<EFBFBD>|<EFBFBD>_ip<EFBFBD>\0
~i<EFBFBD>5<EFBFBD><EFBFBD>
??<EFBFBD>6]<EFBFBD><EFBFBD>C<EFBFBD><EFBFBD><EFBFBD>_6<EFBFBD>8<EFBFBD><EFBFBD><Q<EFBFBD>?=X-X6P<EFBFBD><EFBFBD>|јVБPI<EFBFBD>#U_XxP"d0ya<EFBFBD><EFBFBD>is<EFBFBD><EFBFBD>0<EFBFBD>g<EFBFBD> <EFBFBD>0mo<EFBFBD>_g<EFBFBD>ׄ<EFBFBD><EFBFBD>R_Wg_Ջ<EFBFBD><EFBFBD>1<EFBFBD>9F<EFBFBD>y<EFBFBD><EFBFBD>ag{_g<EFBFBD><EFBFBD><EFBFBD><EFBFBD>_S_g<EFBFBD><EFBFBD>mKP7<EFBFBD><EFBFBD>e<EFBFBD><\<EFBFBD><EFBFBD>a<EFBFBD>ЩvPp0u <EFBFBD>*<EFBFBD>*<EFBFBD>q)<EFBFBD>gB<EFBFBD><EFBFBD><EFBFBD>*3<EFBFBD>6<EFBFBD><EFBFBD><EFBFBD>*_<EFBFBD><EFBFBD><EFBFBD> <EFBFBD>4<EFBFBD>}<EFBFBD>
s<EFBFBD><EFBFBD>g<EFBFBD>(<EFBFBD><EFBFBD>s<EFBFBD>S<EFBFBD>S_S__Y<EFBFBD><EFBFBD>SF?T4T8r<EFBFBD>0pw?TwP<EFBFBD>}q<EFBFBD>c<EFBFBD>P<EFBFBD>o<EFBFBD>`<EFBFBD>S<EFBFBD>'<27>'<27><>_g
d<EFBFBD><EFBFBD>w0\S<EFBFBD>Y0T<EFBFBD><EFBFBD>M<EFBFBD>C\P<EFBFBD>e<EFBFBD><EFBFBD>h<EFBFBD><EFBFBD>5??<EFBFBD>;<EFBFBD>&<EFBFBD><EFBFBD>1gQ<EFBFBD>6g?<EFBFBD><EFBFBD><EFBFBD>&?<EFBFBD><EFBFBD>P<EFBFBD>WR<EFBFBD>h4[<EFBFBD>o th<EFBFBD>epDL:H"C:\Wind ows<s
yt<EFBFBD>m3"2DRPVR T4.Rl<00>l"
<EFBFBD><EFBFBD>oa"e <12>t<00>0x0<02>7FED<15> (cize <01>1gk896Cb<00><01>s)4<><34><EFBFBD>j<00><><01><00>P@<00>$<00><00> <09> <00>ibpcr_2-<00>8<EFBFBD>0<>] <20>ta<74>?h m<>Yp<59><14>_U<>Ru<52>l<>?p<>eh-<2D>t<><74><EFBFBD><EFBFBD>P<EFBFBD>1o<00>g<EFBFBD>a<>& <20>!i<>!<21>e<EFBFBD>.\G<><47>t<EFBFBD><74><EFBFBD>m<EFBFBD><6D>g<EFBFBD>A6<41><36>\<5C>T}<7D><>\<5C><1A>Z<EFBFBD><5A><EFBFBD><EFBFBD><EFBFBD>-3G<33>{<7B><>M\61<>c3M<33>4@9<>[&\<03>+<2B>R@Uw<55>=<3D><>h Ô <20>5m<35>R<EFBFBD>o@t@<40> K@~hRN@LB<4C>Ŏ U@c>@k@<40>b@ZsW<73>SyE(hm<>^ve<76>~tH|-EA<>t<EFBFBD><74>Y<>}N<>zDh^1<><31>DLI<4C><49><EFBFBD>b•\u<><75>l@b<>w<EFBFBD><77>%G2<47> 68 C<>-Gau6<75>t<EFBFBD>7?u"u?G?G?G<EFBFBD><EFBFBD>?G <EFBFBD>?Gr<EFBFBD><EFBFBD>D<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>OTj<EFBFBD> <EFBFBD>\<EFBFBD>u<EFBFBD>e c.<EFBFBD>ew"_<><EFBFBD><7F><EFBFBD>;<3B>yGV`lK<6C>k<EFBFBD>x<EFBFBD><78><EFBFBD><00><>q?<3F>#Be<42>^B @#<02><00>D1<44><31>7`/<2F>;(knoUS<>`RR<52><52>?(a<><61>?(<>G1<10>%<25>#07 1W<31><57>e<02>%0<><30>4<EFBFBD>n <20><><EFBFBD>y<EFBFBD>!<21>a<EFBFBD>9&<1D>}5mNG<4E>LI<4C>%pPF<00>_o_oL]?<3F>?<3F>3<EFBFBD><33><07><><1F><><17>25_H۰,qS4<53><12>%8<>%<25><12><>9<EFBFBD><39>f<>%L<><4C><EFBFBD>K_]߁<>%<25><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>??;B0<42><30>1%5'3<><01>7<EFBFBD>U<EFBFBD><12>$ <00>/<2F>P<12><>X<EFBFBD>7d<37>$<24>P<EFBFBD>7<EFBFBD><1F><1F>s<EFBFBD>/<15><><07><12><12>1<11>$C<>$<24>$8<>$q+Iߧ<00><00><1C><><EFBFBD><EFBFBD>7KWM9<>ߦ<><DFA6><EFBFBD> 0J<7F><4A><EFBFBD><EFBFBD><EFBFBD><><D7A6>^<5E>^<5E><08><13><13>CPD<50>߀<EFBFBD><DF80>30`80?&Qwj^8V^S<>IO0<4F><30><13><13><13>r9<72>_^<5E><>?1<13>EPI1]94<39><34><EFBFBD>1<EFBFBD>M2~<7E>Q͝&RP<52>W<00><>R<EFBFBD>F<>LLL<>͟^<5E><><EFBFBD><EFBFBD><EFBFBD>9}_dR__<13>:C<><43>q1]<13>^<5E>q2<71>L<EFBFBD><4C>^ym<>&<26><>T0M<30>PA<>_I<13>Fu_d<><64>dp"<EFBFBD>A <EFBFBD>K<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Wr0<EFBFBD><EFBFBD><EFBFBD>9<EFBFBD><EFBFBD>29216 bytes)U<EFBFBD><EFBFBD><EFBFBD>j<EFBFBD><EFBFBD><EFBFBD><EFBFBD>(L@ <EFBFBD><EFBFBD>lTh<EFBFBD> DL <EFBFBD>CFGMR3<EFBFBD>.d<EFBFBD>l wa<EFBFBD><EFBFBD> mppj<EFBFBD>d<EFBFBD>Fu>pjtM }o[ e+Y: "]:<EFBFBD>\WinW<EFBFBD>oSss<EFBFBD>s<EFBFBD>
ms\<EFBFBD>" <EFBFBD>
<EFBFBD>okd<EFBFBD> u<EFBFBD> 0x<EFBFBD>0<EFBFBD><EFBFBD>7<EFBFBD>bE<EFBFBD>D<EFBFBD>4<EFBFBD> <EFBFBD> (<EFBFBD>`iz<><05>G2<47><32>184 <0B>N<EFBFBD><4E>O<EFBFBD>JEA^U<><55><EFBFBD><EFBFBD>U!<21>ME@`2u<EFBFBD>M8@L0<EFBFBD><EFBFBD>AV<EFBFBD><EFBFBD><EFBFBD>jΛo@<EFBFBD>e<EFBFBD>@t<EFBFBD>Űk<EFBFBD><EFBFBD><EFBFBD>J7<EFBFBD>}E<EFBFBD>Ϙ1<EFBFBD>Q9@<EFBFBD>QKe<EFBFBD>V<EFBFBD>PKDEV<EFBFBD>OBJ<EFBFBD><EFBFBD>?s/s1?&<EFBFBD>#<EFBFBD>rE<EFBFBD>k5&<EFBFBD>%6<EFBFBD>%9<EFBFBD><EFBFBD><EFBFBD>%Tq<EFBFBD>Kk<EFBFBD><EFBFBD>n`Ct<1F>%<25><1D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>PrPog<>a<><61> <20>`<EFBFBD>i<EFBFBD>Ya<EFBFBD>V K <EFBFBD>x<EFBFBD><EFBFBD><EFBFBD>a\/<EFBFBD>'U<>'C<EFBFBD>e7vs5bi4 t-sM+<EFBFBD>DN<EFBFBD>S [PIM<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ls!ץ<EFBFBD>1<EFBFBD>%<EFBFBD>%D I!J<EFBFBD><EFBFBD>s37<EFBFBD>`<EFBFBD>7`&/<2F><>%<25>%W<>%<25>2_<>/s<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>%<EFBFBD><EFBFBD>%l<EFBFBD>s<EFBFBD><EFBFBD>9<EFBFBD>E<EFBFBD><EFBFBD>B<EFBFBD>%<EFBFBD>,<EFBFBD>30<EFBFBD>&ц<EFBFBD>%<EFBFBD>L<EFBFBD><EFBFBD>f߾9<EFBFBD>%<EFBFBD>%<EFBFBD><EFBFBD><EFBFBD>tP7Q<EFBFBD><EFBFBD><EFBFBD><EFBFBD>dPw<EFBFBD>~\0~y0<EFBFBD><EFBFBD>??;E<EFBFBD>P1?%2P%68R%<EFBFBD><EFBFBD>l<EFBFBD>p<EFBFBD>b<EFBFBD> -0_%/<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?L<EFBFBD>C<EFBFBD>8\<EFBFBD>?L7LG<EFBFBD>tޚmPjnPOw<EFBFBD>\<EFBFBD><EFBFBD>ќ\<EFBFBD><EFBFBD>Oi<EFBFBD>s<EFBFBD>