├── .gitignore ├── README.md ├── answers ├── ex01 │ └── index.html ├── ex02 │ ├── ex02.sef.json │ ├── ex02.xsl │ └── index.html ├── ex03 │ ├── ex03.sef.json │ ├── ex03.xsl │ └── index.html ├── ex04 │ ├── ex04.sef.json │ └── ex04.xsl ├── ex06 │ ├── ex06.sef.json │ └── ex06.xsl ├── ex07 │ ├── ex07.sef.json │ └── ex07.xsl ├── ex08 │ ├── ex08.sef.json │ └── ex08.xsl ├── ex08b │ ├── ex08b.sef.json │ └── ex08b.xsl ├── ex09a │ ├── ex09a.sef.json │ └── ex09a.xsl ├── ex09b │ ├── ex09b.sef.json │ └── ex09b.xsl ├── ex11a │ ├── ex11a.sef.json │ └── ex11a.xsl ├── ex11b │ ├── ex11b.sef.json │ └── ex11b.xsl ├── ex12 │ ├── ex12.sef.json │ └── ex12.xsl └── ex13 │ ├── ex13.sef.json │ └── ex13.xsl ├── build.gradle ├── css └── recipe.css ├── exercises ├── ex01 │ ├── README.md │ └── index.html ├── ex02 │ ├── README.md │ ├── ex02.sef.json │ ├── ex02.xsl │ └── index.html ├── ex03 │ ├── README.md │ ├── ex03.xsl │ └── index.html ├── ex04 │ ├── README.md │ └── ex04.xsl ├── ex05 │ └── README.md ├── ex06 │ ├── README.md │ └── ex06.xsl ├── ex07 │ ├── README.md │ └── ex07.xsl ├── ex08 │ ├── README.md │ └── ex08.xsl ├── ex09 │ ├── README.md │ └── ex09.xsl ├── ex10 │ ├── README.md │ └── ex10.xsl ├── ex11 │ ├── README.md │ └── ex11.xsl ├── ex12 │ ├── README.md │ └── ex12.xsl └── ex13 │ ├── README.md │ └── ex13.xsl ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hello ├── css │ └── hello.css ├── index.html ├── js │ ├── SaxonJS2.rt.js │ └── start.js └── xslt │ ├── stylesheet.sef.json │ └── stylesheet.xsl ├── js └── SaxonJS2.rt.js ├── lib ├── .gitignore ├── README.md ├── conversions.json └── utils.xsl ├── node ├── Dockerfile ├── christmas.js ├── package-lock.json ├── package.json └── server.js ├── presentation ├── SaxonJSDiagram.svg ├── ch01.html ├── ch02.html ├── ch03.html ├── css │ ├── docbook-paged.css │ ├── docbook-screen.css │ ├── docbook-toc.css │ ├── docbook.css │ ├── oxy-markup.css │ ├── presentation.css │ ├── print-book.css │ ├── print-paper.css │ ├── print-verso.css │ ├── prism.css │ ├── pygments.css │ ├── speaker-notes.css │ └── xlink.css ├── index.html ├── js │ ├── annotations.js │ ├── chunk-nav.js │ ├── controls.js │ ├── persistent-toc.js │ ├── prism.js │ └── xlink.js ├── orientation.jpg ├── parti.html ├── partich01.html ├── partich02.html ├── partich03.html ├── partich04.html ├── partich05.html ├── partich06.html ├── partich07.html ├── partich08.html ├── partich09.html ├── partich10.html ├── partich11.html ├── partich12.html ├── partich13.html ├── partich14.html ├── partich15.html ├── partich16.html ├── partich17.html ├── partich18.html ├── partich19.html ├── partich20.html ├── partich21.html ├── partich22.html ├── partich23.html ├── partich24.html ├── partich25.html ├── partich26.html ├── partich27.html ├── partich28.html ├── partich29.html ├── partich30.html ├── partich31.html ├── partich32.html ├── partich33.html ├── partich34.html ├── partich35.html ├── partich36.html ├── partich37.html ├── partich38.html ├── partich39.html ├── partich40.html ├── partich41.html ├── partich42.html ├── partich43.html ├── partii.html ├── partiich01.html ├── partiich02.html ├── partiich03.html ├── partiich04.html ├── partiich05.html ├── partiich06.html ├── partiich07.html ├── partiii.html ├── partiiiappa.html ├── partiiiappas02.html ├── partiiiappb.html ├── partiiiappc.html ├── rfinish.png └── rstart.png ├── python └── server.py ├── recipes ├── README.md ├── beef-stroganof.html ├── categories.html ├── categories.json ├── mac-n-cheese.html ├── marys-beef-stew.html ├── pizza.html ├── rice-a-roni.html ├── template.xml └── treacle-taffy.html ├── settings.gradle ├── src └── main │ ├── resources │ ├── SaxonJSDiagram.svg │ ├── css │ │ ├── presentation.css │ │ └── pygments.css │ ├── orientation.jpg │ ├── rfinish.png │ └── rstart.png │ └── xml │ ├── browser.xml │ ├── node.xml │ ├── presentation.xml │ └── refs.xml └── tools ├── docbook.rng ├── presentation.xsl └── xinclude.xsl /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle/ 2 | /build/ 3 | /node/node_modules/ 4 | .mypy_cache/ 5 | 6 | -------------------------------------------------------------------------------- /answers/ex01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exercise 1 4 | 5 | 6 | 7 | 8 | 9 |

This page intentionally left blank.

10 | 11 | 12 | -------------------------------------------------------------------------------- /answers/ex02/ex02.sef.json: -------------------------------------------------------------------------------- 1 | {"N":"package","version":"30","packageVersion":"1","saxonVersion":"SaxonJS 2.6","target":"JS","targetVersion":"2","name":"TOP-LEVEL","relocatable":"false","buildDateTime":"2024-07-15T09:24:19.774+01:00","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","C":[{"N":"co","binds":"","id":"0","uniform":"true","C":[{"N":"template","flags":"os","module":"ex02.xsl","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex02/ex02.xsl","name":"Q{http://www.w3.org/1999/XSL/Transform}initial-template","line":"14","expand-text":"false","sType":"0 ","C":[{"N":"resultDoc","sType":"0 ","role":"body","line":"15","local":"","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#body","role":"href"},{"N":"elem","name":"p","sType":"1NE nQ{http://www.w3.org/1999/xhtml}p ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"16","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Congratulations, your stylesheet ran!"}]}]}]}]}]},{"N":"co","binds":"","id":"1","C":[{"N":"mode","onNo":"TC","flags":"","patternSlots":"0","prec":""}]},{"N":"overridden"},{"N":"output","C":[{"N":"property","name":"Q{http://saxon.sf.net/}stylesheet-version","value":"30"},{"N":"property","name":"method","value":"html"},{"N":"property","name":"html-version","value":"5"},{"N":"property","name":"encoding","value":"utf-8"},{"N":"property","name":"indent","value":"no"}]},{"N":"decimalFormat"}],"Σ":"755c6165"} -------------------------------------------------------------------------------- /answers/ex02/ex02.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 |

Congratulations, your stylesheet ran!

17 |
18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /answers/ex02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exercise 2 4 | 5 | 6 | 12 | 13 | 14 |

This page intentionally left blank.

15 | 16 | 17 | -------------------------------------------------------------------------------- /answers/ex03/ex03.sef.json: -------------------------------------------------------------------------------- 1 | {"N":"package","version":"30","packageVersion":"1","saxonVersion":"SaxonJS 2.6","target":"JS","targetVersion":"2","name":"TOP-LEVEL","relocatable":"false","buildDateTime":"2024-07-15T09:24:18.803+01:00","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","C":[{"N":"co","binds":"","id":"0","uniform":"true","C":[{"N":"template","flags":"os","module":"ex03.xsl","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex03/ex03.xsl","name":"Q{http://www.w3.org/1999/XSL/Transform}initial-template","line":"14","expand-text":"false","sType":"0 ","C":[{"N":"resultDoc","sType":"0 ","role":"body","line":"15","local":"method=Q{http://saxonica.com/ns/interactiveXSLT}replace-content\n","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#body","role":"href"},{"N":"elem","name":"p","sType":"1NE nQ{http://www.w3.org/1999/xhtml}p ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"16","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Congratulations, your stylesheet ran!"}]}]}]}]}]},{"N":"co","binds":"","id":"1","C":[{"N":"mode","onNo":"TC","flags":"","patternSlots":"0","prec":""}]},{"N":"overridden"},{"N":"output","C":[{"N":"property","name":"Q{http://saxon.sf.net/}stylesheet-version","value":"30"},{"N":"property","name":"method","value":"html"},{"N":"property","name":"html-version","value":"5"},{"N":"property","name":"encoding","value":"utf-8"},{"N":"property","name":"indent","value":"no"}]},{"N":"decimalFormat"}],"Σ":"f2258ab7"} -------------------------------------------------------------------------------- /answers/ex03/ex03.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 |

Congratulations, your stylesheet ran!

17 |
18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /answers/ex03/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exercise 3 4 | 5 | 6 | 12 | 13 | 14 |

This page intentionally left blank.

15 | 16 | 17 | -------------------------------------------------------------------------------- /answers/ex04/ex04.sef.json: -------------------------------------------------------------------------------- 1 | {"N":"package","version":"30","packageVersion":"1","saxonVersion":"SaxonJS 2.6","target":"JS","targetVersion":"2","name":"TOP-LEVEL","relocatable":"false","buildDateTime":"2024-07-15T09:24:17.83+01:00","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","C":[{"N":"co","binds":"","id":"0","uniform":"true","C":[{"N":"template","flags":"os","module":"ex04.xsl","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex04/ex04.xsl","name":"Q{http://www.w3.org/1999/XSL/Transform}initial-template","line":"14","expand-text":"false","sType":"0 ","C":[{"N":"resultDoc","sType":"0 ","role":"body","line":"15","local":"","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#main","role":"href"},{"N":"elem","name":"h1","sType":"1NE nQ{http://www.w3.org/1999/xhtml}h1 ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"16","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"If you can read this, it worked!"}]}]}]}]}]},{"N":"co","binds":"","id":"1","C":[{"N":"mode","onNo":"TC","flags":"","patternSlots":"0","prec":""}]},{"N":"overridden"},{"N":"output","C":[{"N":"property","name":"Q{http://saxon.sf.net/}stylesheet-version","value":"30"},{"N":"property","name":"method","value":"html"},{"N":"property","name":"html-version","value":"5"},{"N":"property","name":"encoding","value":"utf-8"},{"N":"property","name":"indent","value":"no"}]},{"N":"decimalFormat"}],"Σ":"24743fbf"} -------------------------------------------------------------------------------- /answers/ex04/ex04.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 |

If you can read this, it worked!

17 |
18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /answers/ex06/ex06.sef.json: -------------------------------------------------------------------------------- 1 | {"N":"package","version":"30","packageVersion":"1","saxonVersion":"SaxonJS 2.6","target":"JS","targetVersion":"2","name":"TOP-LEVEL","relocatable":"false","buildDateTime":"2024-07-15T09:24:12.508+01:00","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","C":[{"N":"co","id":"0","uniform":"true","binds":"1","C":[{"N":"template","flags":"os","module":"ex06.xsl","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex06/ex06.xsl","name":"Q{http://www.w3.org/1999/XSL/Transform}initial-template","line":"16","expand-text":"false","sType":"0 ","C":[{"N":"resultDoc","sType":"0 ","role":"body","line":"17","local":"method=Q{http://saxonica.com/ns/interactiveXSLT}replace-content\n","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#main","role":"href"},{"N":"applyT","sType":"* ","line":"18","mode":"#unnamed","role":"content","bSlot":"0","C":[{"N":"docOrder","sType":"*NE","role":"select","line":"18","C":[{"N":"slash","op":"/","sType":"*NE","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","C":[{"N":"ifCall","name":"Q{http://saxonica.com/ns/interactiveXSLT}page"},{"N":"axis","name":"descendant","nodeTest":"*NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]"}]}]}]}]}]}]},{"N":"co","id":"1","binds":"1","C":[{"N":"mode","onNo":"SC","flags":"","patternSlots":"0","prec":"","C":[{"N":"templateRule","rank":"0","prec":"0","seq":"2","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex06/ex06.xsl","line":"35","module":"ex06.xsl","expand-text":"false","match":"div[@id='directions']","prio":"0.5","matches":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","C":[{"N":"p.withPredicate","role":"match","sType":"1NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","C":[{"N":"p.nodeTest","test":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]"},{"N":"gc","op":"=","comp":"GAC|http://www.w3.org/2005/xpath-functions/collation/codepoint","card":"1:1","C":[{"N":"attVal","name":"Q{}id"},{"N":"str","val":"directions"}]}]},{"N":"copy","sType":"1NE u[1NE nQ{}div ,1NE nQ{http://www.w3.org/1999/xhtml}div ] ","flags":"cin","role":"action","line":"36","C":[{"N":"sequence","sType":"* ","C":[{"N":"applyT","sType":"* ","line":"37","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"attribute","nodeTest":"*NA","sType":"*NA","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"37"}]},{"N":"elem","name":"h2","sType":"1NE nQ{http://www.w3.org/1999/xhtml}h2 ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"38","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Directions"}]}]},{"N":"applyT","sType":"* ","line":"39","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"child","nodeTest":"*N u[NT,NP,NC,NE]","sType":"*N u[NT,NP,NC,NE]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"39"}]}]}]}]},{"N":"templateRule","rank":"1","prec":"0","seq":"1","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex06/ex06.xsl","line":"27","module":"ex06.xsl","expand-text":"false","match":"div[@id='ingredients']","prio":"0.5","matches":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","C":[{"N":"p.withPredicate","role":"match","sType":"1NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","C":[{"N":"p.nodeTest","test":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]"},{"N":"gc","op":"=","comp":"GAC|http://www.w3.org/2005/xpath-functions/collation/codepoint","card":"1:1","C":[{"N":"attVal","name":"Q{}id"},{"N":"str","val":"ingredients"}]}]},{"N":"copy","sType":"1NE u[1NE nQ{}div ,1NE nQ{http://www.w3.org/1999/xhtml}div ] ","flags":"cin","role":"action","line":"28","C":[{"N":"sequence","sType":"* ","C":[{"N":"applyT","sType":"* ","line":"29","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"attribute","nodeTest":"*NA","sType":"*NA","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"29"}]},{"N":"elem","name":"h2","sType":"1NE nQ{http://www.w3.org/1999/xhtml}h2 ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"30","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Ingredients"}]}]},{"N":"applyT","sType":"* ","line":"31","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"child","nodeTest":"*N u[NT,NP,NC,NE]","sType":"*N u[NT,NP,NC,NE]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"31"}]}]}]}]},{"N":"templateRule","rank":"2","prec":"0","seq":"0","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex06/ex06.xsl","line":"22","module":"ex06.xsl","expand-text":"false","match":"main","prio":"0","matches":"NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]","C":[{"N":"p.nodeTest","role":"match","test":"NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]","sType":"1NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ "},{"N":"sequence","role":"action","sType":"* ","C":[{"N":"elem","name":"h1","sType":"1NE nQ{http://www.w3.org/1999/xhtml}h1 ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"23","C":[{"N":"fn","name":"string","sType":"1AS","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"3","C":[{"N":"check","card":"?","diag":"0|0||string","C":[{"N":"docOrder","C":[{"N":"slash","op":"/","C":[{"N":"slash","op":"/","C":[{"N":"slash","op":"/","C":[{"N":"root"},{"N":"axis","name":"child","nodeTest":"*NE u[NE nQ{}html,NE nQ{http://www.w3.org/1999/xhtml}html]"}]},{"N":"axis","name":"child","nodeTest":"*NE u[NE nQ{}head,NE nQ{http://www.w3.org/1999/xhtml}head]"}]},{"N":"axis","name":"child","nodeTest":"*NE u[NE nQ{}title,NE nQ{http://www.w3.org/1999/xhtml}title]"}]}]}]}]}]},{"N":"applyT","sType":"* ","line":"24","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"child","nodeTest":"*N u[NT,NP,NC,NE]","sType":"*N u[NT,NP,NC,NE]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"24"}]}]}]}]}]},{"N":"overridden"},{"N":"output","C":[{"N":"property","name":"Q{http://saxon.sf.net/}stylesheet-version","value":"30"},{"N":"property","name":"method","value":"html"},{"N":"property","name":"html-version","value":"5"},{"N":"property","name":"encoding","value":"utf-8"},{"N":"property","name":"indent","value":"no"}]},{"N":"decimalFormat"}],"Σ":"8c36dc4d"} -------------------------------------------------------------------------------- /answers/ex06/ex06.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

24 | 25 |
26 | 27 | 28 | 29 | 30 |

Ingredients

31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 |

Directions

39 | 40 |
41 |
42 | 43 |
44 | -------------------------------------------------------------------------------- /answers/ex07/ex07.sef.json: -------------------------------------------------------------------------------- 1 | {"N":"package","version":"30","packageVersion":"1","saxonVersion":"SaxonJS 2.6","target":"JS","targetVersion":"2","name":"TOP-LEVEL","relocatable":"false","buildDateTime":"2024-07-15T09:24:11.439+01:00","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","C":[{"N":"co","id":"0","uniform":"true","binds":"1","C":[{"N":"template","flags":"os","module":"ex07.xsl","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex07/ex07.xsl","name":"Q{http://www.w3.org/1999/XSL/Transform}initial-template","line":"16","expand-text":"false","sType":"0 ","C":[{"N":"resultDoc","sType":"0 ","role":"body","line":"17","local":"method=Q{http://saxonica.com/ns/interactiveXSLT}replace-content\n","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#main","role":"href"},{"N":"applyT","sType":"* ","line":"18","mode":"#unnamed","role":"content","bSlot":"0","C":[{"N":"docOrder","sType":"*NE","role":"select","line":"18","C":[{"N":"slash","op":"/","sType":"*NE","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","C":[{"N":"ifCall","name":"Q{http://saxonica.com/ns/interactiveXSLT}page"},{"N":"axis","name":"descendant","nodeTest":"*NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]"}]}]}]}]}]}]},{"N":"co","id":"1","binds":"1","C":[{"N":"mode","onNo":"SC","flags":"","patternSlots":"0","prec":"","C":[{"N":"templateRule","rank":"0","prec":"0","seq":"3","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex07/ex07.xsl","line":"50","module":"ex07.xsl","expand-text":"false","match":"div[@id='directions']","prio":"0.5","matches":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","C":[{"N":"p.withPredicate","role":"match","sType":"1NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","C":[{"N":"p.nodeTest","test":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]"},{"N":"gc","op":"=","comp":"GAC|http://www.w3.org/2005/xpath-functions/collation/codepoint","card":"1:1","C":[{"N":"attVal","name":"Q{}id"},{"N":"str","val":"directions"}]}]},{"N":"copy","sType":"1NE u[1NE nQ{}div ,1NE nQ{http://www.w3.org/1999/xhtml}div ] ","flags":"cin","role":"action","line":"51","C":[{"N":"sequence","sType":"* ","C":[{"N":"applyT","sType":"* ","line":"52","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"attribute","nodeTest":"*NA","sType":"*NA","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"52"}]},{"N":"elem","name":"h2","sType":"1NE nQ{http://www.w3.org/1999/xhtml}h2 ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"53","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Directions"}]}]},{"N":"applyT","sType":"* ","line":"54","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"child","nodeTest":"*N u[NT,NP,NC,NE]","sType":"*N u[NT,NP,NC,NE]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"54"}]}]}]}]},{"N":"templateRule","rank":"1","prec":"0","seq":"2","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex07/ex07.xsl","line":"42","module":"ex07.xsl","expand-text":"false","match":"div[@id='ingredients']","prio":"0.5","matches":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","C":[{"N":"p.withPredicate","role":"match","sType":"1NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","C":[{"N":"p.nodeTest","test":"NE u[NE nQ{}div,NE nQ{http://www.w3.org/1999/xhtml}div]"},{"N":"gc","op":"=","comp":"GAC|http://www.w3.org/2005/xpath-functions/collation/codepoint","card":"1:1","C":[{"N":"attVal","name":"Q{}id"},{"N":"str","val":"ingredients"}]}]},{"N":"copy","sType":"1NE u[1NE nQ{}div ,1NE nQ{http://www.w3.org/1999/xhtml}div ] ","flags":"cin","role":"action","line":"43","C":[{"N":"sequence","sType":"* ","C":[{"N":"applyT","sType":"* ","line":"44","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"attribute","nodeTest":"*NA","sType":"*NA","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"44"}]},{"N":"elem","name":"h2","sType":"1NE nQ{http://www.w3.org/1999/xhtml}h2 ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"45","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Ingredients"}]}]},{"N":"applyT","sType":"* ","line":"46","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"child","nodeTest":"*N u[NT,NP,NC,NE]","sType":"*N u[NT,NP,NC,NE]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"46"}]}]}]}]},{"N":"templateRule","rank":"2","prec":"0","seq":"1","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex07/ex07.xsl","line":"27","module":"ex07.xsl","expand-text":"false","match":"span[@id='serves']","prio":"0.5","matches":"NE u[NE nQ{}span,NE nQ{http://www.w3.org/1999/xhtml}span]","C":[{"N":"p.withPredicate","role":"match","sType":"1NE u[NE nQ{}span,NE nQ{http://www.w3.org/1999/xhtml}span]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","C":[{"N":"p.nodeTest","test":"NE u[NE nQ{}span,NE nQ{http://www.w3.org/1999/xhtml}span]"},{"N":"gc","op":"=","comp":"GAC|http://www.w3.org/2005/xpath-functions/collation/codepoint","card":"1:1","C":[{"N":"attVal","name":"Q{}id"},{"N":"str","val":"serves"}]}]},{"N":"let","var":"Q{}default","slot":"0","sType":"*NE ","line":"28","role":"action","C":[{"N":"cast","flags":"ae","as":"ADILI","sType":"*","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"28","C":[{"N":"atomSing","diag":"0|0||xs:int","card":"?","C":[{"N":"dot"}]}]},{"N":"let","var":"Q{}servings","slot":"1","sType":"*NE ","line":"29","C":[{"N":"fn","name":"distinct-values","sType":"*A","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"29","C":[{"N":"data","diag":"0|0||distinct-values","C":[{"N":"sequence","C":[{"N":"varRef","name":"Q{}default","slot":"0"},{"N":"int","val":"1"},{"N":"int","val":"2"},{"N":"int","val":"4"},{"N":"int","val":"6"},{"N":"int","val":"8"}]}]},{"N":"str","val":"http://www.w3.org/2005/xpath-functions/collation/codepoint"}]},{"N":"elem","name":"select","sType":"1NE nQ{http://www.w3.org/1999/xhtml}select ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"30","C":[{"N":"sequence","sType":"*N ","C":[{"N":"att","name":"id","nsuri":"","sType":"1NA ","C":[{"N":"str","sType":"1AS ","val":"serves"}]},{"N":"forEach","sType":"*NE nQ{http://www.w3.org/1999/xhtml}option ","line":"31","C":[{"N":"varRef","name":"Q{}servings","slot":"1","sType":"*","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"31"},{"N":"elem","name":"option","sType":"1NE nQ{http://www.w3.org/1999/xhtml}option ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"32","C":[{"N":"sequence","sType":"* ","C":[{"N":"att","name":"value","nsuri":"","sType":"1NA ","C":[{"N":"fn","name":"string-join","sType":"1AS ","C":[{"N":"convert","type":"AS*","from":"AZ","to":"AS","C":[{"N":"data","C":[{"N":"mergeAdj","sType":"1","C":[{"N":"dot","sType":"1","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","line":"32"}]}]}]},{"N":"str","sType":"1AS ","val":" "}]}]},{"N":"choose","sType":"? ","line":"33","C":[{"N":"gc","op":"=","comp":"GAC|http://www.w3.org/2005/xpath-functions/collation/codepoint","card":"1:1","sType":"1AB","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","line":"33","C":[{"N":"atomSing","diag":"1|0||gc","card":"?","C":[{"N":"dot"}]},{"N":"data","diag":"1|1||gc","C":[{"N":"varRef","name":"Q{}default","slot":"0"}]}]},{"N":"att","name":"selected","sType":"1NA ","line":"34","C":[{"N":"str","role":"select","val":"selected","sType":"1AS","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","line":"34"}]},{"N":"true"},{"N":"empty","sType":"0 "}]},{"N":"dot","sType":"1","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"36"}]}]}]}]}]}]}]}]},{"N":"templateRule","rank":"3","prec":"0","seq":"0","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/answers/ex07/ex07.xsl","line":"22","module":"ex07.xsl","expand-text":"false","match":"main","prio":"0","matches":"NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]","C":[{"N":"p.nodeTest","role":"match","test":"NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]","sType":"1NE u[NE nQ{}main,NE nQ{http://www.w3.org/1999/xhtml}main]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ "},{"N":"sequence","role":"action","sType":"* ","C":[{"N":"elem","name":"h1","sType":"1NE nQ{http://www.w3.org/1999/xhtml}h1 ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","line":"23","C":[{"N":"fn","name":"string","sType":"1AS","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"3","C":[{"N":"check","card":"?","diag":"0|0||string","C":[{"N":"docOrder","C":[{"N":"slash","op":"/","C":[{"N":"slash","op":"/","C":[{"N":"slash","op":"/","C":[{"N":"root"},{"N":"axis","name":"child","nodeTest":"*NE u[NE nQ{}html,NE nQ{http://www.w3.org/1999/xhtml}html]"}]},{"N":"axis","name":"child","nodeTest":"*NE u[NE nQ{}head,NE nQ{http://www.w3.org/1999/xhtml}head]"}]},{"N":"axis","name":"child","nodeTest":"*NE u[NE nQ{}title,NE nQ{http://www.w3.org/1999/xhtml}title]"}]}]}]}]}]},{"N":"applyT","sType":"* ","line":"24","mode":"#unnamed","bSlot":"0","C":[{"N":"axis","name":"child","nodeTest":"*N u[NT,NP,NC,NE]","sType":"*N u[NT,NP,NC,NE]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","role":"select","line":"24"}]}]}]}]}]},{"N":"overridden"},{"N":"output","C":[{"N":"property","name":"Q{http://saxon.sf.net/}stylesheet-version","value":"30"},{"N":"property","name":"method","value":"html"},{"N":"property","name":"html-version","value":"5"},{"N":"property","name":"encoding","value":"utf-8"},{"N":"property","name":"indent","value":"no"}]},{"N":"decimalFormat"}],"Σ":"41961f21"} -------------------------------------------------------------------------------- /answers/ex07/ex07.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

24 | 25 |
26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 44 | 45 |

Ingredients

46 | 47 |
48 |
49 | 50 | 51 | 52 | 53 |

Directions

54 | 55 |
56 |
57 | 58 |
59 | -------------------------------------------------------------------------------- /answers/ex08/ex08.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 44 | 45 | 46 | 47 | 48 | 49 |

Ingredients

50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 |

Directions

58 | 59 |
60 |
61 | 62 |
63 | -------------------------------------------------------------------------------- /answers/ex08b/ex08b.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

24 | 25 |
26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 47 | 48 | 49 | 50 |

Ingredients

51 | 52 |
53 |
54 | 55 | 56 | 57 | 58 |

Directions

59 | 60 |
61 |
62 | 63 |
64 | -------------------------------------------------------------------------------- /answers/ex09a/ex09a.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

26 | 27 |
28 | 29 | 30 | 31 | 32 | 42 | 43 | 44 | 45 | 47 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |

Ingredients

62 | 63 |
64 |
65 | 66 | 67 | 68 | 69 |

Directions

70 | 71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 | -------------------------------------------------------------------------------- /answers/ex09b/ex09b.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

26 | 27 |
28 | 29 | 30 | 31 | 32 | 42 | 43 | 44 | 45 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |

Ingredients

61 | 62 |
63 |
64 | 65 | 66 | 67 | 68 |

Directions

69 | 70 |
71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | -------------------------------------------------------------------------------- /answers/ex11a/ex11a.xsl: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

37 | 38 |
39 | 40 | 41 | 42 | 43 |

Recipe categories

44 |
45 |
46 | 47 | 48 | 52 | 53 | 54 |

Select a category:

55 | 64 |
65 |
66 | 67 |
68 | -------------------------------------------------------------------------------- /answers/ex11b/ex11b.xsl: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

37 | 38 |
39 | 40 | 41 | 42 | 43 |

Recipe categories

44 |
45 |
46 | 47 | 48 | 52 | 53 | 54 |

Select a category:

55 | 64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |

Recipes in

74 |

Select a recipe:

75 | 84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | 96 |
97 |
98 |
99 |
100 |
101 | 102 |
103 | -------------------------------------------------------------------------------- /answers/ex12/ex12.xsl: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |

27 | 28 |
29 | 30 | 31 | 32 | 33 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 61 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |

Ingredients

74 | 75 |
76 |
77 | 78 | 79 | 80 | 81 |

Directions

82 | 83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
172 | -------------------------------------------------------------------------------- /answers/ex13/ex13.xsl: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |

35 | 36 |
37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 58 | 59 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 76 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |

Ingredients

89 | 90 |
91 |
92 | 93 | 94 | 95 | 96 |

Directions

97 | 98 |
99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 |
191 | -------------------------------------------------------------------------------- /css/recipe.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | @import url("https://fonts.googleapis.com/css?family=B612+Mono&display=swap"); 3 | @import url("https://fonts.googleapis.com/css?family=Noto+Sans&display=swap"); 4 | @import url("https://fonts.googleapis.com/css?family=Noto+Serif&display=swap"); 5 | @import url('https://fonts.googleapis.com/css2?family=Oswald&display=swap'); 6 | @import url('https://fonts.googleapis.com/css2?family=Lobster&display=swap'); 7 | } 8 | 9 | :root { 10 | --symbol-fonts: "Arial Unicode", "Apple Symbols", "Symbol", "Symbola_hint"; 11 | --body-family: "Noto Serif", serif, var(--symbol-fonts); 12 | --title-family: "Lobster", cursive, var(--symbol-fonts); 13 | --mono-family: "B612 Mono", monospace, var(--symbol-fonts); 14 | } 15 | 16 | html { 17 | font-size: 16pt; 18 | background-color: rgb(228, 238, 240); 19 | } 20 | 21 | body { 22 | width: 100%; 23 | padding-bottom: 1in; 24 | } 25 | 26 | main { 27 | max-width: 50em; 28 | margin-left: auto; 29 | margin-right: auto; 30 | } 31 | 32 | h1, h2, h3, h4, h5, h6 { 33 | font-family: var(--title-family); 34 | } 35 | 36 | h1 { 37 | text-align: center; 38 | background-color: rgb(193, 206,221); 39 | padding-top: 0.5rem; 40 | padding-bottom: 0.5rem; 41 | } 42 | 43 | #ingredients { 44 | border: 1px solid black; 45 | padding: 1em; 46 | padding-bottom: 0.25em; 47 | border-radius: 0.75em; 48 | } 49 | 50 | #ingredients h2 { 51 | margin-top: 0; 52 | } 53 | -------------------------------------------------------------------------------- /exercises/ex01/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 1 2 | 3 | This exercise is just to get you familiar with the “furniture” 4 | necessary to load SaxonJS into the browser. 5 | 6 | 1. Open `index.html` in the `exercises/ex01` directory in your favorite editor. 7 | 2. Add a script link to SaxonJS in the `head` of the document: 8 | ``` 9 | 10 | ``` 11 | 3. Add another script just below the first to call the `getProcessorInfo()` API. The SaxonJS API includes several 12 | methods, including this one which returns information about the processor. 13 | ``` 14 | 15 | ``` 16 | 4. Open your browser’s console window to view the processor info. 17 | -------------------------------------------------------------------------------- /exercises/ex01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exercise 1 4 | 5 | 6 | 7 |

This page intentionally left blank.

8 | 9 | 10 | -------------------------------------------------------------------------------- /exercises/ex02/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 2 2 | 3 | In this exercise, you’ll run your first XSLT 3.0 transformation in the browser with SaxonJS! 4 | We’ll cover how you compile an XSLT stylesheet into a “SEF” file in the next exercise. 5 | 6 | 1. Open `index.html` in your favorite editor. 7 | 2. Add a script link to SaxonJS in the `head` of the document: 8 | ``` 9 | 10 | ``` 11 | 3. Add a script to run the `ex02.xsl` stylesheet in its compiled form: `ex02.sef.json`: 12 | ``` 13 | 20 | ``` 21 | 4. Load the answer into your browser `http://localhost:9000/exercises/ex02` 22 | -------------------------------------------------------------------------------- /exercises/ex02/ex02.sef.json: -------------------------------------------------------------------------------- 1 | {"N":"package","version":"30","packageVersion":"1","saxonVersion":"SaxonJS 2.6","target":"JS","targetVersion":"2","name":"TOP-LEVEL","relocatable":"false","buildDateTime":"2024-07-15T09:24:20.736+01:00","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","C":[{"N":"co","binds":"","id":"0","uniform":"true","C":[{"N":"template","flags":"os","module":"ex02.xsl","slots":"200","baseUri":"file:///Volumes/Saxonica/src/examples/SaxonJS-Tutorial-2021/exercises/ex02/ex02.xsl","name":"Q{http://www.w3.org/1999/XSL/Transform}initial-template","line":"14","expand-text":"false","sType":"0 ","C":[{"N":"resultDoc","sType":"0 ","role":"body","line":"15","local":"","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#body","role":"href"},{"N":"elem","name":"p","sType":"1NE nQ{http://www.w3.org/1999/xhtml}p ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"16","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Congratulations, your stylesheet ran!"}]}]}]}]}]},{"N":"co","binds":"","id":"1","C":[{"N":"mode","onNo":"TC","flags":"","patternSlots":"0","prec":""}]},{"N":"overridden"},{"N":"output","C":[{"N":"property","name":"Q{http://saxon.sf.net/}stylesheet-version","value":"30"},{"N":"property","name":"method","value":"html"},{"N":"property","name":"html-version","value":"5"},{"N":"property","name":"encoding","value":"utf-8"},{"N":"property","name":"indent","value":"no"}]},{"N":"decimalFormat"}],"Σ":"24ab86e5"} -------------------------------------------------------------------------------- /exercises/ex02/ex02.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 |

Congratulations, your stylesheet ran!

17 |
18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /exercises/ex02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exercise 2 4 | 5 | 6 | 7 |

This page intentionally left blank.

8 | 9 | 10 | -------------------------------------------------------------------------------- /exercises/ex03/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 3 2 | 3 | In this exercise, you’ll compile an XSLT stylesheet into a SEF file. 4 | 5 | Start in the “root” directory where you checked out the repository. (Up two levels from here.) 6 | 7 | If you’re using Java, you can compile the stylesheet by running: 8 | 9 | ``` 10 | ./gradlew -Pxsl=exercises/ex03/ex03.xsl eej 11 | ``` 12 | 13 | That’s the equivalent of running 14 | 15 | ``` 16 | java com.saxonica.Transform -xsl:exercises/ex03/ex03.xsl \ 17 | -export:exercises/ex03/ex03.sef.json \ 18 | -target:JS -nogo -relocate:on -ns:##html5 19 | ``` 20 | 21 | but using Gradle saves some typing and makes sure that the classpath 22 | and other Java infrastructure is set up correctly. 23 | 24 | If you’re using Node, you can compile the stylesheet by running 25 | 26 | ``` 27 | ./gradlew -Pxsl=exercises/ex03/ex03.xsl node_xslt3 28 | ``` 29 | 30 | That’s the equivalent of running 31 | 32 | ``` 33 | node node_modules/xslt3/xslt3.js -xsl:exercises/ex03/ex03.xsl \ 34 | -export:exercises/ex03/ex03.sef.json \ 35 | -nogo -ns:##html5 36 | ``` 37 | 38 | but using Gradle saves some typing and makes sure that the node 39 | environment is set up correctly. 40 | 41 | 1. Open up `ex03.xsl` in your favorite editor. Find the 42 | `xsl:result-document` instruction and add 43 | `method="ixsl:replace-content"` to it. 44 | 2. Recompile the stylesheet. (This step is important!) 45 | 3. What effect do you think that will have on the result? 46 | 4. Load the answer into your browser `http://localhost:9000/exercises/ex03/` 47 | 5. Did you remember to add the script lines? 48 | 6. Were you right about the effect of the `method` attribute? 49 | -------------------------------------------------------------------------------- /exercises/ex03/ex03.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 |

Congratulations, your stylesheet ran!

17 |
18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /exercises/ex03/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exercise 3 4 | 5 | 6 | 7 |

This page intentionally left blank.

8 | 9 | 10 | -------------------------------------------------------------------------------- /exercises/ex04/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 4 2 | 3 | In this exercise, we’ll try out the “let the server fix it for you” feature. 4 | 5 | Both the Python and Node (and Dockerized Node) web servers will 6 | automatically add the script headers to any documents they serve if 7 | you pass them as parameters to the script. 8 | 9 | Point your browser at one of the recipes, for example: 10 | http://localhost:9000/recipes/pizza.html 11 | 12 | You’ll see a fairly plain and in many ways incomplete recipe. We’re 13 | going to fix those things in future exercises. 14 | 15 | (If you open 16 | http://localhost:9000/recipes/ you’ll get a list of the recipes, as you 17 | might have expected.) 18 | 19 | Make sure you have the server running in a shell window that you can see. 20 | 21 | Now add “?exercise=ex04” to the URI: http://localhost:9000/recipes/pizza.html?exercise=ex04 22 | 23 | You should see the transformed output this time. 24 | 25 | If you’re using the Python server, you should see something like this in the shell window 26 | where the server is running: 27 | 28 | ``` 29 | 127.0.0.1 - - [21/Oct/2021 15:04:30] "GET /recipes/pizza.html?exercise=ex04 HTTP/1.1" 200 - 30 | 127.0.0.1 - - [21/Oct/2021 15:04:30] "GET /js/SaxonJS2.rt.js HTTP/1.1" 200 - 31 | > Task :pythonServerStart 32 | > Configure project : 33 | Building with Java version 1.8.0_231 34 | > Task :parseCompilerOptions 35 | > Task :eej 36 | BUILD SUCCESSFUL in 8s 37 | 2 actionable tasks: 2 executed 38 | 127.0.0.1 - - [21/Oct/2021 15:04:39] "GET /exercises/ex04/ex04.sef.json HTTP/1.1" 200 - 39 | ``` 40 | 41 | If you’re using Node.js, you’ll get the slightly less chatty output: 42 | 43 | ``` 44 | GET /recipes/beef-stroganof.html?exercise=ex04 45 | Compiling XSL with xslt3.js 46 | GET /js/SaxonJS2.rt.js 47 | GET /exercises/ex04/ex04.sef.json 48 | ``` 49 | 50 | There’s one more trick to show you in this exercise. Edit the 51 | stylesheet `ex04.xsl` so that the “it worked” message is different and 52 | save the XSL file. 53 | 54 | *Before* you recompile the SEF file, hit reload in the browser. 55 | 56 | Magic! 57 | 58 | The browser will also automatically recompile your XSL file if it’s 59 | newer than the corresponding SEF file. 60 | 61 | That’s going to save you some time! 62 | -------------------------------------------------------------------------------- /exercises/ex04/ex04.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 |

If you can read this, it worked!

17 |
18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /exercises/ex05/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 5 2 | 3 | Review the recipe markup. This will probably be easiest by just 4 | opening each of the recipe files in your favorite editor. 5 | 6 | They’re in HTML, but think about how the significant features are represented. 7 | 8 | * Number of servings. 9 | * Ingredients and directions. 10 | * Quantities and units in ingredients. 11 | * Quantities and units in the directions. 12 | 13 | -------------------------------------------------------------------------------- /exercises/ex06/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 6 2 | 3 | A little warmup exercise with the recipe markup. 4 | 5 | You will have noticed in the preceding exercise, and maybe in the 6 | recipe you used for exercise 4, that there are no heading elements in 7 | the recipe pages. 8 | 9 | The markup vocabulary designer has taken the position that the title 10 | shouldn’t be repeated in the “source” markup, because repeating 11 | information introduces the risk of discrepancy. The titles for the 12 | sections have also been left out because they can be automatically 13 | generated. 14 | 15 | Write a stylesheet that does an “almost identity” transform but copies 16 | the HTML title as a top-level H1 and adds “Ingredients” and 17 | “Directions” headings as H2 elements in the relevant sections. 18 | 19 | Remember that you can take advantage of the server to do some of the 20 | tedious work for you. Point your browser at one of the recipes, for 21 | example: 22 | 23 | ``` 24 | http://localhost:9000/recipes/pizza.html?exercise=ex06 25 | ``` 26 | 27 | You can then edit the stylesheet (`ex06.xsl`) and reload to see your 28 | progress. When you think it’s working, try it out on some of the other 29 | recipes, for example Macaroni and Cheese: 30 | 31 | ``` 32 | http://localhost:9000/recipes/mac-n-cheese.html?exercise=ex06 33 | ``` 34 | 35 | Code hints: 36 | 1. Get the transform to start modifying the `main` element from the 37 | initial template using something like: 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 2. Add templates that match on `main`, `div[@id='ingredients']` and 44 | `div[@id='directions']`, starting with something like this. 45 | 46 | For the main section, copy the HTML title: 47 | 48 | ``` 49 | 50 |

51 | 52 |
53 | ``` 54 | 55 | For the ingredients and directions, generate the titles, like this: 56 | 57 | ``` 58 | 59 | 60 | 61 |

Ingredients

62 | 63 |
64 |
65 | ``` 66 | -------------------------------------------------------------------------------- /exercises/ex06/ex06.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /exercises/ex07/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 7 2 | 3 | Let’s make a more interesting change to the markup this time. Over the 4 | next few exercises, we’re going to make it possible to change the 5 | number of servings and have the recipe recalculate the ingredients 6 | quantities automatically. 7 | 8 | As a first step, let’s replace the “servings” text with a pull down menu. 9 | If you don’t remember the HTML for a pull down, it looks like this: 10 | 11 | ``` 12 | 18 | ``` 19 | 20 | Start with either your answer from exercise 6, or ours. Whichever you 21 | prefer. 22 | 23 | You can influence which option the browser treats as initially selected with 24 | a `selected` attribute. For example: 25 | 26 | ``` 27 | 33 | ``` 34 | -------------------------------------------------------------------------------- /exercises/ex07/ex07.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercises/ex08/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 8 2 | 3 | It’s time to get (mildly) interactive. 4 | 5 | Starting with the results of the previous exercise, add a template that will 6 | fire when the user selects a different value for the number of servings. 7 | 8 | Detect the `change` event on the `select` element. 9 | 10 | In future exercises, we’ll look at the larger problem of actually updating the recipe. 11 | 12 | For this exercise, just do something really simple to demonstrate that you’ve 13 | captured the event. For example: 14 | 15 | * Use `xsl:message` to write a message to the console, 16 | * or use `xsl:result-document` to update some part of the page, 17 | 18 | (If you choose to use `xsl:message`, remember that you’ll have to look at the 19 | browser console window to see the output!) 20 | 21 | The event-handling template should look like this: 22 | 23 | ``` 24 | 25 | ... 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /exercises/ex08/ex08.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercises/ex09/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 9 2 | 3 | Continue where you left off in exercise 8. 4 | 5 | When a user changes the number of servings, update all of the 6 | quantities in the recipe. 7 | 8 | One way to do this is: 9 | 10 | * Compute a scaling factor. If the original recipe was for 4 servings 11 | and the user has selected 6, the scaling factor would be 1.5 (i.e., 12 | 6 div 4). (Note that you’ll have to find a way to remember the 13 | original number of servings to make this work). 14 | * Reformat the ingredients section in a mode that multiplies by the 15 | scaling factor. 16 | * Use `xsl:result-document` to update the ingredients. 17 | 18 | Code hints: 19 | * Recall that you can get the value of the option that the user selected 20 | from the `target.value` property with: 21 | ``` 22 | ixsl:get(ixsl:event(), 'target.value') 23 | ``` 24 | 25 | ## Exercise 9 (continued) 26 | 27 | Update your answer to exercise 9 so that it only updates the 28 | individual elements that need to be changed. 29 | 30 | Code hints: 31 | * If the current context item is a node “under” `ixsl:page()`, you can 32 | update it directly with the special href value “?.”. For example: 33 | 34 | ``` 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /exercises/ex09/ex09.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercises/ex10/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 10 2 | 3 | Examine the stylesheet `ex10.xsl`. What do you think it does? 4 | How will the browser respond? 5 | 6 | Run it and find out: 7 | 8 | ``` 9 | http://localhost:9000/recipes/pizza.html?exercise=ex10 10 | ``` 11 | 12 | (It doesn’t matter which recipe you use.) 13 | -------------------------------------------------------------------------------- /exercises/ex10/ex10.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

All your base are belong to us.

24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /exercises/ex11/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 11 2 | 3 | Each recipe has a category. The “endpoint” ¹ 4 | 5 | ``` 6 | http://localhost:9000/recipes/categories.json 7 | ``` 8 | 9 | returns a JSON payload that identifies each recipe by category: 10 | 11 | ``` 12 | { 13 | "main": ["beef-stroganof.html", "mac-n-cheese.html", "marys-beef-stew.html", "pizza.html"], 14 | "side": ["rice-a-roni.html"], 15 | "candy": ["treacle-taffy.html"] 16 | } 17 | ``` 18 | 19 | Write a stylesheet that will retrieve this document and let the user 20 | navigate between categories and recipes. 21 | 22 | * Start with the categories HTML file: http://localhost:9000/recipes/categories.html 23 | * Write a stylesheet that formats the “`categories`” division with the list of categories. 24 | * If you have time, make the stylesheet display all of the recipes in each category when a category is selected. 25 | 26 | ¹ This isn’t an endpoint in any real sense because we don’t have any sort 27 | of database in this tutorial. It’s just a JSON document. But the principles 28 | are exactly the same. 29 | -------------------------------------------------------------------------------- /exercises/ex11/ex11.xsl: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /exercises/ex12/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 12 (Ambitious) 2 | 3 | Pretty much everyone outside North America is going to find these 4 | recipes confusing. Tablespoons of butter? Cups of flour? What is wrong 5 | with these Americans? 6 | 7 | * Write a stylesheet that adds a “conversion” menu next to the servings menu. 8 | * Allow the user to select either “Imperial” or “Metric” units. 9 | * For metric, conversions: 10 | * A `tsp` is about 6ml 11 | * A `Tbs` is 15ml 12 | * A `cup` is 284ml 13 | * A `lb` is 0.454kg 14 | * An `oz` is 28.35g 15 | * A `qt` is 1.137ℓ 16 | * An `in` is 2.54cm 17 | * To convert °F to °C, subtract 32 and then multiply by 5/9. 18 | * For imperial, you can just display the original values 19 | -------------------------------------------------------------------------------- /exercises/ex12/ex12.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercises/ex13/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 13 (More ambitious) 2 | 3 | Imperial to metric conversions are only half the battle. No rational 4 | person measures butter or flour by volume. 5 | 6 | Redraft your solution to exercise 12 by first loading the conversion 7 | guide in `http://localhost:9000/lib/conversions.json` using 8 | `ixsl:schedule-action`. 9 | 10 | For each ingredient, the conversion table gives the conversion factor 11 | and the resulting units. So, for example, 3 Tbs of butter is about 43g. 12 | -------------------------------------------------------------------------------- /exercises/ex13/ex13.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | serverPort=9000 2 | version=1.0.4 3 | dockerImage=ghcr.io/saxonica/saxon-js-tutorial-2021 4 | saxonJsVersion=2.6 5 | xsltCompiler=XJ 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saxonica/SaxonJS-Tutorial-2021/0128341f83c9f19eb7fdab484bc3adc8d0e6cb7e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /hello/css/hello.css: -------------------------------------------------------------------------------- 1 | /* Simple CSS */ 2 | 3 | html { 4 | font-size: 36pt; 5 | max-width: 50em; 6 | line-height: 1.5; 7 | } 8 | 9 | body { 10 | display: grid; 11 | grid-template-rows: 1fr auto 2fr; 12 | grid-template-columns: 100%; 13 | } 14 | 15 | #hello { 16 | text-align: center; 17 | } 18 | 19 | #button { 20 | text-align: center; 21 | } 22 | -------------------------------------------------------------------------------- /hello/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello, world 4 | 5 | 7 | 8 | 9 | 11 | 13 | 14 | 15 |
16 |
17 |

Hello, world.

18 |
19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /hello/js/start.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let options = { stylesheetLocation: "/hello/xslt/stylesheet.sef.json" }; 4 | 5 | SaxonJS.transform(options, "async"); 6 | -------------------------------------------------------------------------------- /hello/xslt/stylesheet.sef.json: -------------------------------------------------------------------------------- 1 | {"N":"package","version":"30","packageVersion":"1","saxonVersion":"Saxon-JS 2.3","target":"JS","targetVersion":"2","name":"TOP-LEVEL","relocatable":"true","buildDateTime":"2021-10-07T14:53:45.154Z","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","C":[{"N":"co","id":"0","uniform":"true","binds":"1","C":[{"N":"template","flags":"os","baseUri":"file:///src/xslt/stylesheet.xsl","module":"stylesheet.xsl","slots":"200","name":"Q{http://www.w3.org/1999/XSL/Transform}initial-template","line":"14","expand-text":"false","sType":"* ","C":[{"N":"callT","bSlot":"0","sType":"* ","name":"Q{}hello","role":"body","line":"15"}]}]},{"N":"co","binds":"","id":"1","uniform":"true","C":[{"N":"template","flags":"os","baseUri":"file:///src/xslt/stylesheet.xsl","module":"stylesheet.xsl","slots":"200","name":"Q{}hello","line":"30","expand-text":"false","sType":"* ","C":[{"N":"sequence","role":"body","sType":"* ","C":[{"N":"resultDoc","sType":"0 ","line":"31","local":"method=Q{http://saxonica.com/ns/interactiveXSLT}replace-content\n","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#hello","role":"href"},{"N":"elem","name":"p","sType":"1NE nQ{http://www.w3.org/1999/xhtml}p ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"32","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Hello, world."}]}]}]},{"N":"resultDoc","sType":"0 ","line":"34","local":"method=Q{http://saxonica.com/ns/interactiveXSLT}replace-content\n","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#button","role":"href"},{"N":"elem","name":"button","sType":"1NE nQ{http://www.w3.org/1999/xhtml}button ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"35","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Click me."}]}]}]}]}]}]},{"N":"co","binds":"","id":"2","C":[{"N":"mode","onNo":"TC","flags":"","patternSlots":"0","prec":""}]},{"N":"co","id":"3","binds":"1","C":[{"N":"mode","onNo":"TC","flags":"","patternSlots":"0","name":"Q{http://saxonica.com/ns/interactiveXSLT}onclick","prec":"","C":[{"N":"templateRule","rank":"0","prec":"0","seq":"0","ns":"xml=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ =http://www.w3.org/1999/xhtml","minImp":"0","flags":"s","baseUri":"file:///src/xslt/stylesheet.xsl","slots":"200","line":"18","module":"stylesheet.xsl","expand-text":"false","match":"button","prio":"0","matches":"NE u[NE nQ{}button,NE nQ{http://www.w3.org/1999/xhtml}button]","C":[{"N":"p.nodeTest","role":"match","test":"NE u[NE nQ{}button,NE nQ{http://www.w3.org/1999/xhtml}button]","sType":"1NE u[NE nQ{}button,NE nQ{http://www.w3.org/1999/xhtml}button]","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ "},{"N":"sequence","role":"action","sType":"* ","C":[{"N":"resultDoc","sType":"0 ","line":"19","local":"method=Q{http://saxonica.com/ns/interactiveXSLT}replace-content\n","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#hello","role":"href"},{"N":"elem","name":"p","sType":"1NE nQ{http://www.w3.org/1999/xhtml}p ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"20","C":[{"N":"valueOf","sType":"1NT ","C":[{"N":"str","sType":"1AS ","val":"Thanks."}]}]}]},{"N":"resultDoc","sType":"0 ","line":"22","local":"method=Q{http://saxonica.com/ns/interactiveXSLT}replace-content\n","global":"encoding=utf-8\nhtml-version=5\nmethod=html\nindent=no\n","C":[{"N":"str","sType":"1AS ","val":"#button","role":"href"},{"N":"elem","name":"p","sType":"1NE nQ{http://www.w3.org/1999/xhtml}p ","nsuri":"http://www.w3.org/1999/xhtml","namespaces":"","role":"content","line":"23","C":[{"N":"empty","sType":"0 "}]}]},{"N":"ifCall","name":"Q{http://saxonica.com/ns/interactiveXSLT}schedule-action","sType":"0","line":"25","C":[{"N":"int","val":"1000","sType":"1ADI","ns":"= xml=~ fn=~ xsl=~ ixsl=~ h=http://www.w3.org/1999/xhtml js=~ saxon=~ xs=~ ","line":"25"},{"N":"empty","sType":"0 "},{"N":"callT","bSlot":"0","sType":"* ","name":"Q{}hello","line":"26"}]}]}]}]}]},{"N":"overridden"},{"N":"output","C":[{"N":"property","name":"Q{http://saxon.sf.net/}stylesheet-version","value":"30"},{"N":"property","name":"method","value":"html"},{"N":"property","name":"html-version","value":"5"},{"N":"property","name":"encoding","value":"utf-8"},{"N":"property","name":"indent","value":"no"}]},{"N":"decimalFormat"}],"Σ":"e13f4a5"} -------------------------------------------------------------------------------- /hello/xslt/stylesheet.xsl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

Thanks.

21 |
22 | 23 |

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |

Hello, world.

33 |
34 | 35 | 36 | 37 |
38 | 39 |
40 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the license file. This reduces the chances that 2 | # it'll accidentally get committed to a public repository. 3 | saxon-license.lic 4 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # Ancillary files 2 | 3 | This directory contains a few ancillary materials. 4 | 5 | This is the directory where the tutorial expects to find the SaxonEE 6 | license. If you are using SaxonEE to compile stylesheets, copy your license here. 7 | 8 | -------------------------------------------------------------------------------- /lib/conversions.json: -------------------------------------------------------------------------------- 1 | 2 | 3 | { 4 | "*": { "tsp": { "factor": 5.9194, "units": "ml" }, 5 | "Tbs": { "factor": 15.0, "units": "ml" }, 6 | "cup": { "factor": 284.0, "units": "ml" }, 7 | "in": { "factor": 2.54, "units": "cm" }, 8 | "lb": { "factor": 0.4536, "units": "kg" }, 9 | "oz": { "factor": 28.35, "units": "g" }, 10 | "qt": { "factor": 1.137, "units": "ℓ" }, 11 | "pinch": { "factor": 1, "units": "pinch" } 12 | }, 13 | "butter": { 14 | "Tbs": { 15 | "factor": 14.1748, 16 | "units": "g" 17 | }, 18 | "cup": { 19 | "factor": 226.796, 20 | "units": "g" 21 | }, 22 | "oz": { 23 | "factor": 28.346, 24 | "units": "g" 25 | } 26 | }, 27 | "flour": { 28 | "Tbs": { 29 | "factor": 7.81, 30 | "units": "g" 31 | }, 32 | "cup": { 33 | "factor": 125, 34 | "units": "g" 35 | } 36 | }, 37 | "pasta": { 38 | "cup": { 39 | "factor": 100, 40 | "units": "g" 41 | }, 42 | "lb": { "factor": 0.4536, "units": "kg" } 43 | }, 44 | "frozen peas": { 45 | "cup": { 46 | "factor": 134, 47 | "units": "g" 48 | } 49 | }, 50 | "rice": { 51 | "cup": { 52 | "factor": 200, 53 | "units": "g" 54 | } 55 | }, 56 | "cinnamon": { 57 | "tsp": { 58 | "factor": 2.6, 59 | "units": "g" 60 | } 61 | }, 62 | "pepper": { 63 | "tsp": { 64 | "factor": 2.3, 65 | "units": "g" 66 | } 67 | }, 68 | "miso paste": { 69 | "tsp": { 70 | "factor": 5.67, 71 | "units": "g" 72 | } 73 | }, 74 | "salt": { 75 | "tsp": { 76 | "factor": 5.69, 77 | "units": "g" 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/utils.xsl: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | MAINTAINER Norman Walsh 4 | 5 | RUN npm install -g npm@7.24.2 6 | 7 | WORKDIR /node 8 | 9 | ADD package-lock.json . 10 | ADD package.json . 11 | ADD server.js . 12 | 13 | RUN npm install 14 | 15 | CMD ["node", "server.js"] 16 | -------------------------------------------------------------------------------- /node/christmas.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SaxonJS = require('saxon-js'); 4 | 5 | const DECEMBER = 11; // 0-based counting! 6 | 7 | const today = new Date(); 8 | let year = today.getFullYear(); 9 | 10 | if (today.getMonth() == DECEMBER && today.getDate() >= 25) { 11 | year += 1; 12 | } 13 | 14 | let christmas = `${year}-12-25`; 15 | let options = { "params": {"Q{}christmas": christmas} }; 16 | 17 | let days = SaxonJS.XPath.evaluate( 18 | `let $duration := xs:date($christmas) - current-date() 19 | return 20 | if ($duration <= xs:dayTimeDuration("P1D")) 21 | then 'Christmas is TOMORROW!' 22 | else 'It''s ' 23 | || days-from-duration($duration) 24 | || ' days ''til Christmas'`, 25 | null, options); 26 | 27 | console.log(days); 28 | -------------------------------------------------------------------------------- /node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "express": "^4.17.1", 4 | "saxon-js": "^2.6.0", 5 | "xslt3": "^2.6.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /node/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const querystring = require('querystring'); 7 | const { exec } = require('child_process'); 8 | 9 | const app = express(); 10 | let server; 11 | 12 | // Where are we? 13 | const root = path.resolve(process.env.ROOTDIR || ".."); 14 | const port = process.env.PORT || "9000"; 15 | 16 | app.disable('x-powered-by'); 17 | 18 | app.use(function timeLog (req, res, next) { 19 | console.log(`${req.method} ${req.originalUrl}`); 20 | next(); 21 | }); 22 | 23 | app.post('/stop', (request, response) => { 24 | response.status(200).send("OK"); 25 | server.close(); 26 | }); 27 | 28 | app.get(/^.*/, (request, response) => { 29 | let fn = `${root}${request.url}`; 30 | show_file(request, response, fn); 31 | }); 32 | 33 | function show_file(request, response, fn) { 34 | let pos = fn.indexOf("?"); 35 | let query = {}; 36 | if (pos >= 0) { 37 | query = querystring.parse(fn.substring(pos+1)); 38 | fn = fn.substring(0, pos); 39 | } 40 | 41 | fs.readFile(fn, null, (error, data) => { 42 | if (error) { 43 | if (error.code == 'EISDIR') { 44 | show_dir(request, response, fn); 45 | } else { 46 | if (fn === `${root}/favicon.ico`) { 47 | fakeicon(request, response); 48 | return; 49 | } else { 50 | console.log(`Error reading ${fn}:`); 51 | console.log(error); 52 | } 53 | response.status(404).send("Not found."); 54 | } 55 | return; 56 | } 57 | 58 | let ext = "unknown"; 59 | let pos = fn.lastIndexOf("."); 60 | if (pos >= 0) { 61 | ext = fn.substring(pos+1); 62 | response.type(ext); 63 | } 64 | 65 | if ("answer" in query || "exercise" in query) { 66 | patch(request, response, data, query); 67 | } else { 68 | response.status(200).send(data); 69 | } 70 | }); 71 | } 72 | 73 | function patch(request, response, data, query) { 74 | let html = data.toString(); 75 | let pos = html.indexOf(""); 76 | 77 | let stylebase = ""; 78 | let params = {}; 79 | 80 | if ("answer" in query) { 81 | stylebase = `/answers/${query['answer']}/${query['answer']}`; 82 | delete query['answer']; 83 | } else { 84 | stylebase = `/exercises/${query['exercise']}/${query['exercise']}`; 85 | delete query['exercise']; 86 | } 87 | 88 | let script = "\n"; 89 | script += "\n"; 102 | 103 | return checkCompile(request, response, root + stylebase, 104 | html.substring(0, pos) + script + html.substring(pos)); 105 | } 106 | 107 | function checkCompile(request, response, stylebase, html) { 108 | const xsl = `${stylebase}.xsl`; 109 | const sef = `${stylebase}.sef.json`; 110 | 111 | try { 112 | let sefstat = fs.statSync(sef); 113 | let xslstat = fs.statSync(xsl); 114 | if (sefstat.mtime < xslstat.mtime) { 115 | return compile(request, response, stylebase, html); 116 | } 117 | } catch (e) { 118 | return compile(request, response, stylebase, html); 119 | } 120 | 121 | response.status(200).send(html); 122 | } 123 | 124 | function compile(request, response, stylebase, html) { 125 | let cmd = "node node_modules/xslt3/xslt3.js -t " 126 | + `-xsl:${stylebase}.xsl ` 127 | + `-export:${stylebase}.sef.json ` 128 | + "-nogo -ns:##html5"; 129 | 130 | console.log("Compiling XSL with xslt3.js"); 131 | const child = exec(cmd, (error, stdout) => { 132 | if (stdout) { 133 | console.log(stdout); 134 | } 135 | if (error) { 136 | console.log(error); 137 | } 138 | response.status(200).send(html); 139 | }); 140 | } 141 | 142 | function show_dir(request, response, dir) { 143 | if (!request.url.endsWith("/")) { 144 | response.location(request.url + "/"); 145 | response.status(301).send("Redirect to " + request.url + "/"); 146 | } else { 147 | let index = dir + "index.html"; 148 | fs.readFile(index, null, (error, data) => { 149 | if (error) { 150 | let displayDir = dir.substring(root.length); 151 | let html = ""; 152 | html += `

Directory listing for ${displayDir}

`; 153 | html += "
"; 154 | html += ""; 173 | html += "
"; 174 | html += ""; 175 | response.status(200).send(html); 176 | return; 177 | } 178 | show_file(request, response, index); 179 | }); 180 | } 181 | } 182 | 183 | function fakeicon(request, response) { 184 | const base64 = ["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xh", 185 | "BQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB", 186 | "X1BMVEXn8PHk7vDl7/Dm8PHf6u7F0eDCz97Bzt7l7/Hg6+7X4unO2uTD0N6wvtSy", 187 | "wda4xtm/zN3h7O7Azt2WqMaClrx8kbl9kbl7j7h6j7h/k7qNn8KpudHj7e+wv9R5", 188 | "jbeBlbultc6+zNyvvtSmts+TpcV4jLacrcrAzd3o8PLW4ejE0d/f6e3Cz993jLbo", 189 | "8fLU3+eAlLt5jrfj7vDE0eC+y9yjs82fsMu3xdjh6+6nt892i7Z+krqcrsq1w9e7", 190 | "ydu/zd3Dz9+8ytp8kLiDlryMn8GdrsqxwNXg6u7G0+CUpsaIm791irW6x9rm7/Hd", 191 | "5+ze6O3E0N+ltc+BlLtzibSXqMfAzd7k7fCktM2uvdPd6O3D0N+gsMxziLS9yty2", 192 | "xde7yNuPosN6jreVp8avvtO5x9mrutKmtc+ZqsiEmL12i7XD0d5+krmfsMzCzt7b", 193 | "5uvV4OjK1+Kzwtazwte9y9z///9ckkS+AAAAAWJLR0R0322obQAAAAd0SU1FB+UK", 194 | "DQ06HAv38YgAAAD1SURBVBjTY2BgBAImZhZWNjZ2EGAACnBwcnHz8PLxC7BBBASF", 195 | "hEVExcQlJKWkBUACMrJy8gqKbErKKhKqaursDBqaInJaHMzarDrsyroSfDoMevoG", 196 | "hmxGzNrG7OwmpmbmbAwMFpZW1ja2bPx29joOOmBDtR1VDFR1nZxdXN3AhjJyMLvr", 197 | "e7h5ell7S6j4sDP4+nH5M3EwswSY8AcGBYeEMoSFS0YwMTJGRrGzRUXHeMUyaMR5", 198 | "y7NZAJ2uwxafEKOizsCgbSmXmJSckpqWnpHpZQu0ltEiy9VLUlRcItsrxycX4jmZ", 199 | "vPyCwiL+YqjnQN7n0IZ5HwDkUSR0fRGbDQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAy", 200 | "MS0xMC0xM1QxMzo1ODoxNyswMDowMJv1GdsAAAAldEVYdGRhdGU6bW9kaWZ5ADIw", 201 | "MjEtMTAtMTNUMTM6NTg6MDkrMDA6MDB2PdqkAAAAAElFTkSuQmCC"].join(""); 202 | const buf = Buffer.from(base64, 'base64'); 203 | response.type("png"); 204 | response.status(200).send(buf); 205 | }; 206 | 207 | server = app.listen(port, () => { 208 | console.log(`Node.js Express server listening on port ${port}.`); 209 | console.log(`Server root directory: ${root}`); 210 | }); 211 | -------------------------------------------------------------------------------- /presentation/css/docbook-paged.css: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is docbook-paged.css. 4 | * 5 | * See https://xsltng.docbook.org/ 6 | * 7 | * This stylesheet should be followed by the stylesheet for the particular 8 | * presentation style: paper, book, etc. 9 | * 10 | */ 11 | 12 | @page { 13 | size: A4; 14 | margin: 1in; 15 | counter-reset: footnote; 16 | } 17 | 18 | @page title { 19 | @top-center { 20 | content: ""; 21 | } 22 | @bottom-right { 23 | content: ""; 24 | } 25 | @bottom-left { 26 | content: ""; 27 | } 28 | } 29 | 30 | @page normal-flow { 31 | @footnote { 32 | float: bottom page; 33 | border-top: thin solid black; 34 | border-length: 30%; 35 | padding-top: 0.5em; 36 | } 37 | } 38 | 39 | @page normal-flow:blank { 40 | @top-center { 41 | content: ""; 42 | } 43 | @bottom-right { 44 | content: ""; 45 | } 46 | @bottom-left { 47 | content: ""; 48 | } 49 | } 50 | 51 | html { 52 | page: normal-flow; 53 | } 54 | 55 | .book > header { 56 | padding: 0; 57 | border: inherit; 58 | } 59 | 60 | h1, h2 { 61 | string-set: ComponentTitle content() 62 | } 63 | 64 | header { 65 | page-break-inside: avoid; 66 | } 67 | 68 | .example header { 69 | page-break-before: avoid; 70 | } 71 | 72 | .book .list-of-titles { 73 | counter-reset: page; 74 | } 75 | 76 | .book .lot { 77 | margin-top: 1em; 78 | } 79 | 80 | .part .list-of-titles { 81 | display: none; 82 | } 83 | 84 | /* ============================================================ */ 85 | 86 | .toc a::after { 87 | content: leader(dotted) " " target-counter(attr(href url), page); 88 | } 89 | 90 | /* ============================================================ */ 91 | 92 | .footnote { 93 | float: footnote; 94 | margin-left: 0.75rem; 95 | font-size: 1rem; 96 | font-weight: normal; 97 | font-family: "Noto Serif", serif, "Arial Unicode"; 98 | } 99 | 100 | /* ============================================================ */ 101 | 102 | .indexref::before { 103 | content: target-counter(attr(href url), page); 104 | } 105 | 106 | /* ============================================================ */ 107 | 108 | details { 109 | display: none; 110 | } 111 | 112 | a, a:visited { 113 | text-decoration: none; 114 | } 115 | 116 | .error * { 117 | background-color: inherit; 118 | color: inherit; 119 | padding: inherit; 120 | } 121 | 122 | span.error { 123 | display: inline; 124 | border: none; 125 | padding: inherit; 126 | margin: inherit; 127 | } 128 | 129 | span.error::before { 130 | content: none; 131 | } 132 | 133 | span.error::after { 134 | content: none; 135 | } 136 | 137 | span.error.broken-link::after { 138 | content: none; 139 | } 140 | 141 | .annotations { 142 | border-top: none; 143 | page-break-before: always; 144 | } 145 | 146 | .xlink-arc-list { 147 | font-style: normal; 148 | } 149 | 150 | .xlink-arc-delim { 151 | display: inline; 152 | } 153 | 154 | .nhrefs .arc a::after { 155 | content: " (" attr(href) ")"; 156 | } 157 | 158 | /* Work around bug in AntennaHouse V7.0 MR2 for MacOSX */ 159 | span.footnote p { 160 | margin-bottom: 0pt; 161 | } 162 | -------------------------------------------------------------------------------- /presentation/css/docbook-toc.css: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is docbook-toc.css. 4 | * 5 | * See https://xsltng.docbook.org/ 6 | * 7 | */ 8 | 9 | nav.tocopen { 10 | position: fixed; 11 | top: 2px; 12 | right: 1em; 13 | color: inherit; 14 | z-index: 1; 15 | cursor: pointer; 16 | font-size: 14pt; 17 | display: none; 18 | } 19 | 20 | nav.toc { 21 | border-left: 1px solid var(--border-color); 22 | background-color: var(--focused-color); 23 | color: var(--on-surface-color); 24 | height: 100%; 25 | width: 0; 26 | position: fixed; 27 | z-index: 2; 28 | top: 0; 29 | right: 0; 30 | overflow-x: scroll; 31 | padding: 0; 32 | margin: 0; 33 | font-size: 12pt; 34 | opacity: 1; 35 | padding-left: 0; 36 | } 37 | 38 | nav.toc.slide { 39 | transition: 0.33s; 40 | } 41 | 42 | nav.toc header { 43 | height: 4rem; 44 | font-family: var(--title-family); 45 | font-size: 1.25em; 46 | } 47 | 48 | nav.toc header .close { 49 | float: right; 50 | cursor: pointer; 51 | font-weight: bold; 52 | font-size: 16pt; 53 | } 54 | 55 | nav.toc div { 56 | overflow: scroll; 57 | height: calc(100% - 4rem); 58 | } 59 | 60 | nav.toc a, 61 | nav.toc a:visited { 62 | color: inherit; 63 | text-decoration: none; 64 | } 65 | 66 | nav.toc .refpurpose { 67 | display: none; 68 | } 69 | 70 | nav.toc li { 71 | text-indent: -1em; 72 | padding-left: 1em; 73 | } 74 | 75 | nav.toc li a.found::before { 76 | color: #ff7f7f; 77 | content: "►"; 78 | } 79 | 80 | .ptoc-search { 81 | margin: 0; 82 | padding: 0; 83 | } 84 | -------------------------------------------------------------------------------- /presentation/css/oxy-markup.css: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is oxy-markup.css. This CSS supports oXygen change markup 4 | * processing instructions, converted to DocBook proper and then to 5 | * HTML classes/spans. 6 | * 7 | * See https://xsltng.docbook.org/ 8 | * 9 | */ 10 | 11 | .oxy_insert { 12 | background-color: transparent; 13 | color: #009900; 14 | } 15 | 16 | .oxy_insert::before { 17 | content: var(--revadded-before); 18 | } 19 | 20 | .oxy_insert::after { 21 | content: var(--revadded-after); 22 | } 23 | 24 | .oxy_delete { 25 | background-color: transparent; 26 | color: #cc3333; 27 | text-decoration: line-through; 28 | } 29 | 30 | .oxy_delete::before { 31 | content: var(--revdeleted-before); 32 | } 33 | 34 | .oxy_delete::after { 35 | content: var(--revdeleted-after); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /presentation/css/presentation.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | @import url("https://fonts.googleapis.com/css?family=B612+Mono&display=swap"); 3 | @import url("https://fonts.googleapis.com/css?family=Noto+Sans&display=swap"); 4 | @import url("https://fonts.googleapis.com/css?family=Noto+Serif&display=swap"); 5 | @import url('https://fonts.googleapis.com/css2?family=Oswald&display=swap'); 6 | } 7 | 8 | :root { 9 | --symbol-fonts: "Arial Unicode", "Apple Symbols", "Symbol", "Symbola_hint"; 10 | --body-family: "Noto Serif", serif, var(--symbol-fonts); 11 | --title-family: "Oswald", sans-serif, var(--symbol-fonts); 12 | --mono-family: "B612 Mono", monospace, var(--symbol-fonts); 13 | } 14 | 15 | html { 16 | font-size: 16pt; 17 | } 18 | 19 | main { 20 | max-width: 90%; 21 | } 22 | 23 | article header { 24 | min-height: 700px; 25 | } 26 | 27 | h1 { 28 | margin-top: 2rem; 29 | } 30 | 31 | article header h1 { 32 | padding-top: 1em; 33 | line-height: 150%; 34 | } 35 | 36 | article header .affiliation { 37 | font-size: 1.5rem; 38 | padding-top: 1rem; 39 | } 40 | 41 | article header .email { 42 | text-align: center; 43 | font-size: 1.2rem; 44 | } 45 | 46 | article header .icons { 47 | padding-top: 1em; 48 | text-align: center; 49 | } 50 | 51 | #xavier { 52 | width: 500px; 53 | float: right; 54 | } 55 | 56 | img.float { 57 | width: 350px; 58 | float: right; 59 | } 60 | 61 | .list-of-titles { 62 | display: none; 63 | } 64 | 65 | nav.tocopen span { 66 | display: none; 67 | } 68 | 69 | nav.tocopen:before { 70 | content: "☰"; 71 | } 72 | 73 | /* Hacks for the home page */ 74 | 75 | .author h3 { 76 | text-align: center; 77 | } 78 | 79 | p.affiliation { 80 | margin-top: 0.1em; 81 | text-align: center; 82 | } 83 | 84 | p.pubdate { 85 | text-align: center; 86 | margin-bottom: 0; 87 | } 88 | 89 | .confgroup { 90 | font-size: 1.2rem; 91 | text-align: center; 92 | } 93 | 94 | .conftitle { 95 | font-style: italic; 96 | } 97 | 98 | .confdates::before { 99 | content: ", "; 100 | } 101 | 102 | article.homepage header { 103 | text-align: left; 104 | } 105 | 106 | article.homepage header h1 { 107 | margin-top: 0; 108 | font-size: 2.4rem; 109 | } 110 | 111 | .chapter, 112 | .appendix { 113 | font-size: 28pt; 114 | line-height: 1.4; 115 | } 116 | 117 | .chapter h2, 118 | .appendix h2 { 119 | font-size: 36pt; 120 | } 121 | 122 | .popup-annotation-body { 123 | width: 80%; 124 | max-height: 90%; 125 | font-size: 24pt; 126 | } 127 | 128 | blockquote { 129 | width: 75%; 130 | padding: 0px 80px; 131 | position: relative; 132 | margin-bottom: 28px; 133 | } 134 | 135 | blockquote::before, 136 | blockquote::after { 137 | top: 0; 138 | bottom: 0; 139 | width: 25px; 140 | content: ''; 141 | position: absolute; 142 | } 143 | 144 | blockquote::before { 145 | right: 100%; 146 | } 147 | 148 | blockquote::after { 149 | left: 100%; 150 | } 151 | 152 | blockquote p { 153 | margin: 0; 154 | } 155 | 156 | blockquote p::before { 157 | top: -48px; 158 | left: 8px; 159 | content: '“'; 160 | font-size: 4em; 161 | position: absolute; 162 | color: rgb(193, 206,221); 163 | } 164 | 165 | blockquote .attribution { 166 | padding-top: 1.5rem; 167 | } 168 | 169 | /* ============================================================ */ 170 | 171 | nav.top { 172 | width: 100%; 173 | background-color: rgb(193, 206,221); 174 | display: grid; 175 | grid-template-columns: 33% 34% 33%; 176 | } 177 | 178 | nav .title { 179 | font-weight: normal; 180 | } 181 | 182 | nav .fas { 183 | font-weight: normal; 184 | } 185 | 186 | nav.top .nav { 187 | float: left; 188 | margin-left: 0.25rem; 189 | } 190 | 191 | nav.top .text { 192 | text-align: center; 193 | font-size: 18px; 194 | } 195 | 196 | nav.bottom { 197 | min-height: 1.0rem; 198 | margin-top: 2rem; 199 | margin-bottom: 0px; 200 | padding-top: px; 201 | padding-bottom: 10px; 202 | padding-left: 1rem; 203 | padding-right: 1rem; 204 | border-top-style: solid; 205 | border-top-width: 1px; 206 | display: grid; 207 | font-size: 1.0rem; 208 | } 209 | 210 | .navrow { 211 | display: grid; 212 | grid-template-columns: 33% 34% 33%; 213 | } 214 | 215 | .navleft { 216 | text-align: left; 217 | grid-column: 1; 218 | } 219 | 220 | .navmiddle { 221 | text-align: center; 222 | grid-column: 2; 223 | } 224 | 225 | .navright { 226 | text-align: right; 227 | grid-column: 3; 228 | } 229 | 230 | .navrow .navtitle { 231 | font-size: 18px; 232 | } 233 | 234 | nav .version { 235 | display: none; 236 | } 237 | 238 | nav .title { 239 | color: inherit; 240 | background-color: inherit; 241 | } 242 | 243 | .infofooter { 244 | display: none; 245 | } 246 | 247 | .home .infofooter { 248 | display: block; 249 | } 250 | 251 | .sidebar code { 252 | background-color: var(--sidebar-color); 253 | } 254 | 255 | .mediaobject { 256 | text-align: center; 257 | } 258 | 259 | .fa-solid { 260 | font-size: 30pt; 261 | } 262 | 263 | .toc .fa-solid { 264 | display: none; 265 | } 266 | 267 | .appendix .section .section { 268 | border-top: 1px solid black; 269 | } 270 | 271 | h3 { font-weight: 400; 272 | margin-top: 2.1rem; 273 | margin-bottom: 2rem; 274 | font-size: 2.2rem; 275 | line-height: 1; 276 | } 277 | 278 | h4 { font-weight: 400; 279 | margin-top: 2.1rem; 280 | margin-bottom: 2rem; 281 | font-size: 2.2rem; 282 | line-height: 1; 283 | } 284 | 285 | .itemizedlist .title { 286 | font-weight: normal; 287 | } 288 | 289 | #thanks h1 .number { 290 | display: none; 291 | } 292 | -------------------------------------------------------------------------------- /presentation/css/print-book.css: -------------------------------------------------------------------------------- 1 | /* "Book" style; recto/verso pages, chapters start on the right */ 2 | 3 | @page normal-flow::right { 4 | @top-center { 5 | content: string(ComponentTitle); 6 | } 7 | @bottom-right { 8 | content: counter(page); 9 | } 10 | } 11 | 12 | @page normal-flow::left { 13 | @top-center { 14 | content: string(ComponentTitle); 15 | } 16 | @bottom-left { 17 | content: counter(page); 18 | } 19 | } 20 | 21 | @page normal-flow:first::right { 22 | @top-center { 23 | content: ""; 24 | } 25 | @bottom-right { 26 | content: counter(page); 27 | } 28 | } 29 | 30 | @page normal-flow:first::left { 31 | @top-center { 32 | content: ""; 33 | } 34 | @bottom-left { 35 | content: counter(page); 36 | } 37 | } 38 | 39 | html { 40 | page: title; 41 | } 42 | 43 | main { 44 | page: normal-flow; 45 | } 46 | 47 | body > header { 48 | page-break-after: right; 49 | } 50 | 51 | .book > header .revhistory { 52 | page-break-before: always; 53 | } 54 | 55 | .book > header .copyright { 56 | float: bottom; 57 | } 58 | 59 | main { 60 | page-break-before: always; 61 | } 62 | 63 | .book .list-of-titles { 64 | break-before: right; 65 | } 66 | 67 | .book .chapter, 68 | .book .preface, 69 | .book .appendix, 70 | .book .refentry, 71 | .book .part, 72 | .book .partintro, 73 | .book .dedication, 74 | .book .glossary, 75 | .book .bibliography, 76 | .book .index, 77 | .book .colophon { 78 | break-before: right; 79 | page: normal-flow; 80 | } 81 | -------------------------------------------------------------------------------- /presentation/css/print-paper.css: -------------------------------------------------------------------------------- 1 | /* "Paper" style; symmetrical pages breaks between chapters */ 2 | 3 | @page normal-flow { 4 | @top-center { 5 | content: string(ComponentTitle); 6 | } 7 | @bottom-center { 8 | content: counter(page); 9 | } 10 | } 11 | 12 | @page normal-flow:first { 13 | @top-center { 14 | content: ""; 15 | } 16 | } 17 | 18 | .book > header { 19 | page-break-after: always; 20 | } 21 | 22 | .book .list-of-titles { 23 | break-before: always; 24 | } 25 | 26 | .book .list-of-titles div .title { 27 | string-set: ComponentTitle content() 28 | } 29 | 30 | .book .article, 31 | .book .chapter, 32 | .book .preface, 33 | .book .appendix, 34 | .book .refentry, 35 | .book .part, 36 | .book .partintro, 37 | .book .dedication, 38 | .book .glossary, 39 | .book .bibliography, 40 | .book .index, 41 | .book .colophon { 42 | break-before: always; 43 | page: normal-flow; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /presentation/css/print-verso.css: -------------------------------------------------------------------------------- 1 | @import url("print-book.css"); 2 | 3 | body > header { 4 | page-break-after: always; 5 | } 6 | 7 | .book .list-of-titles { 8 | break-before: always; 9 | } 10 | 11 | .book .chapter, 12 | .book .preface, 13 | .book .appendix, 14 | .book .refentry, 15 | .book .part, 16 | .book .partintro, 17 | .book .dedication, 18 | .book .glossary, 19 | .book .bibliography, 20 | .book .index, 21 | .book .colophon { 22 | break-before: always; 23 | } 24 | -------------------------------------------------------------------------------- /presentation/css/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.20.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+apacheconf+bash+c+csharp+cpp+coffeescript+css-extras+diff+go+http+ini+java+json+json5+jsonp+kotlin+less+lua+makefile+markdown+markup-templating+nginx+objectivec+perl+php+php-extras+python+ruby+rust+scss+sql+swift+toml+typescript+xquery+yaml&plugins=line-highlight+line-numbers */ 3 | /** 4 | * prism.js default theme for JavaScript, CSS and HTML 5 | * Based on dabblet (http://dabblet.com) 6 | * @author Lea Verou 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: black; 12 | background: none; 13 | text-shadow: 0 1px white; 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | font-size: 1em; 16 | text-align: left; 17 | white-space: pre; 18 | word-spacing: normal; 19 | word-break: normal; 20 | word-wrap: normal; 21 | line-height: 1.5; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 34 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 35 | text-shadow: none; 36 | background: #b3d4fc; 37 | } 38 | 39 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 40 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 41 | text-shadow: none; 42 | background: #b3d4fc; 43 | } 44 | 45 | @media print { 46 | code[class*="language-"], 47 | pre[class*="language-"] { 48 | text-shadow: none; 49 | } 50 | } 51 | 52 | /* Code blocks */ 53 | pre[class*="language-"] { 54 | padding: 1em; 55 | margin: .5em 0; 56 | overflow: auto; 57 | } 58 | 59 | :not(pre) > code[class*="language-"], 60 | pre[class*="language-"] { 61 | background: #f5f2f0; 62 | } 63 | 64 | /* Inline code */ 65 | :not(pre) > code[class*="language-"] { 66 | padding: .1em; 67 | border-radius: .3em; 68 | white-space: normal; 69 | } 70 | 71 | .token.comment, 72 | .token.prolog, 73 | .token.doctype, 74 | .token.cdata { 75 | color: slategray; 76 | } 77 | 78 | .token.punctuation { 79 | color: #999; 80 | } 81 | 82 | .token.namespace { 83 | opacity: .7; 84 | } 85 | 86 | .token.property, 87 | .token.tag, 88 | .token.boolean, 89 | .token.number, 90 | .token.constant, 91 | .token.symbol, 92 | .token.deleted { 93 | color: #905; 94 | } 95 | 96 | .token.selector, 97 | .token.attr-name, 98 | .token.string, 99 | .token.char, 100 | .token.builtin, 101 | .token.inserted { 102 | color: #690; 103 | } 104 | 105 | .token.operator, 106 | .token.entity, 107 | .token.url, 108 | .language-css .token.string, 109 | .style .token.string { 110 | color: #9a6e3a; 111 | /* This background color was intended by the author of this theme. */ 112 | background: hsla(0, 0%, 100%, .5); 113 | } 114 | 115 | .token.atrule, 116 | .token.attr-value, 117 | .token.keyword { 118 | color: #07a; 119 | } 120 | 121 | .token.function, 122 | .token.class-name { 123 | color: #DD4A68; 124 | } 125 | 126 | .token.regex, 127 | .token.important, 128 | .token.variable { 129 | color: #e90; 130 | } 131 | 132 | .token.important, 133 | .token.bold { 134 | font-weight: bold; 135 | } 136 | .token.italic { 137 | font-style: italic; 138 | } 139 | 140 | .token.entity { 141 | cursor: help; 142 | } 143 | 144 | pre[data-line] { 145 | position: relative; 146 | padding: 1em 0 1em 3em; 147 | } 148 | 149 | .line-highlight { 150 | position: absolute; 151 | left: 0; 152 | right: 0; 153 | padding: inherit 0; 154 | margin-top: 1em; /* Same as .prism’s padding-top */ 155 | 156 | background: hsla(24, 20%, 50%,.08); 157 | background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 158 | 159 | pointer-events: none; 160 | 161 | line-height: inherit; 162 | white-space: pre; 163 | } 164 | 165 | .line-highlight:before, 166 | .line-highlight[data-end]:after { 167 | content: attr(data-start); 168 | position: absolute; 169 | top: .4em; 170 | left: .6em; 171 | min-width: 1em; 172 | padding: 0 .5em; 173 | background-color: hsla(24, 20%, 50%,.4); 174 | color: hsl(24, 20%, 95%); 175 | font: bold 65%/1.5 sans-serif; 176 | text-align: center; 177 | vertical-align: .3em; 178 | border-radius: 999px; 179 | text-shadow: none; 180 | box-shadow: 0 1px white; 181 | } 182 | 183 | .line-highlight[data-end]:after { 184 | content: attr(data-end); 185 | top: auto; 186 | bottom: .4em; 187 | } 188 | 189 | .line-numbers .line-highlight:before, 190 | .line-numbers .line-highlight:after { 191 | content: none; 192 | } 193 | 194 | pre[id].linkable-line-numbers span.line-numbers-rows { 195 | pointer-events: all; 196 | } 197 | pre[id].linkable-line-numbers span.line-numbers-rows > span:before { 198 | cursor: pointer; 199 | } 200 | pre[id].linkable-line-numbers span.line-numbers-rows > span:hover:before { 201 | background-color: rgba(128, 128, 128, .2); 202 | } 203 | 204 | pre[class*="language-"].line-numbers { 205 | position: relative; 206 | padding-left: 3.8em; 207 | counter-reset: linenumber; 208 | } 209 | 210 | pre[class*="language-"].line-numbers > code { 211 | position: relative; 212 | white-space: inherit; 213 | } 214 | 215 | .line-numbers .line-numbers-rows { 216 | position: absolute; 217 | pointer-events: none; 218 | top: 0; 219 | font-size: 100%; 220 | left: -3.8em; 221 | width: 3em; /* works for line-numbers below 1000 lines */ 222 | letter-spacing: -1px; 223 | border-right: 1px solid #999; 224 | 225 | -webkit-user-select: none; 226 | -moz-user-select: none; 227 | -ms-user-select: none; 228 | user-select: none; 229 | 230 | } 231 | 232 | .line-numbers-rows > span { 233 | display: block; 234 | counter-increment: linenumber; 235 | } 236 | 237 | .line-numbers-rows > span:before { 238 | content: counter(linenumber); 239 | color: #999; 240 | display: block; 241 | padding-right: 0.8em; 242 | text-align: right; 243 | } 244 | 245 | -------------------------------------------------------------------------------- /presentation/css/speaker-notes.css: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is speaker-notes.css. This CSS supports the notion of "speaker 4 | * notes" in the chunked output. It allows chunked output to be used 5 | * as a set of foils for a presentation. 6 | * 7 | * See https://xsltng.docbook.org/ 8 | * 9 | */ 10 | 11 | main .speaker-notes { 12 | display: none; 13 | } 14 | 15 | main .speaker-notes .foil-wrapper { 16 | float: right; 17 | width: 400px; 18 | } 19 | 20 | main .speaker-notes .foil { 21 | border: 1px solid #7f7f7f; 22 | border-radius: 4px; 23 | padding: 1em; 24 | width: 800px; 25 | transform: scale(0.5); 26 | transform-origin: 0 0; 27 | } 28 | 29 | main .speaker-notes .foil h1 { 30 | margin-top: 0; 31 | font-size: 24pt; 32 | text-align: center; 33 | } 34 | -------------------------------------------------------------------------------- /presentation/css/xlink.css: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is xlink.css providing support for multi-targeted links. 4 | * The "js" class is added by JavaScript. This enables graceful 5 | * fallback in the non-JS case. 6 | * 7 | * See https://xsltng.docbook.org/ 8 | * 9 | */ 10 | 11 | .xlink .source { 12 | border-bottom: 1px dotted black; 13 | cursor: pointer; 14 | } 15 | 16 | .xlink-arc-list { 17 | display: inline; 18 | font-style: normal; 19 | cursor: pointer; 20 | font-size: 70%; 21 | } 22 | 23 | .xlink-arc-list.js::before { 24 | content: " "; /* thin space */ 25 | } 26 | 27 | .nhrefs.js .xlink-arc-delim { 28 | display: none; 29 | } 30 | 31 | .nhrefs.js { 32 | font-style: normal; 33 | display: none; 34 | margin-left: 4px; 35 | margin-right: 0; 36 | padding-bottom: 1em; 37 | border: 2px solid var(--primary-variant-color); 38 | border-radius: 4px; 39 | z-index: 2; 40 | background-color: var(--primary-color); 41 | color: var(--on-primary-color); 42 | } 43 | 44 | .nhrefs.js .arc { 45 | display: list-item; 46 | margin-left: 1.5em; 47 | padding-right: 1em; 48 | } 49 | 50 | .nhrefs.js .xlink-arc-title { 51 | display: block; 52 | background-color: #afafaf; 53 | margin-left: 0; 54 | margin-bottom: 0.5em; 55 | text-align: center; 56 | } 57 | -------------------------------------------------------------------------------- /presentation/js/annotations.js: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is annotations.js providing support for popup annotations. 4 | * 5 | * See https://xsltng.docbook.org/ 6 | * 7 | */ 8 | 9 | (function() { 10 | const html = document.querySelector("html"); 11 | 12 | const showAnnotation = function(event, id) { 13 | let div = document.querySelector(id); 14 | div.style.display = "block"; 15 | enablePopup(div); 16 | 17 | event.preventDefault(); 18 | 19 | // Give the current click event a chance to settle? 20 | window.setTimeout(function () { 21 | let curpress = document.onkeyup; 22 | document.onkeyup = function (event) { 23 | hideAnnotation(event, id, curpress); 24 | }; 25 | }, 25); 26 | 27 | return false; 28 | }; 29 | 30 | const hideAnnotation = function(event, id, curpress) { 31 | let div = document.querySelector(id); 32 | div.style.display = "none"; 33 | disablePopup(div); 34 | 35 | if (curpress) { 36 | document.onkeyup = curpress; 37 | } 38 | 39 | return true; 40 | }; 41 | 42 | const enablePopup = function(div) { 43 | togglePopup(div, 'popup-', ''); 44 | }; 45 | 46 | const disablePopup = function(div) { 47 | togglePopup(div, '', 'popup-'); 48 | }; 49 | 50 | const togglePopup = function(div, on, off) { 51 | div.classList.remove(`${off}annotation-wrapper`); 52 | div.classList.add(`${on}annotation-wrapper`); 53 | ["body", "header", "content"].forEach(function(token) { 54 | const find = `.${off}annotation-${token}`; 55 | const addClass = `${on}annotation-${token}`; 56 | const removeClass = `${off}annotation-${token}`; 57 | div.querySelectorAll(find).forEach(function (div) { 58 | div.classList.add(addClass); 59 | div.classList.remove(removeClass); 60 | }); 61 | }); 62 | }; 63 | 64 | let jsannotations = window.localStorage.getItem("docbook-js-annotations"); 65 | if (jsannotations === "false") { 66 | return; 67 | } 68 | html.classList.add("js-annotations"); 69 | 70 | // Turn off the display of the individual annotations 71 | document.querySelectorAll("footer .annotations > div").forEach(function(div) { 72 | div.style.display = "none"; 73 | }); 74 | 75 | // Change the class on the annotations block to remove its styling 76 | document.querySelectorAll("footer .annotations").forEach(function(div) { 77 | // Turn off the annotation styling 78 | div.classList.add("popup-annotations"); 79 | div.classList.remove("annotations"); 80 | }); 81 | 82 | // The annotation close markup is hidden in a script. This prevents 83 | // it from showing up spuriously all over screen readers and other 84 | // user agents that don't support JavaScript. Find it and copy it 85 | // into the annotations. 86 | let annoClose = document.querySelector("script.annotation-close"); 87 | if (!annoClose) { 88 | // I have a bad feeling about this... 89 | annoClose = document.createElement("span"); 90 | annoClose.innerHTML = "╳"; 91 | } 92 | document.querySelectorAll("div.annotation-close").forEach(function(div) { 93 | div.innerHTML = annoClose.innerHTML; 94 | }); 95 | 96 | // Setup the onclick event for the annotation marks 97 | document.querySelectorAll("a.annomark").forEach(function(mark) { 98 | let id = mark.getAttribute("href"); 99 | // Escape characters that confuse querySelector 100 | id = id.replace(/\./g, "\\."); 101 | mark.onclick = function (event) { 102 | showAnnotation(event, id); 103 | }; 104 | }); 105 | 106 | // Take out the annotation numbers (in the text and the popup titles) 107 | document.querySelectorAll(".annomark sup.num").forEach(function(sup) { 108 | sup.style.display = "none"; 109 | }); 110 | 111 | // If an annotation is displayed, make clicking on the page hide it 112 | document.querySelectorAll("div.annotation-close").forEach(function(anno) { 113 | // Carefully walk up the tree; this might just be the close tag lying 114 | // around in the footer not actually inside an annotation. 115 | let id = anno.parentNode.parentNode.parentNode.getAttribute("id"); 116 | // Escape characters that confuse querySelector 117 | id = id.replace(/\./g, "\\."); 118 | anno.onclick = function (event) { 119 | hideAnnotation(event, `#${id}`); 120 | }; 121 | }); 122 | })(); 123 | -------------------------------------------------------------------------------- /presentation/js/controls.js: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is controls.js providing JavaScript controls. 4 | * 5 | * See https://xsltng.docbook.org/ 6 | * 7 | */ 8 | 9 | (function() { 10 | const prefersDark = window.matchMedia 11 | && window.matchMedia('(prefers-color-scheme: dark)').matches; 12 | const html = document.querySelector("html"); 13 | const body = document.querySelector("body"); 14 | const controlScript = document.querySelector("#db-js-controls"); 15 | let themeList = []; 16 | let togglesFieldset = null; 17 | let themesFieldset = null; 18 | let toggleAnnotations = "#db-js-annotations_"; 19 | let toggleXLinks = "#db-js-xlinks_"; 20 | let jsControlsReload = "#db-js-controls-reload_"; 21 | let controls = null; 22 | let menu = null; 23 | 24 | const activateControls = function() { 25 | let open = controls.querySelector(".controls-open"); 26 | open = body.appendChild(open); 27 | open.style.position = "fixed"; 28 | open.style.left = "10px"; 29 | open.style.top = "0"; 30 | open.style.cursor = "pointer"; 31 | open.onclick = function(event) { 32 | showMenu(event); 33 | }; 34 | 35 | menu = controls.querySelector(".js-controls-wrapper"); 36 | menu = body.appendChild(menu); 37 | let close = menu.querySelector(".js-controls-close"); 38 | close.onclick = function(event) { 39 | hideMenu(event); 40 | }; 41 | 42 | let nodes = menu.querySelectorAll("button"); 43 | nodes[0].style.cursor = "pointer"; 44 | nodes[0].onclick = function(event) { 45 | hideMenu(event); 46 | }; 47 | nodes[1].style.cursor = "pointer"; 48 | nodes[1].onclick = function(event) { 49 | updateSettings(event); 50 | }; 51 | 52 | nodes = menu.querySelectorAll("fieldset"); 53 | togglesFieldset = nodes[0]; 54 | themesFieldset = nodes[1]; 55 | 56 | let random = togglesFieldset.getAttribute("db-random"); 57 | toggleAnnotations = toggleAnnotations + random; 58 | toggleXLinks = toggleXLinks + random; 59 | jsControlsReload = jsControlsReload + random; 60 | 61 | let check = document.querySelector(toggleAnnotations); 62 | check.onchange = function (event) { 63 | let check = document.querySelector(toggleAnnotations); 64 | window.localStorage.setItem("docbook-js-annotations", check.checked); 65 | checkReload(event); 66 | }; 67 | check.checked = html.classList.contains("js-annotations"); 68 | 69 | check = document.querySelector(toggleXLinks); 70 | check.onchange = function (event) { 71 | let check = document.querySelector(toggleXLinks); 72 | window.localStorage.setItem("docbook-js-xlinks", check.checked); 73 | checkReload(event); 74 | }; 75 | check.checked = html.classList.contains("js-xlinks"); 76 | 77 | let theme = window.localStorage.getItem("docbook-theme"); 78 | if (theme) { 79 | document.querySelectorAll("input[name='db-theme']").forEach(function(input) { 80 | if (input.value === theme) { 81 | input.checked = true; 82 | } else { 83 | input.checked = false; 84 | } 85 | }); 86 | } 87 | }; 88 | 89 | const showMenu = function(event) { 90 | let toggles = menu.querySelector("fieldset.js-controls-toggles"); 91 | if (event.shiftKey) { 92 | toggles.style.display = "block"; 93 | } else { 94 | toggles.style.display = "none"; 95 | } 96 | 97 | let theme = currentTheme(); 98 | document.querySelectorAll("input[name='db-theme']").forEach(function(input) { 99 | if (input.value === theme) { 100 | input.checked = true; 101 | } else { 102 | input.checked = false; 103 | } 104 | }); 105 | 106 | menu.style.display = "block"; 107 | }; 108 | 109 | const checkReload = function(event) { 110 | let reload = false; 111 | let value = window.localStorage.getItem("docbook-js-annotations") === "true"; 112 | reload = reload || value !== document.querySelector(toggleAnnotations).checked; 113 | value = window.localStorage.getItem("docbook-js-xlinks") === "true"; 114 | reload = reload || value !== document.querySelector(toggleXLinks).checked; 115 | 116 | if (reload) { 117 | document.querySelector(jsControlsReload).innerHTML = "Reload required"; 118 | } else { 119 | document.querySelector(jsControlsReload).innerHTML = ""; 120 | } 121 | 122 | return false; 123 | }; 124 | 125 | const currentTheme = function() { 126 | let theme = null; 127 | themeList.forEach(function(item) { 128 | if (html.classList.contains(item)) { 129 | theme = item; 130 | } 131 | }); 132 | return theme; 133 | }; 134 | 135 | const setTheme = function(theme) { 136 | themeList.forEach(function(item) { 137 | html.classList.remove(item); 138 | }); 139 | html.classList.add(theme); 140 | }; 141 | 142 | const updateSettings = function(event) { 143 | menu.style.display = "none"; 144 | 145 | const newTheme = document.querySelector("input[name='db-theme']:checked"); 146 | if (newTheme) { 147 | setTheme(newTheme.value); 148 | window.localStorage.setItem("docbook-theme", newTheme.value); 149 | } 150 | }; 151 | 152 | const hideMenu = function(event) { 153 | menu.style.display = "none"; 154 | }; 155 | 156 | if (controlScript) { 157 | controls = document.createElement("DIV"); 158 | controls.innerHTML = controlScript.innerHTML; 159 | activateControls(); 160 | 161 | // Find the controls in the document 162 | document.querySelectorAll(".js-controls-wrapper").forEach(function(div) { 163 | // The controls will be the last one, in the unlikely event there's 164 | // more than one. (It's possible for a document to use that class.) 165 | controls = div; 166 | }); 167 | 168 | // Populate the themes list 169 | document.querySelectorAll("input[name='db-theme']").forEach(function(input) { 170 | themeList.push(input.value); 171 | }); 172 | 173 | let theme = window.localStorage.getItem("docbook-theme"); 174 | if (theme !== null) { 175 | setTheme(theme); 176 | } else { 177 | let dark = controls.querySelector("div").getAttribute("db-dark-theme"); 178 | if (dark && prefersDark) { 179 | setTheme(dark); 180 | } 181 | } 182 | 183 | html.style.display = "block"; 184 | } 185 | })(); 186 | -------------------------------------------------------------------------------- /presentation/js/persistent-toc.js: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is persistent-toc.js providing support for the ToC popup 4 | * 5 | * See https://xsltng.docbook.org/ 6 | * 7 | */ 8 | 9 | (function() { 10 | const ESC = 27; 11 | const SPACE = 32; 12 | const toc = document.querySelector("nav.toc"); 13 | let tocPersist = null; 14 | let borderLeftColor = "white"; 15 | let curpress = null; 16 | let searchListener = false; 17 | 18 | const showToC = function(event) { 19 | toc.style.width = "300px"; 20 | toc.style["padding-left"] = "1em"; 21 | toc.style["padding-right"] = "1em"; 22 | toc.style["border-left"] = `1px solid ${borderLeftColor}`; 23 | 24 | // Make sure the tocPersist checkbox is created 25 | tocPersistCheckbox(); 26 | 27 | if (event) { 28 | event.preventDefault(); 29 | } 30 | 31 | // Turn off any search markers that might have been set 32 | toc.querySelectorAll("li").forEach(function (li) { 33 | const link = li.querySelector("a"); 34 | li.style.display = "list-item"; 35 | link.classList.remove("found"); 36 | }); 37 | 38 | // Give the current click event a chance to settle? 39 | window.setTimeout(function () { 40 | const tocClose = toc.querySelector("header .close"); 41 | curpress = document.onkeyup; 42 | tocClose.onclick = function (event) { 43 | hideToC(event); 44 | }; 45 | document.onkeyup = function (event) { 46 | event = event || window.event; 47 | if (event.srcElement && event.srcElement.classList.contains("ptoc-search")) { 48 | // Don't navigate if the user is typing in the persistent toc search box 49 | return false; 50 | } else { 51 | let charCode = event.keyCode || event.which; 52 | if (charCode == SPACE || charCode == ESC) { 53 | hideToC(event); 54 | return false; 55 | } 56 | return true; 57 | } 58 | }; 59 | 60 | let url = window.location.href; 61 | let hash = ""; 62 | let pos = url.indexOf("#"); 63 | if (pos > 0) { 64 | hash = url.substring(pos); 65 | url = url.substring(0,pos); 66 | } 67 | 68 | pos = url.indexOf("?"); 69 | if (pos >= 0) { 70 | tocPersistCheckbox(); 71 | if (tocPersist) { 72 | tocPersist.checked = true; 73 | } 74 | url = url.substring(0, pos); 75 | } 76 | url = url + hash; 77 | 78 | // Remove ?toc from the URI so that if it's bookmarked, 79 | // the ToC reference isn't part of the bookmark. 80 | window.history.replaceState({}, document.title, url); 81 | 82 | pos = url.lastIndexOf("/"); 83 | url = url.substring(pos+1); 84 | let target = document.querySelector("nav.toc div a[href='"+url+"']"); 85 | if (target) { 86 | target.scrollIntoView(); 87 | } else { 88 | // Maybe it's just a link in this page? 89 | pos = url.indexOf("#"); 90 | if (pos > 0) { 91 | let hash = url.substring(pos); 92 | target = document.querySelector("nav.toc div a[href='"+hash+"']"); 93 | if (target) { 94 | target.scrollIntoView(); 95 | } else { 96 | console.log(`No target: ${url} (or ${hash})`); 97 | } 98 | } 99 | } 100 | 101 | if (!searchListener) { 102 | configureSearch(); 103 | searchListener = true; 104 | } 105 | }, 400); 106 | 107 | return false; 108 | }; 109 | 110 | const hideToC = function(event) { 111 | document.onkeyup = curpress; 112 | toc.classList.add("slide"); 113 | toc.style.width = "0px"; 114 | toc.style["padding-left"] = "0"; 115 | toc.style["padding-right"] = "0"; 116 | toc.style["border-left"] = "none"; 117 | 118 | if (event) { 119 | event.preventDefault(); 120 | } 121 | 122 | const searchp = toc.querySelector(".ptoc-search"); 123 | if (searchp) { 124 | const search = searchp.querySelector("input"); 125 | if (search) { 126 | search.value = ""; 127 | } 128 | } 129 | toc.querySelectorAll("li").forEach(function (li) { 130 | li.style.display = "list-item"; 131 | }); 132 | 133 | return false; 134 | }; 135 | 136 | const tocPersistCheckbox = function() { 137 | if (tocPersist != null) { 138 | return; 139 | } 140 | 141 | let ptoc = toc.querySelector("p.ptoc-search"); 142 | let sbox = ptoc.querySelector("input.ptoc-search"); 143 | if (sbox) { 144 | sbox.setAttribute("title", "Simple text search in ToC"); 145 | let pcheck = document.createElement("input"); 146 | pcheck.classList.add("persist"); 147 | pcheck.setAttribute("type", "checkbox"); 148 | pcheck.setAttribute("title", "Keep ToC open when following links"); 149 | pcheck.checked = (window.location.href.indexOf("?toc") >= 0); 150 | ptoc.appendChild(pcheck); 151 | } 152 | 153 | tocPersist = toc.querySelector("p.ptoc-search .persist"); 154 | }; 155 | 156 | const patchLink = function(event, anchor) { 157 | if (!tocPersist || !tocPersist.checked) { 158 | return false; 159 | } 160 | 161 | let href = anchor.getAttribute("href"); 162 | let pos = href.indexOf("#"); 163 | 164 | if (pos === 0) { 165 | // If the anchor is a same-document reference, we don't 166 | // need to do any of this query string business. 167 | return false; 168 | } 169 | 170 | if (pos > 0) { 171 | href = href.substring(0, pos) + "?toc" + href.substring(pos); 172 | } else { 173 | href = href + "?toc"; 174 | } 175 | 176 | event = event || window.event; 177 | if (event) { 178 | event.preventDefault(); 179 | } 180 | window.location.href = href; 181 | return false; 182 | }; 183 | 184 | const configureSearch = function() { 185 | const searchp = toc.querySelector(".ptoc-search"); 186 | if (searchp == null) { 187 | return; 188 | } 189 | const search = searchp.querySelector("input"); 190 | search.onkeyup = function (event) { 191 | event = event || window.event; 192 | if (event) { 193 | event.preventDefault(); 194 | } 195 | let charCode = event.keyCode || event.which; 196 | if (charCode == ESC) { 197 | hideToC(event); 198 | return false; 199 | } 200 | 201 | const value = search.value.toLowerCase().trim(); 202 | let restr = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(" ", ".*"); 203 | const regex = RegExp(restr); 204 | 205 | toc.querySelectorAll("li").forEach(function (li) { 206 | const link = li.querySelector("a"); 207 | if (restr === "") { 208 | li.style.display = "list-item"; 209 | link.classList.remove("found"); 210 | } else { 211 | if (li.textContent.toLowerCase().match(regex)) { 212 | li.style.display = "list-item"; 213 | if (link.textContent.toLowerCase().match(regex)) { 214 | link.classList.add("found"); 215 | } else { 216 | link.classList.remove("found"); 217 | } 218 | } else { 219 | li.style.display = "none"; 220 | } 221 | } 222 | }); 223 | 224 | return false; 225 | }; 226 | }; 227 | 228 | // Setting the border-left-style in CSS will put a thin border-colored 229 | // stripe down the right hand side of the window. Here we get the color 230 | // of that stripe and then remove it. We'll put it back when we 231 | // expand the ToC. 232 | borderLeftColor = window.getComputedStyle(toc)["border-left-color"]; 233 | toc.style["border-left"] = "none"; 234 | 235 | const tocOpenScript = document.querySelector("script.tocopen"); 236 | const tocOpen = document.querySelector("nav.tocopen"); 237 | tocOpen.innerHTML = tocOpenScript.innerHTML; 238 | tocOpen.onclick = showToC; 239 | 240 | const tocScript = document.querySelector("script.toc"); 241 | toc.innerHTML = tocScript.innerHTML; 242 | 243 | tocOpen.style.display = "inline"; 244 | 245 | document.querySelectorAll("nav.toc div a").forEach(function (anchor) { 246 | anchor.onclick = function(event) { 247 | if (!tocPersist || !tocPersist.checked) { 248 | hideToC(); 249 | } 250 | patchLink(event, anchor); 251 | }; 252 | }); 253 | 254 | let tocJump = false; 255 | let pos = window.location.href.indexOf("?"); 256 | if (pos >= 0) { // How could it be zero? 257 | let query = window.location.href.substring(pos+1); 258 | pos = query.indexOf("#"); 259 | if (pos >= 0) { 260 | query = query.substring(0, pos); 261 | } 262 | query.split("&").forEach(function(item) { 263 | tocJump = tocJump || (item === "toc" || item === "toc=1" || item === "toc=true"); 264 | }); 265 | } 266 | 267 | if (tocJump) { 268 | showToC(null); 269 | } else { 270 | // If we're not going to jump immediately to the ToC, 271 | // add the slide class for aesthetics if the user clicks 272 | // on it. 273 | toc.classList.add("slide"); 274 | } 275 | })(); 276 | -------------------------------------------------------------------------------- /presentation/js/xlink.js: -------------------------------------------------------------------------------- 1 | /* DocBook xslTNG version 1.5.4 2 | * 3 | * This is xlink.js providing support for multi-targeted links 4 | * 5 | * See https://xsltng.docbook.org/ 6 | * 7 | */ 8 | 9 | (function() { 10 | const html = document.querySelector("html"); 11 | let OPEN = "▼"; 12 | let CLOSED = "▶"; 13 | 14 | // You can hide alternate open/closed markers in the HTML. 15 | // Put them in a script element so that they get ignored 16 | // by screen readers and other non-JS presentations 17 | let arrow = document.querySelector("script.xlink-icon-open"); 18 | if (arrow) { 19 | OPEN = arrow.innerHTML; 20 | } 21 | arrow = document.querySelector("script.xlink-icon-closed"); 22 | if (arrow) { 23 | CLOSED = arrow.innerHTML; 24 | } 25 | 26 | const showXLinks = function(event, span, qsel) { 27 | span.innerHTML = OPEN; 28 | const top = span.offsetTop + span.offsetHeight; 29 | const left = span.offsetLeft; 30 | 31 | let links = document.querySelector(qsel); 32 | 33 | if (links.style.display === "inline-block") { 34 | // Already displayed: abort! 35 | return; 36 | } 37 | 38 | window.setTimeout(function () { 39 | const width = window.innerWidth 40 | || document.documentElement.clientWidth 41 | || document.body.clientWidth; 42 | checkPosition(links, top, left, width); 43 | }, 25); 44 | 45 | // Give the current click event a chance to settle? 46 | window.setTimeout(function () { 47 | let curclick = document.onclick; 48 | let curpress = document.onkeyup; 49 | document.onclick = function (event) { 50 | unshowXLinks(event, span, qsel, curclick, curpress); 51 | }; 52 | document.onkeyup = function (event) { 53 | unshowXLinks(event, span, qsel, curclick, curpress); 54 | }; 55 | 56 | links.style.display = "inline-block"; 57 | links.style.position = "absolute"; 58 | links.style.top = top; 59 | links.style.left = left; 60 | }, 25); 61 | }; 62 | 63 | const unshowXLinks = function(event, span, qsel, curclick, curpress) { 64 | span.innerHTML = CLOSED; 65 | /* 66 | span.onclick = function (event) { 67 | showXLinks(event, span, qsel); 68 | }; 69 | */ 70 | 71 | let links = document.querySelector(qsel); 72 | links.style.display = "none"; 73 | 74 | document.onclick = curclick; 75 | document.onkeyup = curpress; 76 | }; 77 | 78 | const checkPosition = function(links, top, left, width) { 79 | if (left + links.offsetWidth + 10 >= width) { 80 | const newx = left - 20; 81 | if (newx >= 0) { 82 | links.style.left = newx; 83 | links.style.top = top; 84 | if (newx == left) { 85 | console.log("Looping!"); 86 | } else { 87 | window.setTimeout(function () { 88 | checkPosition(links, top, newx, width); 89 | }, 15); 90 | } 91 | } 92 | } 93 | }; 94 | 95 | let jsxlinks = window.localStorage.getItem("docbook-js-xlinks"); 96 | if (jsxlinks === "false") { 97 | return; 98 | } 99 | html.classList.add("js-xlinks"); 100 | 101 | // Process XLink multi-targeted links. 102 | // 1. The link source is a target, but the JS has to operate 103 | // on the arc list that (immediately) follows it. 104 | document.querySelectorAll(".xlink .source").forEach(function(span) { 105 | span.onclick = function (event) { 106 | // Be conservative in case some text node or something got introduced 107 | let arclist = span.nextSibling; 108 | while (arclist && !arclist.classList.contains("xlink-arc-list")) { 109 | arclist = arclist.nextSibling; 110 | } 111 | if (arclist) { 112 | showXLinks(event, arclist, "#" + arclist.getAttribute("db-arcs")); 113 | } else { 114 | console.log("Document formatting error: no xlink-arc-list"); 115 | } 116 | }; 117 | }); 118 | 119 | // 2. The arc list is also a target. 120 | document.querySelectorAll(".xlink-arc-list").forEach(function(span) { 121 | span.innerHTML = CLOSED; 122 | span.classList.add("js"); 123 | span.onclick = function (event) { 124 | showXLinks(event, span, "#" + span.getAttribute("db-arcs")); 125 | }; 126 | }); 127 | 128 | document.querySelectorAll(".nhrefs").forEach(function(span) { 129 | span.classList.add("js"); 130 | }); 131 | })(); 132 | -------------------------------------------------------------------------------- /presentation/orientation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saxonica/SaxonJS-Tutorial-2021/0128341f83c9f19eb7fdab484bc3adc8d0e6cb7e/presentation/orientation.jpg -------------------------------------------------------------------------------- /presentation/rfinish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saxonica/SaxonJS-Tutorial-2021/0128341f83c9f19eb7fdab484bc3adc8d0e6cb7e/presentation/rfinish.png -------------------------------------------------------------------------------- /presentation/rstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saxonica/SaxonJS-Tutorial-2021/0128341f83c9f19eb7fdab484bc3adc8d0e6cb7e/presentation/rstart.png -------------------------------------------------------------------------------- /python/server.py: -------------------------------------------------------------------------------- 1 | """ Saxon-JS Tutorial Web Server """ 2 | 3 | import re 4 | import os 5 | import sys 6 | import json 7 | import base64 8 | import subprocess 9 | from io import StringIO 10 | from pathlib import Path 11 | from http.server import BaseHTTPRequestHandler, HTTPServer 12 | 13 | HOSTNAME = "localhost" 14 | HOSTPORT = 9000 15 | 16 | class Unbuffered(object): 17 | def __init__(self, stream): 18 | self.stream = stream 19 | def write(self, data): 20 | self.stream.write(data) 21 | self.stream.flush() 22 | def writelines(self, datas): 23 | self.stream.writelines(datas) 24 | self.stream.flush() 25 | def __getattr__(self, attr): 26 | return getattr(self.stream, attr) 27 | 28 | class TutorialServer(BaseHTTPRequestHandler): 29 | """ A trivial web server, but with some fixup for exercises and answers. """ 30 | 31 | CONTENT_TYPES = {".html": "text/html", 32 | ".txt": "text/plain", 33 | ".css": "text/css", 34 | ".svg": "image/svg+xml", 35 | ".xml": "application/xml", 36 | ".json": "application/json", 37 | ".js": "text/javascript", 38 | ".png": "image/png", 39 | ".jpeg": "image/jpeg", 40 | ".jpg": "image/jpeg"} 41 | 42 | def do_POST(self): 43 | """ Repond to HTTP POST requests. """ 44 | if self.path == "/stop": 45 | self.send_response(200) 46 | self.send_header("Content-type", "text/plain") 47 | self.end_headers() 48 | self.wfile.write(bytes("Stopping\n", "utf-8")) 49 | sys.exit(0) 50 | else: 51 | self.send_response(405) 52 | self.send_header("Content-type", "text/plain") 53 | self.end_headers() 54 | self.wfile.write(bytes("POST is not allowed\n", "utf-8")) 55 | 56 | def do_GET(self): 57 | """ Repond to HTTP GET requests. """ 58 | 59 | if self.path == "/favicon.ico": 60 | self.fake_icon() 61 | return 62 | 63 | filename = self.path 64 | if "?" in filename: 65 | filename = filename[0:filename.index("?")] 66 | 67 | path = Path(os.getcwd() + filename) 68 | 69 | if re.match("^/answers/.*\\.sef\\.json$", filename) \ 70 | or re.match("^/exercises/.*\\.sef\\.json$", filename): 71 | self.recompile(path) 72 | 73 | if path.exists(): 74 | if path.is_dir(): 75 | self.show_directory(path) 76 | else: 77 | self.show_file(path) 78 | 79 | def show_file(self, path): 80 | self.send_response(200) 81 | 82 | content_type = "application/octet-stream" 83 | if path.suffix in TutorialServer.CONTENT_TYPES: 84 | content_type = TutorialServer.CONTENT_TYPES[path.suffix] 85 | 86 | self.send_header("Content-type", content_type) 87 | 88 | self.end_headers() 89 | 90 | params = {} 91 | if "?" in self.path: 92 | query = self.path[self.path.index("?")+1:] 93 | for data in query.split("&"): 94 | pos = data.index("=") 95 | if pos > 0: 96 | params[data[0:pos]] = data[pos+1:] 97 | 98 | if "answer" in params: 99 | self.patch_html(path, "answer", params) 100 | elif "exercise" in params: 101 | self.patch_html(path, "exercise", params) 102 | else: 103 | with open(path, "rb") as file: 104 | bytes = file.read(4096) 105 | while bytes: 106 | self.wfile.write(bytes) 107 | bytes = file.read(4096) 108 | 109 | def patch_html(self, path, styledir, params): 110 | sef = params[styledir] 111 | del params[styledir] 112 | 113 | with open(path, "r") as file: 114 | html = "".join(file.readlines()) 115 | pos = html.index("") 116 | 117 | self.wfile.write(bytes(html[0:pos], "utf-8")) 118 | 119 | self.wfile.write(bytes("", "utf-8")) 120 | self.wfile.write(bytes("", "utf-8")) 136 | 137 | self.wfile.write(bytes(html[pos:], "utf-8")) 138 | 139 | def show_directory(self, path): 140 | index = path.joinpath("index.html") 141 | if index.exists(): 142 | url = self.path 143 | if not url.endswith("/"): 144 | url += "/" 145 | url += "index.html" 146 | self.redirect(url) 147 | return 148 | 149 | if not self.path.endswith("/"): 150 | self.redirect(self.path + "/") 151 | return 152 | 153 | body = StringIO() 154 | body.write("") 155 | 156 | relpath = str(path)[len(os.getcwd()):] 157 | if relpath == "": 158 | relpath = "/" 159 | 160 | body.write("

Directory listing for '%s'

" % relpath) 161 | body.write("
") 162 | 163 | body.write("") 177 | body.write("
") 178 | body.write("") 179 | 180 | self.send_response(200) 181 | self.send_header("Content-type", "text/html") 182 | self.end_headers() 183 | self.wfile.write(bytes("%s" % path, "utf-8")) 184 | self.wfile.write(bytes(body.getvalue(), "utf-8")) 185 | self.wfile.write(bytes("", "utf-8")) 186 | 187 | def recompile(self, sef): 188 | pos = str(sef).index(".sef.json") 189 | xsl = Path(str(sef)[0:pos] + ".xsl") 190 | try: 191 | newer = sef.stat().st_mtime < xsl.stat().st_mtime 192 | except FileNotFoundError: 193 | newer = True 194 | 195 | if newer: 196 | relpath = str(xsl)[len(os.getcwd())+1:] 197 | subprocess.run(["./gradlew", "-Pxsl=%s" % relpath, "eej"]) 198 | 199 | def fake_icon(self): 200 | self.send_response(200) 201 | self.send_header("Content-type", "image/png") 202 | self.end_headers() 203 | 204 | bytes = base64.b64decode("".join([ 205 | "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xh", 206 | "BQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB", 207 | "X1BMVEXn8PHk7vDl7/Dm8PHf6u7F0eDCz97Bzt7l7/Hg6+7X4unO2uTD0N6wvtSy", 208 | "wda4xtm/zN3h7O7Azt2WqMaClrx8kbl9kbl7j7h6j7h/k7qNn8KpudHj7e+wv9R5", 209 | "jbeBlbultc6+zNyvvtSmts+TpcV4jLacrcrAzd3o8PLW4ejE0d/f6e3Cz993jLbo", 210 | "8fLU3+eAlLt5jrfj7vDE0eC+y9yjs82fsMu3xdjh6+6nt892i7Z+krqcrsq1w9e7", 211 | "ydu/zd3Dz9+8ytp8kLiDlryMn8GdrsqxwNXg6u7G0+CUpsaIm791irW6x9rm7/Hd", 212 | "5+ze6O3E0N+ltc+BlLtzibSXqMfAzd7k7fCktM2uvdPd6O3D0N+gsMxziLS9yty2", 213 | "xde7yNuPosN6jreVp8avvtO5x9mrutKmtc+ZqsiEmL12i7XD0d5+krmfsMzCzt7b", 214 | "5uvV4OjK1+Kzwtazwte9y9z///9ckkS+AAAAAWJLR0R0322obQAAAAd0SU1FB+UK", 215 | "DQ06HAv38YgAAAD1SURBVBjTY2BgBAImZhZWNjZ2EGAACnBwcnHz8PLxC7BBBASF", 216 | "hEVExcQlJKWkBUACMrJy8gqKbErKKhKqaursDBqaInJaHMzarDrsyroSfDoMevoG", 217 | "hmxGzNrG7OwmpmbmbAwMFpZW1ja2bPx29joOOmBDtR1VDFR1nZxdXN3AhjJyMLvr", 218 | "e7h5ell7S6j4sDP4+nH5M3EwswSY8AcGBYeEMoSFS0YwMTJGRrGzRUXHeMUyaMR5", 219 | "y7NZAJ2uwxafEKOizsCgbSmXmJSckpqWnpHpZQu0ltEiy9VLUlRcItsrxycX4jmZ", 220 | "vPyCwiL+YqjnQN7n0IZ5HwDkUSR0fRGbDQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAy", 221 | "MS0xMC0xM1QxMzo1ODoxNyswMDowMJv1GdsAAAAldEVYdGRhdGU6bW9kaWZ5ADIw", 222 | "MjEtMTAtMTNUMTM6NTg6MDkrMDA6MDB2PdqkAAAAAElFTkSuQmCC"])) 223 | 224 | self.wfile.write(bytes) 225 | 226 | def redirect(self, path): 227 | self.send_response(200) 228 | 229 | content_type = "text/html" 230 | self.send_header("Content-type", content_type) 231 | self.send_header("Location", path) 232 | self.end_headers() 233 | 234 | html = "Redirect" 235 | html += "" % path 236 | html += "

" 237 | html += "Redirect" % path 238 | html += "

" 239 | 240 | self.wfile.write(bytes(html, "utf-8")) 241 | 242 | 243 | if __name__ == "__main__": 244 | sys.stdout = Unbuffered(sys.stdout) 245 | sys.stderr = Unbuffered(sys.stderr) 246 | 247 | # Crudely look to see if an alternate port has been suggested 248 | pos = 0 249 | userport = str(HOSTPORT) 250 | while pos < len(sys.argv): 251 | if sys.argv[pos] == "-p": 252 | if pos +1 < len(sys.argv): 253 | userport = sys.argv[pos + 1] 254 | else: 255 | userport = "" 256 | elif sys.argv[pos].startswith("-p"): 257 | userport = sys.argv[pos][2:] 258 | pos += 1 259 | 260 | try: 261 | HOSTPORT = int(userport) 262 | except ValueError: 263 | print(f"Invalid port specified: {userport}. Port must be an integer > 1024.") 264 | sys.exit(1) 265 | 266 | server = HTTPServer((HOSTNAME, HOSTPORT), TutorialServer) 267 | print("Server started on http://%s:%s" % (HOSTNAME, HOSTPORT)) 268 | 269 | try: 270 | server.serve_forever() 271 | except KeyboardInterrupt: 272 | pass 273 | 274 | server.server_close() 275 | print("Server stopped.") 276 | -------------------------------------------------------------------------------- /recipes/README.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | This is a small collection of recipes used for many of the exercises 4 | in the tutorial. All of them are delicious. 5 | 6 | The `categories.json` file is used by the server to support returning 7 | a list of categories and a list of recipes in a particular category. 8 | 9 | The `template.xml` file is a template to make adding recipes easier. 10 | 11 | -------------------------------------------------------------------------------- /recipes/beef-stroganof.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Beef Stroganof 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Serves 4.

11 |
12 |
    13 |
  • 1 lb steak, sliced
  • 14 |
  • 1 Tbs flour
  • 15 |
  • 3 onions
  • 16 |
  • 2 cloves garlic
  • 17 |
  • 1 cup beef bouillon
  • 18 |
  • 1 dash sherry
  • 19 |
  • 1 lb mushrooms, sliced
  • 20 |
  • sour cream
  • 21 |
22 |
23 |
24 |
    25 |
  1. Flour the steak and add salt and pepper to taste.
  2. 26 |
  3. Brown in butter and oil.
  4. 27 |
  5. Slice the onions and garlic. Add them to the pan. Cook until browned.
  6. 28 |
  7. Add beef bouillon, mushrooms, and sherry.
  8. 29 |
  9. Cook, covered for 30 minutes.
  10. 30 |
  11. Uncover and reduce.
  12. 31 |
  13. Remove from the heat, let cool slighty, add sour cream.
  14. 32 |
33 |

Serve with pasta or rice-a-roni.

34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /recipes/categories.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Recipe by category 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /recipes/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": ["beef-stroganof.html", "mac-n-cheese.html", "marys-beef-stew.html", "pizza.html"], 3 | "side": ["rice-a-roni.html"], 4 | "candy": ["treacle-taffy.html"] 5 | } 6 | -------------------------------------------------------------------------------- /recipes/mac-n-cheese.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Macaroni & cheese 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Serves 4.

12 |
13 |
    14 |
  • ¼ cup butter
  • 15 |
  • 2 small onions
  • 16 |
  • 1 tsp salt
  • 17 |
  • 1 tsp pepper
  • 18 |
  • 3 tsp sharp mustard
  • 19 |
  • 1½ Tbs flour
  • 20 |
  • 1 qt milk
  • 21 |
  • 16 oz extra sharp cheddar cheese
  • 22 |
  • 1 lb elbow macaroni
  • 23 |
  • buttered bread crumbs
  • 24 |
25 |
26 |
27 |
    28 |
  1. Saute chopped onions in melted butter.
  2. 29 |
  3. Add salt, pepper, mustard, and flour to make a roux.
  4. 30 |
  5. Add ½ cup milk, 31 | whisk until smooth.
  6. 32 |
  7. Add remaining milk.
  8. 33 |
  9. Heat, but do not boil.
  10. 34 |
  11. Add shredded cheese to melt.
  12. 35 |
  13. Turn off the heat.
  14. 36 |
  15. Place macaroni in a baking dish. Pour cheese sauce over macaroni.
  16. 37 |
  17. Sprinkle the top with grated cheese and buttered bread crumbs.
  18. 38 |
  19. Bake for 20 minutes at 425 °F.
  20. 39 |
40 |
41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /recipes/marys-beef-stew.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mary’s Beef Stew 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Serves 6.

12 |
13 |
    14 | 15 |
  • 2 slices bacon, cut into large pieces
  • 16 |
  • 2 Tbs oil
  • 17 |
  • 1 lb beef chunk, cut into large cubes, salted
  • 18 |
  • 1 onion, cut into large pieces
  • 19 |
  • 5 cloves garlic, halved
  • 20 |
  • 1 leek, cut into rings
  • 21 |
  • ¼ cup flour
  • 22 |
  • 1 cup red wine
  • 23 |
  • 1 cup stock
  • 24 |
  • 2 medium carrots, cut into chunks
  • 25 |
  • 1 cup frozen peas
  • 26 |
  • ½ lb new potatoes, halved
  • 27 |
  • 1 tsp peppercorns
  • 28 |
  • 1 tsp worcester sauce
  • 29 |
  • ¼ tsp cinnamon
  • 30 |
  • 1 tsp miso paste
  • 31 |
  • 1 bouquet garni (thyme, bay leaf, parsley)
  • 32 |
  • water as needed
  • 33 |
34 |
35 |
36 |
    37 |
  1. In heavy pot render and crisp bacon. Remove and drain.
  2. 38 |
  3. Add oil to pot and brown salted beef in batches; get nice crust on all sides. Remove.
  4. 39 |
  5. Sauté onions, garlic, leeks until translucent.
  6. 40 |
  7. Sprinkle flour over onions and stir to create a roux. Stir in wine and then stock to make gravy.
  8. 41 |
  9. Add remaining ingredients and meats and simmer until beef is soft, adding water as needed. (~1 hour).
  10. 42 |
43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /recipes/pizza.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pizza 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Serves 4.

12 |
13 |
    14 |
  • 3 cup flour
  • 15 |
  • 1 tsp salt
  • 16 |
  • 1 pkg yeast
  • 17 |
  • 1 cup luke warm water
  • 18 |
  • ¼ cup olive oil
  • 19 |
  • tomato sauce
  • 20 |
21 |
22 |
23 |
    24 |
  1. Put dry ingredients into food processor (preferably, though not 25 | necessarily, with a dough blade in) and pulse a few times to mix.
  2. 26 |
  3. While running, add the wet ingredients and let run to knead for about a minute.
  4. 27 |
  5. Turn into a greased bowl and let rise about an hour.
  6. 28 |
  7. Punch down dough, divide into fourths, and roll each part out. (It 29 | may work best to roll one piece out part way and then let it rest 30 | while you roll the rest out part way and then start over. Your choice 31 | on how thin to make them, though I usually roll out so that the result 32 | is about 12 inches in diameter or so.)
  8. 33 |
  9. Put on pizza paddle, top with some sauce, and bake in a preheated 34 | oven on a preheated pizza stone for about 2 minutes in as hot as your 35 | oven will go.
  10. 36 |
  11. Take out and freeze for later or not.
  12. 37 |
  13. If frozen, defrost.
  14. 38 |
  15. Add toppings and cook at 39 | 500 °F or so in a preheated 40 | oven on a preheated pizza stone for about 3-4 minutes until topping 41 | looks right.
  16. 42 |
43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /recipes/rice-a-roni.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Paul’s homemade rice-a-roni 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Serves 2.

12 |
13 |
    14 |
  • ½ cup rice
  • 15 |
  • ¼ cup broken up spaghetti
  • 16 |
  • 1 cup seasoned broth, warmed in the microwave
  • 17 |
  • 4 Tbs butter
  • 18 |
  • finely chopped “green bits,” chives and parsley, for example
  • 19 |
20 |
21 |
22 |
    23 |
  1. Saute the rice and spagehetti in 24 | 3 Tbs butter over medium high 25 | heat stirring constantly until the pasta starts to get golden brown.
  2. 26 |
  3. Add the heated broth and green bits, cover, and turn down to simmer.
  4. 27 |
  5. Simmer until the broth has been absorbed, usually about 10 minutes.
  6. 28 |
  7. When done, toss in the remaining butter (don’t stir it in yet) 29 | and remove from the heat.
  8. 30 |
31 |

When ready to serve, stir, taste for seasoning, and serve.

32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /recipes/template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{TITLE}} 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Serves {{SERVINGS}}.

12 |
13 |
    14 |
  • 15 |
  • 16 |
  • 17 |
18 |
19 |
20 |
    21 |
  1. 22 |
  2. 23 |
  3. 24 |
25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /recipes/treacle-taffy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Treacle taffy 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Serves 10.

12 |
13 |
    14 |
  • ¼ cup water
  • 15 |
  • 1 lb brown sugar
  • 16 |
  • 3 oz butter
  • 17 |
  • ½ cup molasses
  • 18 |
  • ½ cup Karo syrup
  • 19 |
  • a pinch cream of tartar
  • 20 |
21 |
22 |
23 |
    24 |
  1. Dissolve ingredients in water.
  2. 25 |
  3. Boil until the “hard crack” stage 26 | (about 310 °F), stirring occasionally.
  4. 27 |
  5. Pour into an 28 | 8x11 inch 29 | greased baking pan.
  6. 30 |
  7. Mark into squares when firm.
  8. 31 |
  9. Crack when cold.
  10. 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'SaxonJS-Tutorial-2021' 2 | -------------------------------------------------------------------------------- /src/main/resources/css/presentation.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | @import url("https://fonts.googleapis.com/css?family=B612+Mono&display=swap"); 3 | @import url("https://fonts.googleapis.com/css?family=Noto+Sans&display=swap"); 4 | @import url("https://fonts.googleapis.com/css?family=Noto+Serif&display=swap"); 5 | @import url('https://fonts.googleapis.com/css2?family=Oswald&display=swap'); 6 | } 7 | 8 | :root { 9 | --symbol-fonts: "Arial Unicode", "Apple Symbols", "Symbol", "Symbola_hint"; 10 | --body-family: "Noto Serif", serif, var(--symbol-fonts); 11 | --title-family: "Oswald", sans-serif, var(--symbol-fonts); 12 | --mono-family: "B612 Mono", monospace, var(--symbol-fonts); 13 | } 14 | 15 | html { 16 | font-size: 16pt; 17 | } 18 | 19 | main { 20 | max-width: 90%; 21 | } 22 | 23 | article header { 24 | min-height: 700px; 25 | } 26 | 27 | h1 { 28 | margin-top: 2rem; 29 | } 30 | 31 | article header h1 { 32 | padding-top: 1em; 33 | line-height: 150%; 34 | } 35 | 36 | article header .affiliation { 37 | font-size: 1.5rem; 38 | padding-top: 1rem; 39 | } 40 | 41 | article header .email { 42 | text-align: center; 43 | font-size: 1.2rem; 44 | } 45 | 46 | article header .icons { 47 | padding-top: 1em; 48 | text-align: center; 49 | } 50 | 51 | #xavier { 52 | width: 500px; 53 | float: right; 54 | } 55 | 56 | img.float { 57 | width: 350px; 58 | float: right; 59 | } 60 | 61 | .list-of-titles { 62 | display: none; 63 | } 64 | 65 | nav.tocopen span { 66 | display: none; 67 | } 68 | 69 | nav.tocopen:before { 70 | content: "☰"; 71 | } 72 | 73 | /* Hacks for the home page */ 74 | 75 | .author h3 { 76 | text-align: center; 77 | } 78 | 79 | p.affiliation { 80 | margin-top: 0.1em; 81 | text-align: center; 82 | } 83 | 84 | p.pubdate { 85 | text-align: center; 86 | margin-bottom: 0; 87 | } 88 | 89 | .confgroup { 90 | font-size: 1.2rem; 91 | text-align: center; 92 | } 93 | 94 | .conftitle { 95 | font-style: italic; 96 | } 97 | 98 | .confdates::before { 99 | content: ", "; 100 | } 101 | 102 | article.homepage header { 103 | text-align: left; 104 | } 105 | 106 | article.homepage header h1 { 107 | margin-top: 0; 108 | font-size: 2.4rem; 109 | } 110 | 111 | .chapter, 112 | .appendix { 113 | font-size: 28pt; 114 | line-height: 1.4; 115 | } 116 | 117 | .chapter h2, 118 | .appendix h2 { 119 | font-size: 36pt; 120 | } 121 | 122 | .popup-annotation-body { 123 | width: 80%; 124 | max-height: 90%; 125 | font-size: 24pt; 126 | } 127 | 128 | blockquote { 129 | width: 75%; 130 | padding: 0px 80px; 131 | position: relative; 132 | margin-bottom: 28px; 133 | } 134 | 135 | blockquote::before, 136 | blockquote::after { 137 | top: 0; 138 | bottom: 0; 139 | width: 25px; 140 | content: ''; 141 | position: absolute; 142 | } 143 | 144 | blockquote::before { 145 | right: 100%; 146 | } 147 | 148 | blockquote::after { 149 | left: 100%; 150 | } 151 | 152 | blockquote p { 153 | margin: 0; 154 | } 155 | 156 | blockquote p::before { 157 | top: -48px; 158 | left: 8px; 159 | content: '“'; 160 | font-size: 4em; 161 | position: absolute; 162 | color: rgb(193, 206,221); 163 | } 164 | 165 | blockquote .attribution { 166 | padding-top: 1.5rem; 167 | } 168 | 169 | /* ============================================================ */ 170 | 171 | nav.top { 172 | width: 100%; 173 | background-color: rgb(193, 206,221); 174 | display: grid; 175 | grid-template-columns: 33% 34% 33%; 176 | } 177 | 178 | nav .title { 179 | font-weight: normal; 180 | } 181 | 182 | nav .fas { 183 | font-weight: normal; 184 | } 185 | 186 | nav.top .nav { 187 | float: left; 188 | margin-left: 0.25rem; 189 | } 190 | 191 | nav.top .text { 192 | text-align: center; 193 | font-size: 18px; 194 | } 195 | 196 | nav.bottom { 197 | min-height: 1.0rem; 198 | margin-top: 2rem; 199 | margin-bottom: 0px; 200 | padding-top: px; 201 | padding-bottom: 10px; 202 | padding-left: 1rem; 203 | padding-right: 1rem; 204 | border-top-style: solid; 205 | border-top-width: 1px; 206 | display: grid; 207 | font-size: 1.0rem; 208 | } 209 | 210 | .navrow { 211 | display: grid; 212 | grid-template-columns: 33% 34% 33%; 213 | } 214 | 215 | .navleft { 216 | text-align: left; 217 | grid-column: 1; 218 | } 219 | 220 | .navmiddle { 221 | text-align: center; 222 | grid-column: 2; 223 | } 224 | 225 | .navright { 226 | text-align: right; 227 | grid-column: 3; 228 | } 229 | 230 | .navrow .navtitle { 231 | font-size: 18px; 232 | } 233 | 234 | nav .version { 235 | display: none; 236 | } 237 | 238 | nav .title { 239 | color: inherit; 240 | background-color: inherit; 241 | } 242 | 243 | .infofooter { 244 | display: none; 245 | } 246 | 247 | .home .infofooter { 248 | display: block; 249 | } 250 | 251 | .sidebar code { 252 | background-color: var(--sidebar-color); 253 | } 254 | 255 | .mediaobject { 256 | text-align: center; 257 | } 258 | 259 | .fa-solid { 260 | font-size: 30pt; 261 | } 262 | 263 | .toc .fa-solid { 264 | display: none; 265 | } 266 | 267 | .appendix .section .section { 268 | border-top: 1px solid black; 269 | } 270 | 271 | h3 { font-weight: 400; 272 | margin-top: 2.1rem; 273 | margin-bottom: 2rem; 274 | font-size: 2.2rem; 275 | line-height: 1; 276 | } 277 | 278 | h4 { font-weight: 400; 279 | margin-top: 2.1rem; 280 | margin-bottom: 2rem; 281 | font-size: 2.2rem; 282 | line-height: 1; 283 | } 284 | 285 | .itemizedlist .title { 286 | font-weight: normal; 287 | } 288 | 289 | #thanks h1 .number { 290 | display: none; 291 | } 292 | -------------------------------------------------------------------------------- /src/main/resources/orientation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saxonica/SaxonJS-Tutorial-2021/0128341f83c9f19eb7fdab484bc3adc8d0e6cb7e/src/main/resources/orientation.jpg -------------------------------------------------------------------------------- /src/main/resources/rfinish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saxonica/SaxonJS-Tutorial-2021/0128341f83c9f19eb7fdab484bc3adc8d0e6cb7e/src/main/resources/rfinish.png -------------------------------------------------------------------------------- /src/main/resources/rstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saxonica/SaxonJS-Tutorial-2021/0128341f83c9f19eb7fdab484bc3adc8d0e6cb7e/src/main/resources/rstart.png -------------------------------------------------------------------------------- /src/main/xml/node.xml: -------------------------------------------------------------------------------- 1 | 5 | SaxonJS in Node.js 6 | 7 | 8 | What is Node? 9 | 10 |
11 | Nodejs.org website 12 | Node.js is an open-source and cross-platform JavaScript runtime 13 | environment. It is a popular tool for almost any kind of 14 | project! 15 |
16 | 17 | 18 | 19 | Popular server-side application development environment 20 | 21 | 22 | 23 | Very large ecosystem of tools and packages 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | Why SaxonJS on Node? 32 | 33 | 34 | 35 | Lots of applications have been, are being, or will be developed on Node.js. 36 | 37 | 38 | 39 | The large ecosystem of existing tools on Node.js makes it an appealing 40 | platform. 41 | 42 | 43 | 44 | JavaScript is not renowned for its excellent XML support. 45 | 46 | 47 | 48 | SaxonJS provides full-service XPath and XSLT 3.0 features on Node.js. 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | SaxonJS on Node 57 | 58 | SaxonJS is composed of two Node.js packages: 59 | 60 | 61 | 62 | saxon-js is the SaxonJS implementation. 63 | 64 | 65 | xslt3 is a command line tool for performing transformations 66 | (and compiling XSL files to SEF). 67 | 68 | 69 | 70 | 71 | 72 | 73 | Installing SaxonJS on Node 74 | 75 | Add the dependency to your package.json 76 | file, for example: 77 | 78 | { 79 | "dependencies": { 80 | "saxon-js": "^2.3.0", 81 | "xslt3": "^2.3.0" 82 | } 83 | } 84 | 85 | Then run npm install. 86 | 87 | 88 | 89 | 90 | Using SaxonJS on Node 91 | 92 | On Node.js, you must explicitly import the SaxonJS object with 93 | require. Then you can use any of it’s APIs directly 94 | from JavaScript. 95 | 96 | The following Node.js program uses XPath to calculate the number 97 | of days until Christmas. 98 | 99 | = 25) { 107 | year += 1; 108 | } 109 | 110 | let christmas = `${year}-12-25`; 111 | let options = { "params": {"Q{}christmas": christmas} }; 112 | 113 | let days = SaxonJS.XPath.evaluate( 114 | `let $duration := xs:date($christmas) - current-date() 115 | return 116 | if ($duration <= xs:dayTimeDuration("P1D")) 117 | then 'Christmas is TOMORROW!' 118 | else 'It''s ' 119 | || days-from-duration($duration) 120 | || ' days ''til Christmas'`, 121 | null, options); 122 | 123 | console.log(days);]]> 124 | 125 | 126 | 127 | Transforming documents with XSLT 128 | 129 | You can use xslt3.js to transform documents. 130 | For example: 131 | 132 | $ node node_modules/xslt3/xslt3.js \ 133 | -s:mydoc.xml -xsl:tohtml.xsl -o:mydoc.html 134 | 135 | (You can use SaxonJS.transform() directly 136 | in your Node.js programs.) 137 | 138 | You can use xslt3.js to compile stylesheets into SEF files, 139 | as we saw earlier. 140 | 141 | 142 | 143 | 144 | Thank you! 145 | 146 | That’s our tutorial! 147 | 148 | We have been Norm Tovey-Walsh and Debbie Lockett. 149 | 150 | You have been delightful! 151 | 152 | Feel free to follow up with questions and comments in Slack or 153 | by email. 154 | 155 | 156 | 157 | 158 | 159 | 160 |
161 | -------------------------------------------------------------------------------- /src/main/xml/presentation.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | SaxonJS Tutorial 8 | 9 | 10 | 11 | 12 | 13 | Norm 14 | Tovey-Walsh 15 | 16 | 17 | Saxonica 18 | 19 | norm@saxonica.com 20 | 21 | 22 | 23 | Debbie 24 | Lockett 25 | 26 | 27 | Saxonica 28 | 29 | debbie@saxonica.com 30 | 31 | 32 | Declarative Amsterdam 2021 33 | 34 | 2021-11-04 35 | 36 | 2021 37 | Saxonica, Ltd 38 | 39 | 40 | 41 | 42 | 43 | What is SaxonJS? 44 | 45 | 46 | 47 | SaxonJS is an XSLT 3.0 processor written (mostly) in JavaScript. 48 | 49 | 50 | 51 | It runs both in the browser and on Node.js 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Tutorial setup 64 | 65 | The tutorial is available from the GitHub repository: . The 67 | README describes a number of options, but we’ll try the simplest thing 68 | first. 69 | 70 | 71 | 72 | 73 | Open up a shell window and navigate to the directory where 74 | you cloned the repository. (Even when you’re doing the exercises, the server 75 | should always be started in the “top level” directory.) 76 | 77 | 78 | Run the tutorial web server: 79 | 80 | 81 | Run ./gradlew pythonServerStart, 82 | if you prefer the Python version. 83 | (On Windows, use 84 | .\gradlew everwhere we say ./gradlew!) 85 | 86 | 87 | Run ./gradlew nodeServerStart, 88 | if you prefer the Node.js version. 89 | 90 | 91 | Run ./gradlew dockerServerStart, 92 | if you prefer the Docker container version. 93 | 94 | 95 | 96 | 97 | Open in your 98 | favorite (JavaScript enabled) web browser. 99 | 100 | 101 | 102 | Do you see a “Click me” button? Does it work? 103 | 104 | 105 | 106 | 107 | Orienting yourself 108 | 109 | 110 | There are about five windows you’ll want to be able to see while you’re doing 111 | the tutorial: 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | If you have multiple screens, you’ll almost certainly benefit from 120 | spreading the windows around so that you can see them simultaneously. 121 | Barring that, you may want to close some other applications so that 122 | there are fewer windows to switch between. 123 | 124 | (If you’d prefer to follow along with your own copy of the 125 | slides, they’re 126 | published online.) 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /tools/docbook.rng: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tools/presentation.xsl: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 |
35 | 36 |

37 |
38 | 39 |

40 |
41 | 42 |
43 |

44 | 45 |

46 |
47 |
48 |
49 | 50 | 51 |

52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 |

60 | 61 | 62 |

63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |