xslt: accept text or paths, generate Preserves data

This commit is contained in:
Emery Hemingway 2024-02-09 14:50:38 +00:00
parent 2c61d144d0
commit c8e8197ff7
5 changed files with 203 additions and 19 deletions

View File

@ -9,6 +9,6 @@ Pulse = <pulse @periodSec float @proxy #:any>.
XmlTranslation = <xml-translation @xml string @pr any>.
XsltTransform = <xslt-transform @stylesheet string @input string @output string>.
XsltTransform = <xslt-transform @stylesheet string @input string @output any>.
XsltItems = [XsltItem ...].
XsltItem = string.

View File

@ -20,7 +20,7 @@ type
XsltTransform* {.preservesRecord: "xslt-transform".} = object
`stylesheet`*: string
`input`*: string
`output`*: string
`output`*: Value
proc `$`*(x: XsltItems | Pulse | XsltItem | XmlTranslation | FileSystemUsage |
XsltTransform): string =

View File

@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[os, strutils]
import preserves, syndicate
import ../schema/[assertions, config]
@ -10,14 +11,90 @@ import ../schema/[assertions, config]
{.pragma: libxslt, header: "libxslt/xslt.h", importc.}
type
xmlElementType {.libxslt.} = enum
XML_ELEMENT_NODE = 1,
XML_ATTRIBUTE_NODE = 2,
XML_TEXT_NODE = 3,
XML_CDATA_SECTION_NODE = 4,
XML_ENTITY_REF_NODE = 5,
XML_ENTITY_NODE = 6,
XML_PI_NODE = 7,
XML_COMMENT_NODE = 8,
XML_DOCUMENT_NODE = 9,
XML_DOCUMENT_TYPE_NODE = 10,
XML_DOCUMENT_FRAG_NODE = 11,
XML_NOTATION_NODE = 12,
XML_HTML_DOCUMENT_NODE = 13,
XML_DTD_NODE = 14,
XML_ELEMENT_DECL = 15,
XML_ATTRIBUTE_DECL = 16,
XML_ENTITY_DECL = 17,
XML_NAMESPACE_DECL = 18,
XML_XINCLUDE_START = 19,
XML_XINCLUDE_END = 20
xmlNsPtr = ptr xmlNs
xmlNs {.libxslt.} = object
next: xmlNsPtr
href, prefix: cstring
xmlAttrPtr = ptr xmlAttr
xmlAttr {.libxslt.} = object
name: cstring
next: xmlAttrPtr
children: xmlNodePtr
xmlElementContentPtr = ptr xmlElementContent
xmlElementContent {.libxslt.} = object
encoding: cstring
xmlNodePtr = ptr xmlNode
xmlNode {.libxslt.} = object
`type`: xmlElementType
name: cstring
children, next: xmlNodePtr
content: cstring
properties: xmlAttrPtr
nsDef: xmlNsPtr
xmlDocPtr {.libxslt.} = distinct pointer
xsltStylesheetPtr {.libxslt.} = distinct pointer
proc isNil(x: xmlDocPtr): bool {.borrow.}
proc isNil(x: xsltStylesheetPtr): bool {.borrow.}
proc xmlReadMemory(buf: pointer; len: cint; url, enc: cstring; opts: cint): xmlDocPtr {.libxslt.}
proc xmlReadMemory(buf: string; uri = "noname.xml"): xmlDocPtr =
xmlReadMemory(buf[0].addr, buf.len.cint, uri, "UTF-8", 0)
proc xmlParseFile(filename: cstring): xmlDocPtr {.libxslt.}
proc xmlFreeDoc(p: xmlDocPtr) {.libxslt.}
proc xmlDocGetRootElement(doc: xmlDocPtr): xmlNodePtr {.libxslt.}
proc loadXmlDoc(text: string): xmlDocPtr =
if text.startsWith("/") and fileExists(text):
xmlParseFile(text)
else:
xmlReadMemory(text, "noname.xml")
proc xsltParseStylesheetFile(filename: cstring): xsltStylesheetPtr {.libxslt.}
proc xsltParseStylesheetDoc(doc: xmlDocPtr): xsltStylesheetPtr {.libxslt.}
proc xsltParseStylesheetDoc(text: string; uri = "noname.xml"): xsltStylesheetPtr =
var doc = xmlReadMemory(text, uri)
result = xsltParseStylesheetDoc(doc)
# implicit free of doc
proc loadStylesheet(text: string): xsltStylesheetPtr =
if text.startsWith("/") and fileExists(text):
xsltParseStylesheetFile(text)
else:
xsltParseStylesheetDoc(text, "noname.xsl")
proc xsltApplyStylesheet(
style: xsltStylesheetPtr, doc: xmlDocPtr, params: cstringArray): xmlDocPtr {.libxslt.}
@ -41,27 +118,91 @@ proc xsltSaveResultToString(res: xmlDocPtr; style: xsltStylesheetPtr): string =
proc initLibXml =
discard
proc XML_GET_CONTENT(xn: xmlNodePtr): xmlElementContentPtr {.libxslt.}
proc textContent(xn: xmlNodePtr): string =
if xn.content != nil: result = $xn.content
proc content(attr: xmlAttrPtr): string =
var child = attr.children
while not child.isNil:
result.add child.content
child = child.next
proc preserveSiblings(result: var seq[Value]; first: xmlNodePtr) =
var xn = first
while not xn.isNil:
case xn.type
of XML_ELEMENT_NODE:
var child = Value(kind: pkRecord)
if not xn.nsDef.isNil:
child.record.add initDictionary()
var ns = xn.nsDef
while not ns.isNil:
if not ns.href.isNil:
var key = Value(kind: pkString)
if ns.prefix.isNil:
key.string = "xmlns"
else:
key.string = "xmlns:" & $ns.prefix
child.record[0][key] = toPreserves($ns.href)
ns = ns.next
if not xn.properties.isNil:
if child.record.len < 1:
child.record.add initDictionary()
var attr = xn.properties
while not attr.isNil:
var
key = toPreserves($attr.name)
val = toPreserves(attr.content)
child.record[0][key] = val
attr = attr.next
if not xn.children.isNil:
preserveSiblings(child.record, xn.children)
child.record.add tosymbol($xn.name)
result.add child
of XML_TEXT_NODE:
result.add textContent(xn).toPreserves
else:
stderr.writeLine "not an XML_ELEMENT_NODE - ", $xn.type
xn = xn.next
proc toPreservesHook*(xn: xmlNodePtr): Value =
var items = newSeqofCap[Value](1)
preserveSiblings(items, xn)
items[0]
proc spawnXsltActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
spawn("xslt", turn) do (turn: var Turn):
initLibXml()
during(turn, root, ?:XsltArguments) do (ds: Cap):
let sheetsPat = ?Observe(pattern: !XsltTransform) ?? {0: grab(), 1: grab()}
during(turn, ds, sheetsPat) do (stylesheet: Literal[string], input: Literal[string]):
let
cur = xsltParseStylesheetFile(stylesheet.value)
doc = xmlParseFile(input.value)
params = allocCStringArray([])
res = xsltApplyStylesheet(cur, doc, params)
output = xsltSaveResultToString(res, cur)
xmlFreeDoc(res)
xmlFreeDoc(doc)
deallocCStringArray(params)
xsltFreeStylesheet(cur)
publish(turn, ds, XsltTransform(
stylesheet: stylesheet.value,
input: input.value,
output: output,
))
let cur = loadStylesheet(stylesheet.value)
if cur.isNil:
stderr.writeLine "failed to parse stylesheet"
else:
let doc = loadXmlDoc(input.value)
if doc.isNil:
stderr.writeLine "failed to parse input document"
else:
let
params = allocCStringArray([])
res = xsltApplyStylesheet(cur, doc, params)
if res.isNil:
stderr.writeLine "failed to apply stylesheet transformation"
else:
let output = xsltSaveResultToString(res, cur)
deallocCStringArray(params)
publish(turn, ds, XsltTransform(
stylesheet: stylesheet.value,
input: input.value,
output: xmlDocGetRootElement(res).toPreservesHook,
))
xmlFreeDoc(res)
xmlFreeDoc(doc)
xsltFreeStylesheet(cur)
when isMainModule:
import syndicate/relays

View File

@ -1,6 +1,6 @@
# Package
version = "20240208"
version = "20240209"
author = "Emery Hemingway"
description = "Utilites for Syndicated Actors and Synit"
license = "unlicense"

View File

@ -12,8 +12,51 @@ let ?ds = dataspace
<xslt { dataspace: $ds }>
]
let ?stylesheet = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns=\"http://www.w3.org/1999/xhtml\" version=\"1.0\">
<xsl:output method=\"xml\" indent=\"yes\" encoding=\"UTF-8\"/>
<xsl:template match=\"/persons\">
<html>
<head>
<title>Testing XML Example</title>
</head>
<body>
<h1>Persons</h1>
<ul>
<xsl:apply-templates select=\"person\">
<xsl:sort select=\"family-name\"/>
</xsl:apply-templates>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match=\"person\">
<li>
<xsl:value-of select=\"family-name\"/>
<xsl:text>, </xsl:text>
<xsl:value-of select=\"name\"/>
</li>
</xsl:template>
</xsl:stylesheet>
"
let ?input = "<?xml version=\"1.0\"?>
<persons>
<person username=\"JS1\">
<name>John</name>
<family-name>Smith</family-name>
</person>
<person username=\"MI1\">
<name>Morka</name>
<family-name>Minicus</family-name>
</person>
</persons>
"
$ds [
? <xslt-transform "/home/emery/src/syndicate_utils/tests/stylesheet.xslt" "/home/emery/src/syndicate_utils/tests/input.xml" ?outputs> [
? <xslt-transform $stylesheet $input ?outputs> [
$log ! <log "-" { xslt-outputs: $outputs }>
]
]