├── pkg └── pkg.ml ├── .gitignore ├── lib ├── jbuild ├── META ├── ezxmlm.ml └── ezxmlm.mli ├── .travis.yml ├── CHANGES.md ├── ezxmlm.opam ├── LICENSE.md ├── Makefile └── README.md /pkg/pkg.ml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ocaml 2 | #use "topfind" 3 | #require "topkg-jbuilder.auto" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | _opam/ 3 | _build/ 4 | dist/ 5 | *.docdir 6 | *.native 7 | *.byte 8 | *.merlin 9 | *.install 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /lib/jbuild: -------------------------------------------------------------------------------- 1 | (library 2 | ((name ezxmlm) 3 | (public_name ezxmlm) 4 | (libraries (xmlm)) 5 | (modules (Ezxmlm)) 6 | (wrapped false) 7 | )) 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | services: 4 | - docker 5 | install: wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh 6 | script: bash -ex ./.travis-docker.sh 7 | env: 8 | global: 9 | - PACKAGE="ezxmlm" 10 | matrix: 11 | - DISTRO="alpine" OCAML_VERSION="4.02.3" 12 | - DISTRO="alpine" OCAML_VERSION="4.06.0" 13 | -------------------------------------------------------------------------------- /lib/META: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: 3dca1445be4ab7b085565416c9d58998) 3 | version = "1.0.1" 4 | description = "A tree-based interface on top of XMLM" 5 | requires = "xmlm" 6 | archive(byte) = "ezxmlm.cma" 7 | archive(byte, plugin) = "ezxmlm.cma" 8 | archive(native) = "ezxmlm.cmxa" 9 | archive(native, plugin) = "ezxmlm.cmxs" 10 | exists_if = "ezxmlm.cma" 11 | # OASIS_STOP 12 | 13 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## v1.0.2 (04/01/2018): 2 | 3 | * Build with jbuilder and release with topkg 4 | (this also fixes an OCaml 4.06 -safe-string incompatibility with 5 | the oasis-generated code) 6 | * Fixes to the ocamldoc markup to be more compliant with odoc. 7 | 8 | ## v1.0.1 (03-06-2014): 9 | 10 | * Add `has_member` function to test if a tag is present in sub-nodes. 11 | * Add Travis CI scripts. 12 | * Regenerate OASIS build files with 0.4.4 (better dynlink support) 13 | 14 | ## v1.0.0 (02-11-2014): 15 | 16 | * First public release. 17 | -------------------------------------------------------------------------------- /ezxmlm.opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | maintainer: "anil@recoil.org" 3 | authors: [ "Anil Madhavapeddy" ] 4 | license: "ISC" 5 | 6 | build: [ 7 | [ "jbuilder" "subst"] {pinned} 8 | [ "jbuilder" "build" "-p" name "-j" jobs ] 9 | ] 10 | 11 | depends: [ 12 | "jbuilder" {build & >= "1.0+beta9"} 13 | "xmlm" {>= "1.1.0"} 14 | ] 15 | tags: [ "org:mirage" "org:ocamllabs" ] 16 | dev-repo: "https://github.com/avsm/ezxmlm.git" 17 | homepage: "https://github.com/avsm/ezxmlm" 18 | bug-reports: "https://github.com/avsm/ezxmlm/issues" 19 | available: ocaml-version >= "4.02.3" 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | *) 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean test 2 | 3 | build: 4 | jbuilder build @install --dev 5 | 6 | test: 7 | jbuilder runtest 8 | 9 | install: 10 | jbuilder install 11 | 12 | uninstall: 13 | jbuilder uninstall 14 | 15 | clean: 16 | jbuilder clean 17 | 18 | doc: 19 | jbuilder build @doc 20 | 21 | publish-doc: doc 22 | rm -rf .gh-pages 23 | git clone `git config --get remote.origin.url` .gh-pages --reference . 24 | git -C .gh-pages checkout --orphan gh-pages 25 | git -C .gh-pages reset 26 | git -C .gh-pages clean -dxf 27 | cp -r _build/default/_doc/* .gh-pages/ 28 | git -C .gh-pages add . 29 | git -C .gh-pages commit -m "Update Pages" 30 | git -C .gh-pages push origin gh-pages -f 31 | rm -rf .gh-pages 32 | 33 | test-all: 34 | sh ./.docker-run.sh 35 | 36 | REPO=../../mirage/opam-repository 37 | PACKAGES=$(REPO)/packages 38 | # until we have https://github.com/ocaml/opam-publish/issues/38 39 | pkg-%: 40 | topkg opam pkg -n $* 41 | mkdir -p $(PACKAGES)/$* 42 | cp -r _build/$*.* $(PACKAGES)/$*/ 43 | cd $(PACKAGES) && git add $* 44 | 45 | PKGS=$(basename $(wildcard *.opam)) 46 | opam-pkg: 47 | $(MAKE) $(PKGS:%=pkg-%) 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ezxmlm -- combinators to use with xmlm for parsing and selection 2 | 3 | An "easy" interface on top of the Xmlm [1] library. This version provides more 4 | convenient (but far less flexible) input and output functions that go to and 5 | from [string] values. This avoids the need to write signal code, which is 6 | useful for quick scripts that manipulate XML. 7 | 8 | More advanced users should go straight to the Xmlm library and use it directly, 9 | rather than be saddled with the Ezxmlm interface. Since the types in this 10 | library are more specific than Xmlm, it should interoperate just fine with it 11 | if you decide to switch over. 12 | 13 | * Online docs: 14 | * Source Code: 15 | * Discussion: in the Ecosystem category 16 | * Bugs: 17 | 18 | # Example 19 | 20 | In the toplevel, here's an example of how some XHTML can be selected out 21 | quickly using the Ezxmlm combinators. Note that this particular HTML has 22 | been post-processed into valid XML using `xmllint --html --xmlout`. 23 | 24 | ``` 25 | # #require "ezxmlm" ;; 26 | # open Ezxmlm ;; 27 | # let (_,xml) = from_channel (open_in "html/variants.html") ;; 28 | # member "html" xml |> member "head" |> member_with_attr "meta" ;; 29 | - : Xmlm.attribute list * nodes = ([(("", "name"), "generator"); (("", "content"), "DocBook XSL Stylesheets V1.78.1")], []) 30 | # member "html" xml |> member "head" |> member "title" |> data_to_string;; 31 | - : string = "Chapter 6. Variants" 32 | ``` 33 | 34 | Ez peezy lemon squeezy! 35 | 36 | [1] https://github.com/dbuenzli/xmlm 37 | -------------------------------------------------------------------------------- /lib/ezxmlm.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2013 Anil Madhavapeddy 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | *) 17 | 18 | type node = ('a Xmlm.frag as 'a) Xmlm.frag 19 | type nodes = node list 20 | 21 | let from_input i = 22 | try 23 | let el tag children = `El (tag, children) in 24 | let data d = `Data d in 25 | Xmlm.input_doc_tree ~el ~data i 26 | with Xmlm.Error((line,col),err) -> 27 | let err = Xmlm.error_message err in 28 | raise (Failure (Printf.sprintf "[line %d, col %d] %s" line col err)) 29 | 30 | let from_channel chan = 31 | let i = Xmlm.make_input (`Channel chan) in 32 | let (dtd,doc) = from_input i in 33 | (dtd, [doc]) 34 | 35 | let from_string buf = 36 | let i = Xmlm.make_input (`String (0,buf)) in 37 | let (dtd,doc) = from_input i in 38 | (dtd, [doc]) 39 | 40 | let to_output o t = 41 | let frag = function 42 | | `El (tag, childs) -> `El (tag, childs) 43 | | `Data d -> `Data d in 44 | Xmlm.output_doc_tree frag o t 45 | 46 | let write_document mode dtd doc = 47 | let o = Xmlm.make_output ~decl:false mode in 48 | match doc with 49 | | [] -> () 50 | | hd::tl -> 51 | to_output o (dtd, hd); 52 | List.iter (fun t -> to_output o (None, t)) tl 53 | 54 | let to_channel chan dtd doc = 55 | write_document (`Channel chan) dtd doc 56 | 57 | let to_string ?dtd doc = 58 | let buf = Buffer.create 512 in 59 | write_document (`Buffer buf) dtd doc; 60 | Buffer.contents buf 61 | 62 | let make_tag tag (attrs,nodes) : node = 63 | `El ((("",tag),attrs),nodes) 64 | 65 | let mem_attr k v attrs = 66 | try List.assoc ("",k) attrs = v 67 | with Not_found -> false 68 | 69 | let get_attr k attrs = 70 | List.assoc ("",k) attrs 71 | 72 | let rec filter_map ~tag ~f i = 73 | List.concat ( 74 | List.map ( 75 | function 76 | | `El (((_,t),attr),c) when t=tag -> f attr c 77 | | `El (p,c) -> [`El (p, (filter_map ~tag ~f c))] 78 | | `Data x -> [`Data x] 79 | ) i 80 | ) 81 | 82 | let rec filter_iter ~tag ~f i = 83 | List.iter ( 84 | function 85 | | `El (((_,t),attr),c) when t=tag -> f attr c 86 | | `El (_,c) -> filter_iter ~tag ~f c 87 | | `Data _ -> () 88 | ) i 89 | 90 | let filter_attrs attr value (al:(Xmlm.attribute list * nodes) list) = 91 | List.filter (fun (attrs, _nodes) -> 92 | try List.assoc ("",attr) attrs = value 93 | with Not_found -> false 94 | ) al 95 | 96 | let filter_attr attr value al = 97 | match filter_attrs attr value al with 98 | | [] -> raise Not_found 99 | | hd :: _ -> hd 100 | 101 | let hd nodes = 102 | List.hd nodes 103 | 104 | let tl = 105 | function 106 | | [] -> [] 107 | | _::tl -> tl 108 | 109 | exception Tag_not_found of string 110 | 111 | let members_with_attr tag nodes = 112 | let r = List.fold_left (fun a b -> 113 | match b with 114 | | `El (((_,t),attr),c) when t=tag -> (attr,c) :: a 115 | | _ -> a 116 | ) [] nodes in 117 | List.rev r 118 | 119 | let member_with_attr tag nodes = 120 | match members_with_attr tag nodes with 121 | | [] -> raise (Tag_not_found tag) 122 | | hd::_ -> hd 123 | 124 | let has_member tag nodes = 125 | match members_with_attr tag nodes with 126 | | [] -> false 127 | | _hd::_ -> true 128 | 129 | let members tag nodes = 130 | List.map snd (members_with_attr tag nodes) 131 | 132 | let member tag nodes = 133 | snd (member_with_attr tag nodes) 134 | 135 | let pick_tags tag cl v nodes = 136 | members_with_attr tag nodes 137 | |> filter_attrs cl v 138 | |> List.map (make_tag tag) 139 | 140 | let pick_tag tag cl v nodes = 141 | members_with_attr tag nodes 142 | |> filter_attr cl v 143 | |> make_tag tag 144 | 145 | let data_to_string nodes = 146 | let buf = Buffer.create 512 in 147 | List.iter (function 148 | | `Data x -> Buffer.add_string buf x 149 | | _ -> () 150 | ) nodes; 151 | Buffer.contents buf 152 | -------------------------------------------------------------------------------- /lib/ezxmlm.mli: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2013 Anil Madhavapeddy 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | *) 17 | 18 | (** An "easy" interface on top of the [Xmlm] library. 19 | 20 | This version provides more convenient (but less flexible) input and output 21 | functions that go to and from {!string} values. This avoids the need to write signal code, 22 | which is useful for quick scripts that manipulate XML. 23 | 24 | More advanced users should go straight to the [Xmlm] library and use it 25 | directly, rather than be saddled with the Ezxmlm interface below. 26 | *) 27 | 28 | (** {2 Basic types } *) 29 | 30 | (** The type of a single XML tag *) 31 | type node = ('a Xmlm.frag as 'a) Xmlm.frag 32 | 33 | (** The type of a list of XML tags *) 34 | type nodes = node list 35 | 36 | (** Raised by the query combinators *) 37 | exception Tag_not_found of string 38 | 39 | (** {2 Reading XML values } *) 40 | 41 | (** Read an XML document from an [in_channel] *) 42 | val from_channel : in_channel -> Xmlm.dtd * nodes 43 | 44 | (** Read an XML document directly from a [string] *) 45 | val from_string : string -> Xmlm.dtd * nodes 46 | 47 | (** Low-level function to read directly from an [Xmlm] input source *) 48 | val from_input : Xmlm.input -> Xmlm.dtd * node 49 | 50 | (** {2 Writing XML values } *) 51 | 52 | (** Write an XML document to an [out_channel] *) 53 | val to_channel : out_channel -> Xmlm.dtd -> nodes -> unit 54 | 55 | (** Write an XML document to a [string]. This goes via an intermediate 56 | [Buffer] and so may be slow on large documents. *) 57 | val to_string : ?dtd:string -> nodes -> string 58 | 59 | (** Low-level function to write directly to an [Xmlm] output source *) 60 | val to_output : Xmlm.output -> Xmlm.dtd * node -> unit 61 | 62 | (** {2 Attribute handling} *) 63 | 64 | (** Given some selected attributes and nodes (usually from [members_with_attr]) 65 | return the ones that match the [class] and [value] supplied. *) 66 | val filter_attrs : string -> string -> 67 | (Xmlm.attribute list * nodes) list -> (Xmlm.attribute list * nodes) list 68 | 69 | (** Given some selected attributes and nodes (usually from [members_with_attr]) 70 | return the first that matches the [class] and [value] supplied. 71 | Raises [Not_found] if nothing matches. *) 72 | val filter_attr : string -> string -> 73 | (Xmlm.attribute list * nodes) list -> (Xmlm.attribute list * nodes) 74 | 75 | (** [mem_attr name value attrs] returns true if the [name] key is 76 | with value [value] is present in the [attrs] attribute list. *) 77 | val mem_attr : string -> string -> Xmlm.attribute list -> bool 78 | 79 | (** [get_attr name attrs] returns the value associated with key 80 | [name] in the [attrs] attribute list. 81 | Raised [Not_found] if the attribute is not present. *) 82 | val get_attr : string -> Xmlm.attribute list -> string 83 | 84 | (** {2 Selectors and utility functions } *) 85 | 86 | (** [pick_tags tag attr value] selects all the child nodes that 87 | match the [tag] name and contain an attribute with name [tag] 88 | and [value]. *) 89 | val pick_tags : string -> string -> string -> nodes -> nodes 90 | 91 | (** [pick_tag tag attr value] selects the first child node that 92 | matches the [tag] name and contain an attribute with name [tag] 93 | and [value]. 94 | Raises [Not_found] if no such node exists. *) 95 | val pick_tag : string -> string -> string -> nodes -> node 96 | 97 | (** Return the first tag in the list of nodes. 98 | Raised [Not_found] if the nodes are empty *) 99 | val hd : nodes -> node 100 | 101 | (** Return all the tags but the first one in a list of nodes. 102 | Returns an empty list if the list is empty. *) 103 | val tl : nodes -> nodes 104 | 105 | (** Make a tag given a [tag] name and body attributes and nodes *) 106 | val make_tag : string -> Xmlm.attribute list * nodes -> node 107 | 108 | (** Convert a list of [`Data] fragments to a human-readable string. Any elements 109 | within the list are ignored, and multiple [`Data] fragments are concatenated. *) 110 | val data_to_string : nodes -> string 111 | 112 | (** Extracts the immediate subnodes that match the given [tag] name and return 113 | a tuple of the attributes associated with that tag and its child nodes. *) 114 | val members_with_attr : string -> nodes -> (Xmlm.attribute list * nodes) list 115 | 116 | (** Extracts the immediate subnodes that match the given [tag] name, and only return 117 | the contents of those tags (ignoring the attributes, which can be retrieved via 118 | the [members_with_attr] function *) 119 | val members : string -> nodes -> nodes list 120 | 121 | (** Extracts the first subnode that match the given [tag] name, and raises 122 | [Tag_not_found] if it can't find it. *) 123 | val member_with_attr : string -> nodes -> Xmlm.attribute list * nodes 124 | 125 | (** Extracts the first subnode that match the given [tag] name, and raises 126 | [Tag_not_found] if it can't find it. Only the contents of the tag are 127 | returned (ignoring the attributes, which can be retrieved via the 128 | [member_with_attr] function instead *) 129 | val member : string -> nodes -> nodes 130 | 131 | (** [has_member tag subnodes] returns true if the given [tag] name is 132 | present among the subnodes and false if it can't find it. *) 133 | val has_member : string -> nodes -> bool 134 | 135 | (** Traverses XML nodes and applies [f] to any tags that match the [tag] parameter. 136 | The result of the transformations is returned as a new set of nodes. *) 137 | val filter_map : tag:string -> f:(Xmlm.attribute list -> nodes -> nodes) -> nodes -> nodes 138 | 139 | (** Traverses XML nodes and applies [f] to any tags that match the [tag] parameter. *) 140 | val filter_iter : tag:string -> f:(Xmlm.attribute list -> nodes -> unit) -> nodes -> unit 141 | 142 | --------------------------------------------------------------------------------