Skip to content

Commit

Permalink
Cookbook XML\n\nRecreate PR #2121 from https://github.com/ocaml/ocaml…
Browse files Browse the repository at this point in the history
  • Loading branch information
Cuihtlauac ALVARADO committed May 13, 2024
1 parent 2f496c7 commit caebf2b
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
6 changes: 6 additions & 0 deletions data/cookbook/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ categories:
slug: deserialise-from-yaml
- title: Deserialise and Post-Process YAML Data
slug: deserialise-post-process-from-yaml
- title: XML
tasks:
- title: Parse XML
slug: xml-parse
- title: Generate XML
slug: xml-generate
- title: Date and Time
tasks:
- title: Get Today's Date
Expand Down
39 changes: 39 additions & 0 deletions data/cookbook/xml-generate/00-ezxmlm.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
packages:
- name: "ezxmlm"
tested_version: "1.1.0"
used_libraries:
- ezxmlm
discussion: |
- **Understanding `ezxmlm`:** The `ezxmlm` package provides high level functions that can be used to parse and generate an XML file. This package is based on `xmlm` which which works with element start/end callbacks.
- **Alternatives:** TODO
---

(* Lets take a simple structure: a pair list representing an ID and an associated text *)

let item_list = [ ("1", "Text 1"); ("2", "Text 2")]

(* We will convert each item into an XML node. Each XML node must have one of the form:
- \`Data text
- \`El ((("","tag"), attribute_list), node_list)
The attribute_list must have the form [("", "name"), "value"); ...]
Then we can type:
*)

let item_nodes = item_list |> List.map (fun (id, text) ->
`El ((("","item"), [("","id"), id]), [ `Data text ]))

(* We can enclose these items in a list element *)

let list_node = `El ((("","list"), []), item_nodes)

(* Then create and export the XML tree, in a string or in a file *)

let xml_string = Ezxmlm.to_string [list_node]

let xml_header = {xml|<?xml version="1.0" encoding="utf-8"?>|xml}
let () =
Out_channel.with_open_bin "file.xml"
(fun oc -> Ezxmlm.to_channel oc (Some xml_header) [list_node])
81 changes: 81 additions & 0 deletions data/cookbook/xml-parse/00-ezxmlm.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
packages:
- name: "ezxmlm"
tested_version: "1.1.0"
used_libraries:
- ezxmlm
discussion: |
- **Understanding `ezxmlm`:** The `ezxmlm` package provides high level functions that can be used to parse and generate an XML file. This package is based on `xmlm` which which works with element start/end callbacks.
- **Alternatives:** TODO
---

(* Lets take a simple XML string. *)

let xml_string =
{xml|<?xml version="1.0" encoding="utf-8"?>
<root>
<list>
<item id="1" class="a">text 1</item>
<item id="2" class="a">text 2</item>
<item id="3" class="b">text 3</item>
</list>
</root>|xml}

(* Let's parse it: *)

let (_dtd, xml_node) = Ezxmlm.from_string xml_string

(* Or if the XML is in a file.xml *)

let (_dtd, xml_node) = In_channel.with_open_bin
"file.xml" Ezxmlm.from_channel

(* Fetching a member by its tag, assuming it is unique, can be done by the `member` function. (Note: an exception is raised in UTop by the pretty printer; however, these values are correct) *)

let root_node = Ezxmlm.member "root" xml_node
let list_node = Ezxmlm.member "list" root_node

(* Fetching all members by their tags returning a list of nodes can be done by the `members` function *)

let item_node_list = Ezxmlm.members "item" list_node

(* We can then fetch the enclosed text *)

let data_list = List.map Ezxmlm.data_to_string item_node_list

(* If we want to filter with the attributes, we have to deal with a list of pairs (attribute list, nodes) which are returned by `members_with_attr`. *)

let item_pair_list = Ezxmlm.members_with_attr "item" list_node
let item_pair = Ezxmlm.filter_attr "id" "2" item_pair_list

(* We can get its attribute values or use the node with other functions. *)

let class_ = Ezxmlm.get_attr "class" (fst item_pair)
let data = Ezxmlm.data_to_string (snd item_pair)

(* If we expect the attribute selection not to be unique, the `filter_attrs` should be used instead of `filter_attr`. *)

let item_pair_list' = Ezxmlm.filter_attrs "class" "a" item_pair_list
let data_list = List.map
(fun item_pair -> Ezxmlm.data_to_string (snd item_pair))
item_pair_list'

(* A whole example: an RSS parser that returns a list of pair (title, link) from an RSS string *)

let parse_rss str =
let open Ezxmlm in
let (_dtd,xml) = from_string str in
xml
|> member "rss"
|> member "channel"
|> members "item"
|> List.map begin fun item ->
let title = (item
|> member "title"
|> data_to_string)
and link = (item
|> member "link"
|> data_to_string)
in
(title, link)
end

0 comments on commit caebf2b

Please sign in to comment.