AcuteML
Acute Markup Language
AcuteML is an Acute Markup Language (AML) for Web/XML development in Julia.
It automatically creates or extracts HTML/XML files from Julia types!
It also has a general templating engine, which can be used for any type of documents.
Installation and Usage
using Pkg
Pkg.add("AcuteML")
using AcuteML
Main macro and I/O
AcuteML.@aml
— Macro@aml
Type Definition
Use @aml
macro to define a Julia type, and then the package automatically creates a xml or html associated with the defined type. The general syntax would look like this:
@aml mutable struct mybody "body"
myh1, "h1"
p::Vector{String}, "~"
end
You can also define the structs as parametric:
@aml mutable struct MyGeneralXML{T} "my-general-xml"
myfield::T, "~"
end
pxml_string = MyGeneralXML{String}(myfield = "a")
pprint(pxml_string)
pxml_vector = MyGeneralXML{Vector{String}}(myfield = ["b","c"])
pprint(pxml_vector)
Now, we go into the details:
Document Definition
- Use doc literal before the root name to define a HTML or XML document. For HTML documents root should always be "html".
@aml mutable struct Doc doc"xml_root"
# add fields (elements) here
end
@aml mutable struct Web doc"html"
# add fields (elements) here
end
Nodes (Elements) Definition
- Specify the html/xml name of struct as a string after the struct name (after a space)
@aml mutable struct Person "person"
# add fields (elements) here
end
- If the html/xml name is the same as struct name, you can use
"~"
instead
@aml mutable struct person "~"
# add fields (elements) here
end
Fields Names
- Sepecify the html/xml field name as a string in front of the field after
,
field, "study-field"
- If the html/xml name is the same as variable name, you can use
"~"
instead
age::UInt, "~"
The field names of a struct should not be the same as other defined types. This error happens when you use the same name of a type for a field name. For example, the follwing is an error:
@aml struct person "~"
name, "~"
end
@aml struct myxml doc"~"
person::person, "~"
end
Another example of this error:
@aml struct myxml doc"~"
Int, "myint"
end
However, you can choose any xml/html name. The xml/html name of the fields isn't related to the types defined in Julia. So the following is a valid syntax:
@aml struct myxml doc"~"
myint, "Int"
end
Attributes
- If the value is going to be an attribute put
att
before its name
id::Int64, att"~"
Default Value
- You can specify the default value for an argument by using
= defVal
syntax
GPA::Float64 = 4.5, "~"
Value Types
You can use Julia types or defined types for values. see and Supported Value Types Custom Value Types for more information.
- If you don't specify the type of a variable, it is considered to be string for aml manipulations:
field, "study-field"
However, for a high performance code specify String type (field::String, "study-field"
)
- For already
@aml
defined types, name should be the same as the defined type root name
university::University, "university"
- Table types are supported through PrettyTables.jl.
Value Checking
You can define any restriction for values using functions.
- To define any restrictions for the values of one field, define a function that checks a criteria for the field value and returns Bool, and put its name after a
,
after the field name:
GPA::Float64, "~", GPAcheck
- To define any restrictions for multiple values of a struct, define a function that gets all the variables and checks a criteria and returns Bool, and put its name after a
,
after the struct name:
@aml mutable struct Person "person", check_course
# ...
end
Refer to https://aminya.github.io/AcuteML.jl/dev/valueChecking/ for some of these functions examples.
Optional Fields
- If a field is optional, don't forget to define its type as
UN{}
(Union with Nothing), and set the default value asnothing
.
residence::UN{String}=nothing, "residence-stay" # optional with nothing as default value
funds::UN{String}, "financial-funds" # optional, but you should pass nothing manually in construction
Text Nodes
If the value is going to be in a Text node:
- use
txt"index"
for non-vector field type, whichindex
is an Integer that shows the positon of text node. If you givetxt""
it considers it liketxt"1"
.
textnode_single:String, txt"2"
- use
txt"indices"
for vector field type, whichindices
is an array index that shows the positons of the text nodes. If you givetxt""
it considers it liketxt"[:]"
textnode_vector::Vector{String}, txt"[2:3]"
Note that the vector Text nodes should only be used as the last field of a struct (because possible positons for text node should be known). Alternatively, you can make non-vector separate fields with correct position in the struct definition.
Empty Elements (Self-Closing) Definition
- Use
empty"name"
to define an empty (self-closing) element (e.g.<rest />
)
@aml struct rest empty"~"
end
Example - Simple
using AcuteML
@aml mutable struct body "~"
h1, "~"
p::Vector{String}, "~"
end
@aml mutable struct html doc"html"
body::body, "~"
end
b = body(h1 = "My heading", p = ["Paragraph1", "Paragraph2"])
d = html(body = b)
julia> pprint(d)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<body>
<h1>My heading</h1>
<p>Paragraph1</p>
<p>Paragraph2</p>
</body>
</html>
Example - Struct Definition
First, we define the structs using @aml
to store the data in:
using AcuteML
# Types definition
# Person Type
@aml mutable struct Person "person", check_course
age::UInt64, "~"
field, "study-field"
GPA::Float64 = 4.5, "~", GPAcheck
courses::Vector{String}, "taken-courses"
professors::UN{DataFrame} = nothing, "table"
id::Int64, att"~"
comment::UN{String} = nothing, txt"end"
end
@aml mutable struct University doc"university"
name, att"university-name"
people::Vector{Person}, "person"
end
# Value Checking Functions
GPAcheck(x) = x <= 4.5 && x >= 0
function check_course(age, field, GPA, courses, professors, id, comment)
if field == "Mechanical Engineering"
relevant = ["Artificial Intelligence", "Robotics", "Machine Design"]
elseif field == "Computer Engineering"
relevant = ["Julia", "Algorithms"]
else
error("study field is not known")
end
return any(in.(courses, Ref(relevant)))
end
Example - Creator
After we defined the structs, we can create instances of them by passing our data to the fields:
P1 = Person(age=24, field="Mechanical Engineering", courses = ["Artificial Intelligence", "Robotics"], id = 1, comment = "He is a genius")
P2 = Person(age=18, field="Computer Engineering", GPA=4, courses=["Julia"], id = 2)
U = University(name="Julia University", people=[P1, P2])
U.people[2].GPA=4.2 # mutability support after Doc creation
# An example that doesn't meet the criteria function for GPA because GPA is more than 4.5
P3 = Person(age=99, field="Macro Wizard", GPA=10, courses=["Julia Magic"], id = 3)
julia>
GPA doesn't meet criteria function
julia> pprint(P1) # or print(P1.aml)
<person id="1">
<age>24</age>
<study-field>Mechanical Engineering</study-field>
<GPA>4.5</GPA>
<taken-courses>Artificial Intelligence</taken-courses>
<taken-courses>Robotics</taken-courses>
He is a genius
</person>
julia> pprint(U) # or print(U.aml)
<?xml version="1.0" encoding="UTF-8"?>
<university university-name="Julia University">
<person id="1">
<age>24</age>
<study-field>Mechanical Engineering</study-field>
<GPA>4.5</GPA>
<taken-courses>Artificial Intelligence</taken-courses>
<taken-courses>Robotics</taken-courses>
He is a genius
</person>
<person id="2">
<age>18</age>
<study-field>Computer Engineering</study-field>
<GPA>4.2</GPA>
<taken-courses>Julia</taken-courses>
</person>
</university>
P3 with Tables.jl type:
Profs1 = DataFrame(course = ["Artificial Intelligence", "Robotics"], professor = ["Prof. A", "Prof. B"] )
P3 = Person(age=24, field="Mechanical Engineering", courses = ["Artificial Intelligence", "Robotics"], professors= Profs1, id = 1)
julia> pprint(P3)
<person id="1">
<age>24</age>
<study-field>Mechanical Engineering</study-field>
<GPA>4.5</GPA>
<taken-courses>Artificial Intelligence</taken-courses>
<taken-courses>Robotics</taken-courses>
<table>
<tr class="header">
<th style="text-align: right; ">course</th>
<th style="text-align: right; ">professor</th>
</tr>
<tr class="subheader headerLastRow">
<th style="text-align: right; ">String</th>
<th style="text-align: right; ">String</th>
</tr>
<tr>
<td style="text-align: right; ">Artificial Intelligence</td>
<td style="text-align: right; ">Prof. A</td>
</tr>
<tr>
<td style="text-align: right; ">Robotics</td>
<td style="text-align: right; ">Prof. B</td>
</tr>
</table>
</person>
Example - Extractor
After we defined the structs, we can automatically extract and store the data in their fields:
using AcuteML
xml = parsexml("""
<?xml version="1.0" encoding="UTF-8"?>
<university university-name="Julia University">
<person id="1">
<age>24</age>
<study-field>Mechanical Engineering</study-field>
<GPA>4.5</GPA>
<taken-courses>Artificial Intelligence</taken-courses>
<taken-courses>Robotics</taken-courses>
He is a genius
</person>
<person id="2">
<age>18</age>
<study-field>Computer Engineering</study-field>
<GPA>4.2</GPA>
<taken-courses>Julia</taken-courses>
</person>
</university>
""")
# extract University
U = University(xml) # StructName(xml) extracts the data and stores them in proper format
# Now you can access all of the data by calling the fieldnames
julia>U.name
"Julia University"
# extract Person
P1 = U.people[1]
julia>P1.age
24
julia>P1.field
Mechanical Engineering
julia>P1.GPA
4.5
julia>P1.courses
["Artificial Intelligence", "Robotics"]
julia>P1.id
1
julia> P1.comment
"He is a genius"