This commit is contained in:
23
.github/workflows/test.yml
vendored
Normal file
23
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: "27.1.2"
|
||||
gleam-version: "1.12.0"
|
||||
rebar3-version: "3"
|
||||
# elixir-version: "1"
|
||||
- run: gleam deps download
|
||||
- run: gleam test
|
||||
- run: gleam format --check src test
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.beam
|
||||
*.ez
|
||||
/build
|
||||
erl_crash.dump
|
||||
23
README.md
23
README.md
@@ -1,3 +1,24 @@
|
||||
# glxml
|
||||
|
||||
Gleam XML Parser
|
||||
[](https://hex.pm/packages/glxml)
|
||||
[](https://hexdocs.pm/glxml/)
|
||||
|
||||
```sh
|
||||
gleam add glxml@1
|
||||
```
|
||||
```gleam
|
||||
import glxml
|
||||
|
||||
pub fn main() -> Nil {
|
||||
// TODO: An example of the project in use
|
||||
}
|
||||
```
|
||||
|
||||
Further documentation can be found at <https://hexdocs.pm/glxml>.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
gleam run # Run the project
|
||||
gleam test # Run the tests
|
||||
```
|
||||
|
||||
19
gleam.toml
Normal file
19
gleam.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
name = "glxml"
|
||||
version = "1.0.0"
|
||||
|
||||
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||
# your project to the Hex package manager.
|
||||
#
|
||||
# description = ""
|
||||
# licences = ["Apache-2.0"]
|
||||
# repository = { type = "github", user = "", repo = "" }
|
||||
# links = [{ title = "Website", href = "" }]
|
||||
#
|
||||
# For a full reference of all the available options, you can have a look at
|
||||
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||
11
manifest.toml
Normal file
11
manifest.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
# This file was generated by Gleam
|
||||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
|
||||
{ name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
273
src/glxml.gleam
Normal file
273
src/glxml.gleam
Normal file
@@ -0,0 +1,273 @@
|
||||
import gleam/result
|
||||
|
||||
pub type Declaration {
|
||||
Declaration(versioninfo: String, encoding: String)
|
||||
}
|
||||
|
||||
pub type DocType {
|
||||
None
|
||||
DocType(name: String)
|
||||
}
|
||||
|
||||
pub type Document {
|
||||
Document(decl: Declaration, doctype: DocType)
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
parse_document("<?xml version=\"1.1\" encoding='UTF-8'?>") |> echo
|
||||
}
|
||||
|
||||
fn parse_document(doc: String) -> Result(Document, Nil) {
|
||||
use #(decl, doctype, _doc) <- result.try(parse_prolog(doc))
|
||||
|
||||
Ok(Document(decl, doctype))
|
||||
}
|
||||
|
||||
fn parse_prolog(doc: String) -> Result(#(Declaration, DocType, String), Nil) {
|
||||
use #(decl, doc) <- result.try(parse_decl(doc))
|
||||
|
||||
Ok(#(decl, None, doc))
|
||||
}
|
||||
|
||||
fn parse_decl(doc: String) -> Result(#(Declaration, String), Nil) {
|
||||
case doc {
|
||||
"<?xml" <> tail -> {
|
||||
use #(versioninfo, doc) <- result.try(parse_versioninfo(tail))
|
||||
let #(encoding, doc) = case parse_encodingdecl(doc) {
|
||||
Ok(e) -> e
|
||||
Error(_) -> #("", doc)
|
||||
}
|
||||
|
||||
case trim_space(doc) {
|
||||
"?>" <> tail -> Ok(#(Declaration(versioninfo:, encoding:), tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_versioninfo(doc: String) -> Result(#(String, String), Nil) {
|
||||
use #(_, doc) <- result.try(parse_space(doc))
|
||||
case doc {
|
||||
"version=" <> tail -> {
|
||||
use #(version, doc) <- result.try(parse_version(tail))
|
||||
Ok(#(version, doc))
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_version(doc: String) -> Result(#(String, String), Nil) {
|
||||
case doc {
|
||||
"\"1." <> tail -> {
|
||||
use #(version, doc) <- result.try(do_parse_version(tail, "1."))
|
||||
case doc {
|
||||
"\"" <> tail -> Ok(#(version, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
"'1." <> tail -> {
|
||||
use #(version, doc) <- result.try(do_parse_version(tail, "1."))
|
||||
case doc {
|
||||
"'" <> tail -> Ok(#(version, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn do_parse_version(
|
||||
doc: String,
|
||||
version: String,
|
||||
) -> Result(#(String, String), Nil) {
|
||||
case do_parse_digit(doc) {
|
||||
Ok(#(digit, doc)) -> do_parse_version(doc, version <> digit)
|
||||
Error(_) if version == "" -> Error(Nil)
|
||||
Error(_) -> Ok(#(version, doc))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_encodingdecl(doc: String) -> Result(#(String, String), Nil) {
|
||||
use #(_, doc) <- result.try(parse_space(doc))
|
||||
|
||||
case doc {
|
||||
"encoding=" <> tail -> {
|
||||
case tail {
|
||||
"\"" <> tail -> {
|
||||
use #(encoding, doc) <- result.try(parse_encoding(tail))
|
||||
case doc {
|
||||
"\"" <> tail -> Ok(#(encoding, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
"'" <> tail -> {
|
||||
use #(encoding, doc) <- result.try(parse_encoding(tail))
|
||||
case doc {
|
||||
"'" <> tail -> Ok(#(encoding, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_encoding(doc: String) -> Result(#(String, String), Nil) {
|
||||
case do_parse_alpha(doc) {
|
||||
Ok(#(char, doc)) -> {
|
||||
Ok(parse_multiple_optional(
|
||||
doc,
|
||||
try_parsers(
|
||||
[
|
||||
do_parse_alpha,
|
||||
do_parse_digit,
|
||||
fn(doc) {
|
||||
case doc {
|
||||
"." as char <> tail | "_" as char <> tail | "-" as char <> tail ->
|
||||
Ok(#(char, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
},
|
||||
],
|
||||
_,
|
||||
),
|
||||
char,
|
||||
))
|
||||
}
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn do_parse_digit(doc: String) -> Result(#(String, String), Nil) {
|
||||
case doc {
|
||||
"0" as digit <> tail
|
||||
| "1" as digit <> tail
|
||||
| "2" as digit <> tail
|
||||
| "3" as digit <> tail
|
||||
| "4" as digit <> tail
|
||||
| "5" as digit <> tail
|
||||
| "6" as digit <> tail
|
||||
| "7" as digit <> tail
|
||||
| "8" as digit <> tail
|
||||
| "9" as digit <> tail -> Ok(#(digit, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn do_parse_alpha(doc: String) -> Result(#(String, String), Nil) {
|
||||
case doc {
|
||||
"a" as char <> tail
|
||||
| "b" as char <> tail
|
||||
| "c" as char <> tail
|
||||
| "d" as char <> tail
|
||||
| "e" as char <> tail
|
||||
| "f" as char <> tail
|
||||
| "g" as char <> tail
|
||||
| "h" as char <> tail
|
||||
| "i" as char <> tail
|
||||
| "j" as char <> tail
|
||||
| "k" as char <> tail
|
||||
| "l" as char <> tail
|
||||
| "m" as char <> tail
|
||||
| "n" as char <> tail
|
||||
| "o" as char <> tail
|
||||
| "p" as char <> tail
|
||||
| "q" as char <> tail
|
||||
| "r" as char <> tail
|
||||
| "s" as char <> tail
|
||||
| "t" as char <> tail
|
||||
| "u" as char <> tail
|
||||
| "v" as char <> tail
|
||||
| "w" as char <> tail
|
||||
| "x" as char <> tail
|
||||
| "y" as char <> tail
|
||||
| "z" as char <> tail
|
||||
| "A" as char <> tail
|
||||
| "B" as char <> tail
|
||||
| "C" as char <> tail
|
||||
| "D" as char <> tail
|
||||
| "E" as char <> tail
|
||||
| "F" as char <> tail
|
||||
| "G" as char <> tail
|
||||
| "H" as char <> tail
|
||||
| "I" as char <> tail
|
||||
| "J" as char <> tail
|
||||
| "K" as char <> tail
|
||||
| "L" as char <> tail
|
||||
| "M" as char <> tail
|
||||
| "N" as char <> tail
|
||||
| "O" as char <> tail
|
||||
| "P" as char <> tail
|
||||
| "Q" as char <> tail
|
||||
| "R" as char <> tail
|
||||
| "S" as char <> tail
|
||||
| "T" as char <> tail
|
||||
| "U" as char <> tail
|
||||
| "V" as char <> tail
|
||||
| "W" as char <> tail
|
||||
| "X" as char <> tail
|
||||
| "Y" as char <> tail
|
||||
| "Z" as char <> tail -> Ok(#(char, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_space(doc: String) -> String {
|
||||
case parse_space(doc) {
|
||||
Ok(#(_, doc)) -> trim_space(doc)
|
||||
Error(_) -> doc
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_space(doc: String) -> Result(#(String, String), Nil) {
|
||||
case doc {
|
||||
" " as ws <> tail
|
||||
| "\t" as ws <> tail
|
||||
| "\n" as ws <> tail
|
||||
| "\r" as ws <> tail -> Ok(#(ws, tail))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_parsers(
|
||||
over list: List(fn(String) -> Result(#(a, String), Nil)),
|
||||
against static_data: String,
|
||||
) -> Result(#(a, String), Nil) {
|
||||
case list {
|
||||
[] -> Error(Nil)
|
||||
[first, ..rest] ->
|
||||
case first(static_data) {
|
||||
Error(_) -> try_parsers(rest, static_data)
|
||||
Ok(r) -> Ok(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_multiple(
|
||||
to_parse str: String,
|
||||
with to_run: fn(String) -> Result(#(String, String), Nil),
|
||||
) -> Result(#(String, String), Nil) {
|
||||
case parse_multiple_optional(str, to_run, "") {
|
||||
#("", _) -> Error(Nil)
|
||||
#(r, rest) -> Ok(#(r, rest))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_multiple_optional(
|
||||
to_parse str: String,
|
||||
with to_run: fn(String) -> Result(#(String, String), Nil),
|
||||
acc ret: String,
|
||||
) -> #(String, String) {
|
||||
case str {
|
||||
"" -> #(ret, str)
|
||||
_ ->
|
||||
case to_run(str) {
|
||||
Ok(#(r, rest)) -> parse_multiple_optional(rest, to_run, ret <> r)
|
||||
Error(_) -> #(ret, str)
|
||||
}
|
||||
}
|
||||
}
|
||||
13
test/glxml_test.gleam
Normal file
13
test/glxml_test.gleam
Normal file
@@ -0,0 +1,13 @@
|
||||
import gleeunit
|
||||
|
||||
pub fn main() -> Nil {
|
||||
gleeunit.main()
|
||||
}
|
||||
|
||||
// gleeunit test functions end in `_test`
|
||||
pub fn hello_world_test() {
|
||||
let name = "Joe"
|
||||
let greeting = "Hello, " <> name <> "!"
|
||||
|
||||
assert greeting == "Hello, Joe!"
|
||||
}
|
||||
Reference in New Issue
Block a user