: not an error *)
`Start_element "p"
`Start_element "em" (* recovery *)
`Text ["rocks!"]
`End_element (* *)
`End_element (* *)
`End_element (* *)
|> pretty_print (* adjusts the `Text signals *)
|> write_html
|> to_channel stdout;; "...shown above..." (* valid HTML *)
```
The parsers are [tested][tests] thoroughly.
For a higher-level parser, see [Lambda Soup][lambdasoup], which is based on
Markup.ml, but can search documents using CSS selectors, and perform various
manipulations.
## Overview and basic usage
The interface is centered around four functions between byte streams and signal
streams: [`parse_html`][parse_html], [`write_html`][write_html],
[`parse_xml`][parse_xml], and [`write_xml`][write_xml]. These have several
optional arguments for fine-tuning their behavior. The rest of the functions
either [input][input] or [output][output] byte streams, or
[transform][transform] signal streams in some interesting way.
Here is an example with an optional argument:
```ocaml
(* Show up to 10 XML well-formedness errors to the user. Stop after
the 10th, without reading more input. *)
let report =
let count = ref 0 in
fun location error ->
error |> Error.to_string ~location |> prerr_endline;
count := !count + 1;
if !count >= 10 then raise_notrace Exit
file "some.xml" |> fst |> parse_xml ~report |> signals |> drain
```
[input]: http://aantron.github.io/markup.ml/#2_Inputsources
[output]: http://aantron.github.io/markup.ml/#2_Outputdestinations
[transform]: http://aantron.github.io/markup.ml/#2_Utility
## Advanced: [Cohttp][cohttp] + Markup.ml + [Lambda Soup][lambdasoup] + [Lwt][lwt]
This program requests a Google search, then does a streaming scrape of result
titles. It exits when it finds a GitHub link, without reading more input. Only
one `h3` element is converted into an in-memory tree at a time.
```ocaml
let () =
Lwt_main.run begin
(* Send request. Assume success. *)
let url = "https://www.google.com/search?q=markup.ml" in
let%lwt _, body = Cohttp_lwt_unix.Client.get (Uri.of_string url) in
(* Adapt response to a Markup.ml stream. *)
let body = body |> Cohttp_lwt.Body.to_stream |> Markup_lwt.lwt_stream in
(* Set up a lazy stream of h3 elements. *)
let h3s = Markup.(body
|> strings_to_bytes |> parse_html |> signals
|> elements (fun (_ns, name) _attrs -> name = "h3"))
in
(* Find the GitHub link. .iter and .load cause actual reading of data. *)
h3s |> Markup_lwt.iter (fun h3 ->
let%lwt h3 = Markup_lwt.load h3 in
match Soup.(from_signals h3 $? "a[href*=github]") with
| None -> Lwt.return_unit
| Some anchor ->
print_endline (String.concat "" (Soup.texts anchor));
exit 0)
end
```
This prints
`GitHub - aantron/markup.ml: Error-recovering streaming HTML5 and ...`. To run
it, do:
```sh
ocamlfind opt -linkpkg -package lwt.ppx,cohttp.lwt,markup.lwt,lambdasoup \
scrape.ml && ./a.out
```
You can get all the necessary packages by
```
opam install lwt_ssl
opam install cohttp-lwt-unix lambdasoup markup
```
## Installing
```
opam install markup
```
## Documentation
The interface of Markup.ml is three modules: [`Markup`][Markup],
[`Markup_lwt`][Markup_lwt], and [`Markup_lwt_unix`][Markup_lwt_unix]. The last
two are available only if you have [Lwt][lwt] installed (OPAM package `lwt`).
The documentation includes a summary of the [conformance status][conformance] of
Markup.ml.
## Depending
Markup.ml uses [semantic versioning][semver], but is currently in `0.x.x`. The
minor version number will be incremented on breaking changes.
## Contributing
Contributions are very much welcome. Please see [`CONTRIBUTING`][contributing]
for instructions, suggestions, and an overview of the code. There is also a list
of [easy issues][easy].
## License
Markup.ml is distributed under the [MIT license][license]. The Markup.ml source
distribution includes a copy of the HTML5 entity list, which is distributed
under the [W3C document license][w3c-license].
[parse_html]: http://aantron.github.io/markup.ml/#VALparse_html
[write_html]: http://aantron.github.io/markup.ml/#VALwrite_html
[parse_xml]: http://aantron.github.io/markup.ml/#VALparse_xml
[write_xml]: http://aantron.github.io/markup.ml/#VALwrite_xml
[HTML5]: https://www.w3.org/TR/html5/
[XML]: https://www.w3.org/TR/xml/
[tests]: https://github.com/aantron/markup.ml/tree/master/test
[signal]: http://aantron.github.io/markup.ml/#TYPEsignal
[lwt]: https://github.com/ocsigen/lwt
[lambdasoup]: https://github.com/aantron/lambda-soup
[cohttp]: https://github.com/mirage/ocaml-cohttp
[license]: https://github.com/aantron/markup.ml/blob/master/LICENSE.md
[contributing]: https://github.com/aantron/markup.ml/blob/master/docs/CONTRIBUTING.md
[email]: mailto:antonbachin@yahoo.com
[Markup]: http://aantron.github.io/markup.ml
[Markup_lwt]: http://aantron.github.io/markup.ml/Markup_lwt.html
[Markup_lwt_unix]: http://aantron.github.io/markup.ml/Markup_lwt_unix.html
[conformance]: http://aantron.github.io/markup.ml/#2_Conformancestatus
[w3c-license]: https://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231
[semver]: http://semver.org/
[easy]: https://github.com/aantron/markup.ml/labels/easy
markup.ml-1.0.3/docs/ 0000775 0000000 0000000 00000000000 14213577064 0014362 5 ustar 00root root 0000000 0000000 markup.ml-1.0.3/docs/CONTRIBUTING.md 0000664 0000000 0000000 00000023320 14213577064 0016613 0 ustar 00root root 0000000 0000000 # Contributing to Markup.ml
#### Table of contents
- [Getting started](#getting-started)
- [Building and testing](#building)
- [Code overview](#code-overview)
- [Common concepts](#common-concepts)
- [Structure](#structure)
## Getting started
To get a development version of Markup.ml, do:
```
git clone https://github.com/aantron/markup.ml.git
cd markup.ml
opam install --deps-only .
```
## Building and testing
To test the code, run `make test`. To generate a coverage report, run `make
coverage`. There are several other kinds of testing:
- `make performance-test` measures time for Markup.ml to parse some XML and HTML
files. You should have `ocamlnet` and `xmlm` installed. Those libraries will
also be measured, for comparison.
- `make js-test` checks that `Markup_lwt` can be linked into a `js_of_ocaml`
program, i.e. that it is not accidentally pulling in any Unix dependencies.
- `make dependency-test` pins and installs Markup.ml using opam, then builds
some small programs that depend on Markup.ml. This tests correct installation
and that no dependencies are missing.
## Code overview
### Common concepts
The library is internally written entirely in continuation-passing style (CPS),
i.e., roughly speaking, *using callbacks*. Except for really trivial helpers,
most internal functions in Markup.ml take two continuations (callbacks): one to
call if the function succeeds, and one to call if it fails with an exception.
So, for a function `f` we would think of as taking as one `int` argument, and
returning a `string`, the type signature would look like this:
```ocaml
val f : int -> (exn -> unit) -> (string -> unit) -> unit
```
The code will call it on `1337` as `f 1337 throw k`. If `f` succeeds, say with
result `"foo"`, it will call `k "foo"`. If it fails, say with `Exit`, it will
call `throw Exit`.
The point of all this is that `f` doesn't have to return right away: it can,
perhaps transitively, trigger some I/O, and call `throw` or `k` only later,
when the I/O completes.
Due to pervasive use of CPS, there are two useful type aliases defined in
[`Markup.Common`][common]:
```ocaml
type 'a cont = 'a -> unit
type 'a cps = exn cont -> 'a cont -> unit
```
With these aliases, the signature of `f` can be abbreviated as:
```ocaml
val f : int -> string cps
```
which is much more legible.
The other important internal type in Markup.ml is the continuation-passing style
stream, or *kstream* (`k` being the traditional meta-variable for a
continuation). The fundamental operation on a stream is getting the next
element, and for kstreams this looks like:
```ocaml
Kstream.next : 'a Kstream.t -> exn cont -> unit cont -> 'a cont -> unit
```
When you call `next kstream on_exn on_empty k`, `next` eventually calls:
- `on_exn exn` if trying to retrieve the next element resulted in exception
`exn`,
- `on_empty ()` if the stream ended, or
- `k v` in the remaining case, when the stream has a next value `v`.
Each of the parsers and serializers in Markup.ml is a chain of stream
processors, tied together by these kstreams. For example, the HTML and XML parsers both...
- take a stream of bytes,
- transform it into a stream of Unicode characters paired with locations,
- transform that into a stream of language tokens, like "start tag,"
- and transform that into a stream of parsing signals, like "start element."
The synchronous default API of Markup.ml, seen in the [`README`][readme], is a
thin wrapper over this internal implementation. What makes it synchronous is
that the underlying I/O functions guarantee that each call to a CPS function `f`
will call one of its continuations (callbacks) *before* `f` returns.
Likewise, the Lwt API is another thin wrapper, which translates between CPS and
Lwt promises. What makes this API asynchronous is that underlying I/O functions
might not call their continuations until long after the functions have returned,
and this delay is propagated to the continuations nearest to the surface API.
[readme]: https://github.com/aantron/markup.ml#readme
### Structure
As for how the stream processors are chained together, The HTML specification
strongly suggests a structure for the parser in the section
[*8.2.1 Overview of the parsing model*][model], from where the following diagram
is taken:
[model]: https://www.w3.org/TR/html5/syntax.html#overview-of-the-parsing-model
The XML parser follows the same structure, even though it is not explicitly
suggested by the XML specification.
The modules can be arranged in the following categories. Where a module directly
implements a box from the diagram, the box name is indicated in boldface.
Until the modules dealing with Lwt, only `Markup.Stream_io` does I/O. The rest
of the modules are pure with respect to I/O.
Almost everything is based directly on specifications. Most functions are
commented with the HTML or XML specification section number they are
implementing. It may also be useful to see the [conformance status][conformance]
– these are all the known deviations by Markup.ml from the specifications.
#### Helpers
- [`Markup.Common`][common] – shared definitions, compiler compatibility, etc.
- [`Markup.Error`][error] – parsing and serialization error type. Markup.ml does
not throw exceptions, because all errors are recoverable.
- [`Markup.Namespace`][namespace] – namespace URI to prefix conversion and back.
- [`Markup.Entities`][entities] – checked-in auto-generated HTML5 entity list.
The source for this file is `src/entities.json`, and the generator is
`src/translate_entities.ml`. Neither of these latter two files is part of the
built Markup.ml, nor of the build process.
- [`Markup.Trie`][trie] – trie for incrementally searching the entity list.
- [`Markup.Kstream`][kstream] – above-mentioned CPS streams.
- [`Markup.Text`][text] – some utilities for `Markup.Html_tokenizer` and
`Markup.Xml_tokenizer`; see below.
#### I/O
- [`Markup.Stream_io`][stream_io] – make byte streams from files, strings, etc.,
write byte streams to strings, etc. – the first stage of parsing and the last
stage of serialization (**Network** in the diagram). This uses the I/O
functions in `Pervasives`.
#### Encodings
- [`Markup.Encoding`][encoding] – byte streams to Unicode character streams
(**Byte Stream Decoder** in the diagram). For UTF-8, this is a wrapper around
`uutf`.
- [`Markup.Detect`][detect] – prescans byte streams to detect encodings.
- [`Markup.Input`][input] – Unicode streams to "preprocessed" Unicode streams –
in HTML5 parlance, this just means normalizing CR-LF to CR, and attaching
locations (**Input Stream Preprocessor** in the diagram).
#### HTML parsing
- [`Markup.Html_tokenizer`][html_tokenizer] – preprocessed Unicode streams to
HTML lexeme streams (**Tokenizer** in the diagram). HTML lexemes are things
like start tags, end tags, and runs of text.
- [`Markup.Html_parser`][html_parser] – HTML lexeme streams to HTML signal
streams (**Tree Construction** in the diagram). Signal streams are things like
"start an element," "start another element as its child," "now end the child,"
"now end the root element." They are basically a left-to-right traversal of a
DOM tree, without the DOM tree actually being in memory.
#### XML parsing
- [`Markup.Xml_tokenizer`][xml_tokenizer] – as for HTML above, but for XML.
- [`Markup.Xml_parser`][xml_parser] - as for HTML above, but for XML.
#### HTML writing
- [`Markup.Html_writer`][html_writer] – HTML signal streams back to
UTF-8-encoded byte streams.
#### XML writing
- [`Markup.Xml_writer`][xml_writer] - as for HTML above, but for XML.
#### User-friendly APIs
- [`Markup.Utility`][utility] – convenience functions on signal streams for the
user.
- [`Markup`][main], [`Markup_lwt`][lwt], [`Markup_lwt_unix`][lwt_unix] – the
public interface for operating all of the above machinery without having to
touch CPS.
[common]: https://github.com/aantron/markup.ml/blob/master/src/common.ml
[error]: https://github.com/aantron/markup.ml/blob/master/src/error.ml
[namespace]: https://github.com/aantron/markup.ml/blob/master/src/namespace.mli
[entities]: https://github.com/aantron/markup.ml/blob/master/src/entities.ml
[trie]: https://github.com/aantron/markup.ml/blob/master/src/trie.ml
[kstream]: https://github.com/aantron/markup.ml/blob/master/src/kstream.mli
[stream_io]: https://github.com/aantron/markup.ml/blob/master/src/stream_io.ml
[encoding]: https://github.com/aantron/markup.ml/blob/master/src/encoding.ml
[input]: https://github.com/aantron/markup.ml/blob/master/src/input.mli
[html_tokenizer]: https://github.com/aantron/markup.ml/blob/master/src/html_tokenizer.mli
[html_parser]: https://github.com/aantron/markup.ml/blob/master/src/html_parser.mli
[html_writer]: https://github.com/aantron/markup.ml/blob/master/src/html_writer.mli
[xml_tokenizer]: https://github.com/aantron/markup.ml/blob/master/src/xml_tokenizer.mli
[xml_parser]: https://github.com/aantron/markup.ml/blob/master/src/xml_parser.mli
[xml_writer]: https://github.com/aantron/markup.ml/blob/master/src/xml_writer.mli
[text]: https://github.com/aantron/markup.ml/blob/master/src/text.ml
[detect]: https://github.com/aantron/markup.ml/blob/master/src/detect.mli
[utility]: https://github.com/aantron/markup.ml/blob/master/src/utility.ml
[main]: https://github.com/aantron/markup.ml/blob/master/src/markup.mli
[lwt]: https://github.com/aantron/markup.ml/blob/master/src/markup_lwt.mli
[lwt_unix]: https://github.com/aantron/markup.ml/blob/master/src/markup_lwt_unix.mli
[conformance]: http://aantron.github.io/markup.ml/#2_Conformancestatus
markup.ml-1.0.3/docs/footer.html 0000664 0000000 0000000 00000000506 14213577064 0016547 0 ustar 00root root 0000000 0000000
markup.ml-1.0.3/docs/header.html 0000664 0000000 0000000 00000000521 14213577064 0016476 0 ustar 00root root 0000000 0000000
markup.ml-1.0.3/docs/postprocess.ml 0000664 0000000 0000000 00000022630 14213577064 0017303 0 ustar 00root root 0000000 0000000 (* This file is part of Markup.ml, released under the MIT license. See
LICENSE.md for details, or visit https://github.com/aantron/markup.ml. *)
open Soup
let (|>) x f = f x
type transform =
| Rename of string
| TableOfContents
| UpTo of string
| Class of string
| WithType of string
| Meta of (string * string option)
let transforms =
["Markup.html",
[Rename "index.html"; TableOfContents; Class "index";
Meta ("Markup.ml - Error-recovering HTML and XML parsers for OCaml",
Some ("Streaming, error-recovering, standards-based HTML(5) and " ^
"XML parsers with an interface designed for ease of use."))];
"Markup.Error.html",
[UpTo "index.html"; Meta ("Error - Markup.ml", None)];
"Markup.Encoding.html",
[UpTo "index.html"; Meta ("Encoding - Markup.ml", None)];
"Markup.Ns.html",
[UpTo "index.html"; Meta ("Ns - Markup.ml", None)];
"Markup.ASYNCHRONOUS.html",
[UpTo "index.html"; Class "asynchronous";
Meta ("ASYNCHRONOUS - Markup.ml", None)];
"Markup_lwt.html",
[UpTo "index.html"; Class "asynchronous"; WithType "Lwt";
Meta ("Markup_lwt - Markup.ml", None)];
"Markup_lwt_unix.html",
[UpTo "index.html"; Meta ("Markup_lwt_unix - Markup.ml", None)];
"Markup.ASYNCHRONOUS.Encoding.html",
[UpTo "Markup.ASYNCHRONOUS.html";
Meta ("ASYNCHRONOUS.Encoding - Markup.ml", None)]]
let rec find_map f = function
| [] -> None
| x::l ->
match f x with
| None -> find_map f l
| Some _ as v -> v
let lookup f file =
try transforms |> List.assoc file |> find_map f
with Not_found -> None
let should_rename file =
lookup (function
| Rename name -> Some name
| _ -> None)
file
let new_name file =
match should_rename file with
| None -> file
| Some name -> name
let should_make_toc file =
try
transforms
|> List.assoc file
|> List.mem TableOfContents
with Not_found -> false
let should_make_up file =
lookup (function
| UpTo name -> Some name
| _ -> None)
file
let should_add_class file =
lookup (function
| Class name -> Some name
| _ -> None)
file
let should_add_with_type file =
lookup (function
| WithType name -> Some name
| _ -> None)
file
let html_directory = "doc/html"
let read_output_file name = Filename.concat html_directory name |> read_file
let write_output_file name text =
write_file (Filename.concat html_directory name) text
let read_fragment name = Filename.concat "doc" name |> read_file
let clean_up_head soup name =
soup $$ "head link:not([rel=stylesheet])" |> iter delete;
let address = "http://aantron.github.io/markup.ml" in
let canonical =
match new_name name with
| "index.html" -> address
| name -> address ^ "/" ^ name
in
let title, description =
let result =
lookup (function
| Meta v -> Some v
| _ -> None)
name
in
match result with
| None -> failwith ("no metadata for " ^ name)
| Some v -> v
in
let meta_content =
"" ^ title ^ "\n\n" ^ "\n" ^
""
in
let meta_content =
match description with
| None -> meta_content
| Some text ->
meta_content ^ "\n"
in
soup $ "title" |> delete;
meta_content |> parse |> children |> iter (append_child (soup $ "head"))
let clean_up_header soup =
soup $ ".navbar" |> delete;
soup $ "hr" |> delete;
read_fragment "header.html" |> parse |> replace (soup $ "h1");
read_fragment "footer.html" |> parse |> append_child (soup $ "body")
let clean_up_content soup =
soup $$ "body > br" |> iter delete;
soup $$ "a:contains(\"..\")" |> iter unwrap;
begin match soup $? "table.indextable" with
| None -> ()
| Some table ->
table |> R.previous_element |> delete
end;
soup $$ "a[href]" |> iter (fun a ->
let link = R.attribute "href" a in
let prefix = "Markup.html" in
if String.length link >= String.length prefix &&
String.sub link 0 (String.length prefix) = prefix then
let suffix =
String.sub link (String.length prefix)
(String.length link - String.length prefix)
in
set_attribute "href" ("index.html" ^ suffix) a);
soup $$ "a:not(.protect):contains(\"Markup.\")" |> iter (fun a ->
match a $? ".constructor" with
| None ->
let text = R.leaf_text a in
let prefix = "Markup." in
let text =
String.sub text (String.length prefix)
(String.length text - String.length prefix)
in
clear a;
create_text text |> append_child a
| Some element ->
delete element;
let inner_html =
a $ ".code" |> children |> fold (fun s n -> s ^ (to_string n)) "" in
let inner_html = String.sub inner_html 1 (String.length inner_html - 1) in
a $ ".code" |> clear;
inner_html |> parse |> children |> iter (append_child (a $ ".code")));
soup $$ "pre"
|> filter (fun e -> e $? ".type" <> None)
|> filter (fun e -> e $? "br" <> None)
|> filter (fun e -> e $? "+ .info" <> None)
|> iter (fun e -> e $ "+ .info" |> add_class "multiline-member");
let rec reassemble_lists () =
match soup $? "ul + ul" with
| None -> ()
| Some ul ->
let ul = R.previous_element ul in
let rec consume () =
match ul $? "+ ul" with
| None -> ()
| Some ul' ->
R.child_element ul' |> append_child ul;
delete ul';
consume ()
in
consume ();
reassemble_lists ()
in
reassemble_lists ();
soup $$ "ul" |> iter (fun ul -> ul |> R.previous_element |> delete);
soup $$ "pre > .type"
|> filter (fun e -> e $? "br" <> None)
|> iter (fun e ->
create_text " " |> prepend_child e;
create_element "br" |> prepend_child e);
let uncolor class_ content =
soup $$ ("span." ^ class_)
|> filter at_most_one_child
|> filter (fun e -> leaf_text e = Some content)
|> iter unwrap
in
uncolor "constructor" "Error";
uncolor "constructor" "Encoding";
uncolor "constructor" "Markup";
uncolor "constructor" "Markup_lwt";
uncolor "constructor" "Markup_lwt_unix";
uncolor "constructor" "Markup_async";
uncolor "constructor" "ASYNCHRONOUS";
uncolor "constructor" "Pervasives";
uncolor "constructor" "Lwt_io";
uncolor "keyword" "false";
uncolor "keyword" "parser";
soup $$ "span[id]" |> iter (fun span ->
set_name "a" span;
set_attribute "href" ("#" ^ (R.attribute "id" span)) span);
soup $$ "h2[id]" |> iter (fun h2 ->
let href = "#" ^ (R.attribute "id" h2) in
let a =
create_element
~attributes:["href", href] ~inner_text:(R.leaf_text h2) "a";
in
clear h2;
append_child h2 a)
let add_with_type soup type_name =
let extra =
" with type 'a io = 'a " ^
"" ^ type_name ^ ".t"
in
parse extra |> children
|> iter (append_child (soup $ "pre:contains(\"ASYNCHRONOUS\")"))
let add_table_of_contents soup =
let sections =
soup $$ "h2"
|> to_list
|> List.map (fun h2 -> R.id h2, R.leaf_text h2)
in
let toc = create_element ~class_:"toc" "div" in
create_element ~inner_text:"Module contents" "p" |> append_child toc;
let links = create_element ~class_:"links" "div" in
append_child toc links;
("", "[Top]")::sections |> List.iter (fun (id, title) ->
create_element ~attributes:["href", "#" ^ id] ~inner_text:title "a"
|> append_child links;
create_element "br" |> append_child links);
create_element "br" |> insert_after (toc $ "a");
create_element "br" |> append_child toc;
create_element "br" |> append_child toc;
create_element
~attributes:["href", "https://github.com/aantron/markup.ml"]
~classes:["github"; "hide-narrow"] ~inner_text:"GitHub"
"a"
|> append_child toc;
toc $ "a" |> set_attribute "class" "hide-narrow";
append_child (soup $ ".info") toc
let add_up_link soup to_ =
let toc =
match soup $? ".toc" with
| Some element -> element
| None ->
let toc = create_element ~class_:"toc" "div" in
append_child (soup $ ".info") toc;
toc
in
let container = create_element ~class_:"hide-narrow" "div" in
create_element ~inner_text:"[Up]" ~attributes:["href", to_] "a"
|> append_child container;
create_element "br" |> append_child container;
create_element "br" |> append_child container;
container |> prepend_child toc
let () =
html_directory
|> Sys.readdir
|> Array.to_list
|> List.filter (fun f -> Filename.check_suffix f ".html")
|> List.iter begin fun file ->
let soup = file |> read_output_file |> parse in
clean_up_head soup file;
clean_up_header soup;
clean_up_content soup;
begin match should_add_with_type file with
| None -> ()
| Some type_name -> add_with_type soup type_name
end;
if should_make_toc file then
add_table_of_contents soup;
begin match should_make_up file with
| None -> ()
| Some target -> add_up_link soup target
end;
begin match should_add_class file with
| None -> ()
| Some class_ -> soup $ "body" |> add_class class_
end;
begin match should_rename file with
| None -> soup |> to_string |> write_output_file file
| Some name ->
Sys.remove (Filename.concat html_directory file);
soup |> to_string |> write_output_file name
end
end
markup.ml-1.0.3/docs/sample.png 0000664 0000000 0000000 00000246776 14213577064 0016377 0 ustar 00root root 0000000 0000000 PNG
IHDR = ( Z) 'iCCPICC Profile XYTU[^{ġKn* "()%&E@E"DDQ1wo\s5Ws `
i%'poL8Zc|lP+ `}ȱ `GP@Bl&bF2J ancM쳍etQl ۛ i?oo GG(
#<lApLTws8h
"m曘
]>V(G`-M<gK7F3
Q%ylQ}*8!GA|DYUF(Fg|%9y'|#>
$Ąڛo[gY_Fv:kx~!R[XQ`mǸXo`pAg/0_H8#M1mb =t6[T67X }` xAZ}@$m#
_[-$(1ZA<*_#~J-PvFc>u*QV! g3ዲC+OߖQ4!vh7FxwK0j=[#Va4Q(w3HbОbѾ)26$q/>ioM$F!Hx^B7jk&~POoyv_[B?2*˫R;xdd ߷/v[6|oY, e^ hA4em 0p7-l>Р+
p I(uxQ l#TZp\ ͠
tk#!xY,o` C?`a.XU`]vwp4gp\[k K@(fDT}qC2CJ"҉>2, 18#O#ك9)ǜŴbn`c0Xj,'V5ź` ll){]Qo8'SFצ+.;;kFq3%<φkX|6<?ů( <9A zc9*(DT#ZIB)b'qJAG!BI@BNQFq&3/ieMw((SSSS*zBZZڍ::IdJ#%UZIc4D!]dR4hh´{h+h;h.1YӅ;G7@O/LoHGI_K~a`gep&,#Qє110"=S"SS7$3,l\