xslt: accept text or paths, generate Preserves data
This commit is contained in:
parent
2c61d144d0
commit
c8e8197ff7
|
@ -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.
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Package
|
||||
|
||||
version = "20240208"
|
||||
version = "20240209"
|
||||
author = "Emery Hemingway"
|
||||
description = "Utilites for Syndicated Actors and Synit"
|
||||
license = "unlicense"
|
||||
|
|
|
@ -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 }>
|
||||
]
|
||||
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue